From: TimePath <andrew.hardaker1995@gmail.com>
Date: Sun, 29 Nov 2015 02:45:20 +0000 (+1100)
Subject: Merge branch 'master' into TimePath/stats
X-Git-Tag: xonotic-v0.8.2~1609^2~2
X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=1cbef966e133966c5b5f0f5b58fbd1a5851ed35b;p=xonotic%2Fxonotic-data.pk3dir.git

Merge branch 'master' into TimePath/stats

# Conflicts:
#	qcsrc/client/hud/panel/ammo.qc
#	qcsrc/client/hud/panel/powerups.qc
#	qcsrc/client/view.qc
#	qcsrc/common/mutators/mutator/nades/nades.qc
#	qcsrc/common/physics.qc
#	qcsrc/common/physics.qh
#	qcsrc/common/weapons/weapon/vortex.qc
#	qcsrc/lib/registry.qh
#	qcsrc/lib/stats.qh
---

1cbef966e133966c5b5f0f5b58fbd1a5851ed35b
diff --cc qcsrc/client/view.qc
index 87adec58b0,0f1a43e2fa..c98400df56
--- a/qcsrc/client/view.qc
+++ b/qcsrc/client/view.qc
@@@ -360,9 -360,9 +360,9 @@@ float TrueAimCheck(
  			break;
  	}
  
- 	vector traceorigin = getplayerorigin(player_localentnum-1) + (eZ * getstati(STAT_VIEWHEIGHT));
+ 	vector traceorigin = entcs_receiver(player_localentnum - 1).origin + (eZ * getstati(STAT_VIEWHEIGHT));
  
 -	vecs = decompressShotOrigin(getstati(STAT_SHOTORG));
 +	vecs = decompressShotOrigin(STAT(SHOTORG));
  
  	traceline(traceorigin, traceorigin + view_forward * MAX_SHOT_DISTANCE, mv, ta);
  	trueaimpoint = trace_endpos;
@@@ -1005,19 -1005,27 +1005,27 @@@ void HUD_Crosshair(
  
  void HUD_Draw()
  {
- 	if(STAT(FROZEN))
- 		drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, ((STAT(REVIVE_PROGRESS)) ? ('0.25 0.90 1' + ('1 0 0' * STAT(REVIVE_PROGRESS)) + ('0 1 1' * STAT(REVIVE_PROGRESS) * -1)) : '0.25 0.90 1'), autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
- 	else if (STAT(HEALING_ORB)>time)
- 		drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, NADE_TYPE_HEAL.m_color, autocvar_hud_colorflash_alpha*STAT(HEALING_ORB_ALPHA), DRAWFLAG_ADDITIVE);
+ 	vector rgb = '0 0 0';
+ 	float a = 1;
+ 	if (MUTATOR_CALLHOOK(HUD_Draw_overlay))
+ 	{
+ 		rgb = MUTATOR_ARGV(0, vector);
+ 		a = MUTATOR_ARGV(0, float);
+ 	}
 -	else if(getstati(STAT_FROZEN))
++	else if(STAT(FROZEN))
+ 	{
 -		rgb = ((getstatf(STAT_REVIVE_PROGRESS)) ? ('0.25 0.90 1' + ('1 0 0' * getstatf(STAT_REVIVE_PROGRESS)) + ('0 1 1' * getstatf(STAT_REVIVE_PROGRESS) * -1)) : '0.25 0.90 1');
++		rgb = ((STAT(REVIVE_PROGRESS)) ? ('0.25 0.90 1' + ('1 0 0' * STAT(REVIVE_PROGRESS)) + ('0 1 1' * STAT(REVIVE_PROGRESS) * -1)) : '0.25 0.90 1');
+ 	}
+ 	drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, rgb, autocvar_hud_colorflash_alpha * a, DRAWFLAG_ADDITIVE);
  	if(!intermission)
 -	if(getstatf(STAT_NADE_TIMER) && autocvar_cl_nade_timer) // give nade top priority, as it's a matter of life and death
 +	if(STAT(NADE_TIMER) && autocvar_cl_nade_timer) // give nade top priority, as it's a matter of life and death
  	{
 -		DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_NADE_TIMER), '0.25 0.90 1' + ('1 0 0' * getstatf(STAT_NADE_TIMER)) - ('0 1 1' * getstatf(STAT_NADE_TIMER)), autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
 +		DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", STAT(NADE_TIMER), '0.25 0.90 1' + ('1 0 0' * STAT(NADE_TIMER)) - ('0 1 1' * STAT(NADE_TIMER)), autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
  		drawstring_aspect(eY * 0.64 * vid_conheight, ((autocvar_cl_nade_timer == 2) ? _("Nade timer") : ""), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL);
  	}
 -	else if(getstatf(STAT_REVIVE_PROGRESS))
 +	else if(STAT(REVIVE_PROGRESS))
  	{
 -		DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
 +		DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", STAT(REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
  		drawstring_aspect(eY * 0.64 * vid_conheight, _("Revival progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL);
  	}
  
diff --cc qcsrc/common/mutators/mutator/buffs/all.qh
index 0000000000,db22d31411..76ecff8be1
mode 000000,100644..100644
--- a/qcsrc/common/mutators/mutator/buffs/all.qh
+++ b/qcsrc/common/mutators/mutator/buffs/all.qh
@@@ -1,0 -1,73 +1,73 @@@
+ #ifndef BUFFS_ALL_H
+ #define BUFFS_ALL_H
+ // Welcome to the stuff behind the scenes
+ // Below, you will find the list of buffs
+ // Add new buffs here!
+ // Note: Buffs also need spawnfuncs, which are set below
+ 
+ #include "../../../teams.qh"
+ #include "../../../util.qh"
+ 
+ REGISTER_WAYPOINT(Buff, _("Buff"), '1 0.5 0', 1);
+ REGISTER_RADARICON(Buff, 1);
+ 
+ REGISTRY(Buffs, BITS(4))
+ #define Buffs_from(i) _Buffs_from(i, BUFF_Null)
+ REGISTER_REGISTRY(Buffs)
+ REGISTRY_CHECK(Buffs)
+ 
+ #define REGISTER_BUFF(id) \
+     REGISTER(Buffs, BUFF_##id, m_id, NEW(Buff)); \
+     REGISTER_INIT_POST(BUFF_##id) { \
+         this.netname = this.m_name; \
+         this.m_itemid = BIT(this.m_id - 1); \
+         this.m_sprite = strzone(strcat("buff-", this.m_name)); \
+     } \
+     REGISTER_INIT(BUFF_##id)
+ 
+ #include "../../../items/item/pickup.qh"
+ CLASS(Buff, Pickup)
+ 	/** bit index */
+ 	ATTRIB(Buff, m_itemid, int, 0)
+ 	ATTRIB(Buff, m_name, string, "buff")
+ 	ATTRIB(Buff, m_color, vector, '1 1 1')
+ 	ATTRIB(Buff, m_prettyName, string, "Buff")
+ 	ATTRIB(Buff, m_skin, int, 0)
+ 	ATTRIB(Buff, m_sprite, string, "")
+ 	METHOD(Buff, display, void(entity this, void(string name, string icon) returns)) {
+ 		returns(this.m_prettyName, sprintf("/gfx/hud/%s/buff_%s", cvar_string("menu_skin"), this.m_name));
+ 	}
+ #ifdef SVQC
+ 	METHOD(Buff, m_time, float(Buff this))
+ 	{ return cvar(strcat("g_buffs_", this.netname, "_time")); }
+ #endif
+ ENDCLASS(Buff)
+ 
+ #ifdef SVQC
 -	.int buffs;
++	// .int buffs = _STAT(BUFFS);
+ 	void buff_Init(entity ent);
+ 	void buff_Init_Compat(entity ent, entity replacement);
+ 	#define BUFF_SPAWNFUNC(e, b, t) spawnfunc(item_buff_##e) { \
+ 		self.buffs = b.m_itemid; \
+ 		self.team = t; \
+ 		buff_Init(self); \
+ 	}
+ 	#define BUFF_SPAWNFUNCS(e, b)                       \
+ 			BUFF_SPAWNFUNC(e,           b,  0)          \
+ 			BUFF_SPAWNFUNC(e##_team1,   b,  NUM_TEAM_1) \
+ 			BUFF_SPAWNFUNC(e##_team2,   b,  NUM_TEAM_2) \
+ 			BUFF_SPAWNFUNC(e##_team3,   b,  NUM_TEAM_3) \
+ 			BUFF_SPAWNFUNC(e##_team4,   b,  NUM_TEAM_4)
+ 	#define BUFF_SPAWNFUNC_Q3TA_COMPAT(o, r) spawnfunc(item_##o) { buff_Init_Compat(self, r); }
+ #else
+ 	#define BUFF_SPAWNFUNC(e, b, t)
+ 	#define BUFF_SPAWNFUNCS(e, b)
+ 	#define BUFF_SPAWNFUNC_Q3TA_COMPAT(o, r)
+ #endif
+ 
+ REGISTER_BUFF(Null);
+ BUFF_SPAWNFUNCS(random, BUFF_Null)
+ 
+ #include "all.inc"
+ 
+ #endif
diff --cc qcsrc/common/mutators/mutator/buffs/buffs.qc
index fbddd037bc,9eb113a96f..dbabdd2e2f
--- a/qcsrc/common/mutators/mutator/buffs/buffs.qc
+++ b/qcsrc/common/mutators/mutator/buffs/buffs.qc
@@@ -75,9 -75,8 +75,8 @@@ const vector BUFF_MAX = ('16 16 20')
  
  #include "../../../triggers/target/music.qh"
  #include "../../../gamemodes/all.qh"
- #include "../../../buffs/all.qh"
  
 -.float buff_time;
 +.float buff_time = _STAT(BUFF_TIME);
  void buffs_DelayedInit();
  
  REGISTER_MUTATOR(buffs, cvar("g_buffs"))
diff --cc qcsrc/common/mutators/mutator/buffs/module.inc
index 89d31a1b31,5f24f627e7..be00977148
--- a/qcsrc/common/mutators/mutator/buffs/module.inc
+++ b/qcsrc/common/mutators/mutator/buffs/module.inc
@@@ -1,3 -2,45 +2,45 @@@
  #ifdef SVQC
  #include "buffs.qc"
  #endif
+ 
+ #ifdef IMPLEMENTATION
+ 
+ string BUFF_NAME(int i)
+ {
+     Buff b = Buffs_from(i);
+     return sprintf("%s%s", rgb_to_hexcolor(b.m_color), b.m_prettyName);
+ }
+ 
+ #ifndef MENUQC
+ REGISTER_MUTATOR(buffs_flight, true);
+ MUTATOR_HOOKFUNCTION(buffs_flight, IsFlying)
+ {
+     noref entity e = MUTATOR_ARGV(0, entity);
+ 	return BUFFS_STAT(e) & BUFF_FLIGHT.m_itemid;
+ }
+ #endif
+ 
+ #ifdef CSQC
+ REGISTER_MUTATOR(cl_buffs, true);
+ MUTATOR_HOOKFUNCTION(cl_buffs, HUD_Powerups_add)
+ {
 -    int allBuffs = getstati(STAT_BUFFS, 0, 24);
++    int allBuffs = STAT(BUFFS);
+     FOREACH(Buffs, it.m_itemid & allBuffs, LAMBDA(
 -		addPowerupItem(it.m_prettyName, strcat("buff_", it.m_name), it.m_color, bound(0, getstatf(STAT_BUFF_TIME) - time, 99), 60);
++		addPowerupItem(it.m_prettyName, strcat("buff_", it.m_name), it.m_color, bound(0, STAT(BUFF_TIME) - time, 99), 60);
+ 	));
+ }
+ MUTATOR_HOOKFUNCTION(cl_buffs, WP_Format)
+ {
+     entity this = MUTATOR_ARGV(0, entity);
+     string s = MUTATOR_ARGV(0, string);
+     if (s == WP_Buff.netname || s == RADARICON_Buff.netname)
+     {
+         Buff b = Buffs_from(this.wp_extra);
+         MUTATOR_ARGV(0, vector) = b.m_color;
+         MUTATOR_ARGV(0, string) = b.m_prettyName;
+         return true;
+     }
+ }
+ 
+ #endif
+ #endif
diff --cc qcsrc/common/mutators/mutator/nades/nades.qc
index 0c30186994,12a53ff139..ec57e94c1e
--- a/qcsrc/common/mutators/mutator/nades/nades.qc
+++ b/qcsrc/common/mutators/mutator/nades/nades.qc
@@@ -1,50 -1,124 +1,124 @@@
- #ifndef MUTATOR_NADES_H
- #define MUTATOR_NADES_H
+ #include "nades.qh"
  
- #ifdef SVQC
- #include "../../../../server/mutators/mutator/gamemode_freezetag.qc"
+ #ifdef IMPLEMENTATION
+ 
+ #ifndef MENUQC
+ entity Nade_TrailEffect(int proj, int nade_team)
+ {
+     switch (proj)
+     {
+         case PROJECTILE_NADE:       return EFFECT_NADE_TRAIL(nade_team);
+         case PROJECTILE_NADE_BURN:  return EFFECT_NADE_TRAIL_BURN(nade_team);
+     }
+ 
+     FOREACH(Nades, true, LAMBDA(
+         for (int j = 0; j < 2; j++)
+         {
+             if (it.m_projectile[j] == proj)
+             {
+                 string trail = it.m_trail[j].eent_eff_name;
+                 if (trail) return it.m_trail[j];
+                 break;
+             }
+         }
+     ));
+ 
+     return EFFECT_Null;
+ }
  #endif
  
- .entity nade;
- .entity fake_nade;
- .float nade_timer = _STAT(NADE_TIMER);
- .float nade_refire;
- .float bonus_nades = _STAT(NADE_BONUS);
- .float nade_special_time;
- .float bonus_nade_score = _STAT(NADE_BONUS_SCORE);
- .int nade_type = _STAT(NADE_BONUS_TYPE);
- .string pokenade_type;
- .entity nade_damage_target;
- .float cvar_cl_nade_type;
- .string cvar_cl_pokenade_type;
- .float toss_time;
- .float stat_healing_orb = _STAT(HEALING_ORB);
- .float stat_healing_orb_alpha = _STAT(HEALING_ORB_ALPHA);
- .float nade_show_particles;
- 
- // Remove nades that are being thrown
- void nades_Clear(entity player);
- 
- // Give a bonus grenade to a player
- void(entity player, float score) nades_GiveBonus;
- 
- /**
-  * called to adjust nade damage and force on hit
-  */
- #define EV_Nade_Damage(i, o) \
-  	/** weapon */ i(entity, MUTATOR_ARGV_0_entity) \
-     /** force */  i(vector, MUTATOR_ARGV_0_vector) \
-     /**/          o(vector, MUTATOR_ARGV_0_vector) \
-  	/** damage */ i(float,  MUTATOR_ARGV_0_float) \
-     /**/          o(float,  MUTATOR_ARGV_0_float) \
-     /**/
- MUTATOR_HOOKABLE(Nade_Damage, EV_Nade_Damage);
+ #ifdef CSQC
+ REGISTER_MUTATOR(cl_nades, true);
+ MUTATOR_HOOKFUNCTION(cl_nades, HUD_Draw_overlay)
+ {
 -	if (getstatf(STAT_HEALING_ORB) <= time) return false;
++	if (STAT(HEALING_ORB) <= time) return false;
+ 	MUTATOR_ARGV(0, vector) = NADE_TYPE_HEAL.m_color;
 -	MUTATOR_ARGV(0, float) = getstatf(STAT_HEALING_ORB_ALPHA);
++	MUTATOR_ARGV(0, float) = STAT(HEALING_ORB_ALPHA);
+ 	return true;
+ }
+ MUTATOR_HOOKFUNCTION(cl_nades, Ent_Projectile)
+ {
+ 	if (self.cnt == PROJECTILE_NAPALM_FOUNTAIN)
+ 	{
+ 		self.modelindex = 0;
+ 		self.traileffect = EFFECT_FIREBALL.m_id;
+ 		return true;
+ 	}
+ 	if (Nade_FromProjectile(self.cnt) != NADE_TYPE_Null)
+ 	{
+ 		setmodel(self, MDL_PROJECTILE_NADE);
+ 		entity trail = Nade_TrailEffect(self.cnt, self.team);
+ 		if (trail.eent_eff_name) self.traileffect = trail.m_id;
+ 		return true;
+ 	}
+ }
+ MUTATOR_HOOKFUNCTION(cl_nades, EditProjectile)
+ {
+ 	if (self.cnt == PROJECTILE_NAPALM_FOUNTAIN)
+ 	{
+ 		loopsound(self, CH_SHOTS_SINGLE, SND(FIREBALL_FLY2), VOL_BASE, ATTEN_NORM);
+ 		self.mins = '-16 -16 -16';
+ 		self.maxs = '16 16 16';
+ 	}
+ 
+ 	entity nade_type = Nade_FromProjectile(self.cnt);
+ 	if (nade_type == NADE_TYPE_Null) return;
+ 	self.mins = '-16 -16 -16';
+ 	self.maxs = '16 16 16';
+ 	self.colormod = nade_type.m_color;
+ 	self.move_movetype = MOVETYPE_BOUNCE;
+ 	self.move_touch = func_null;
+ 	self.scale = 1.5;
+ 	self.avelocity = randomvec() * 720;
+ 
+ 	if (nade_type == NADE_TYPE_TRANSLOCATE || nade_type == NADE_TYPE_SPAWN)
+ 		self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+ 	else
+ 		self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY;
+ }
+ bool Projectile_isnade(int p)
+ {
+ 	return Nade_FromProjectile(p) != NADE_TYPE_Null;
+ }
+ void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expand_time)
+ {
 -	float bonusNades    = getstatf(STAT_NADE_BONUS);
 -	float bonusProgress = getstatf(STAT_NADE_BONUS_SCORE);
 -	float bonusType     = getstati(STAT_NADE_BONUS_TYPE);
++	float bonusNades    = STAT(NADE_BONUS);
++	float bonusProgress = STAT(NADE_BONUS_SCORE);
++	float bonusType     = STAT(NADE_BONUS_TYPE);
+ 	Nade def = Nades_from(bonusType);
+ 	vector nadeColor    = def.m_color;
+ 	string nadeIcon     = def.m_icon;
+ 
+ 	vector iconPos, textPos;
+ 
+ 	if(autocvar_hud_panel_ammo_iconalign)
+ 	{
+ 		iconPos = myPos + eX * 2 * mySize.y;
+ 		textPos = myPos;
+ 	}
+ 	else
+ 	{
+ 		iconPos = myPos;
+ 		textPos = myPos + eX * mySize.y;
+ 	}
+ 
+ 	if(bonusNades > 0 || bonusProgress > 0)
+ 	{
+ 		DrawNadeProgressBar(myPos, mySize, bonusProgress, nadeColor);
+ 
+ 		if(autocvar_hud_panel_ammo_text)
+ 			drawstring_aspect(textPos, ftos(bonusNades), eX * (2/3) * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ 
+ 		if(draw_expanding)
+ 			drawpic_aspect_skin_expanding(iconPos, nadeIcon, '1 1 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL, expand_time);
  
+ 		drawpic_aspect_skin(iconPos, nadeIcon, '1 1 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+ 	}
+ }
  #endif
  
- #ifdef IMPLEMENTATION
+ #ifdef SVQC
  
- #include "../../../nades/all.qh"
  #include "../../../gamemodes/all.qh"
  #include "../../../monsters/spawn.qh"
  #include "../../../monsters/sv_monsters.qh"
diff --cc qcsrc/common/mutators/mutator/nades/nades.qh
index 0000000000,2e4829354a..312cf4ae2d
mode 000000,100644..100644
--- a/qcsrc/common/mutators/mutator/nades/nades.qh
+++ b/qcsrc/common/mutators/mutator/nades/nades.qh
@@@ -1,0 -1,103 +1,103 @@@
+ #ifndef NADES_ALL_H
+ #define NADES_ALL_H
+ 
+ #include "../../../teams.qh"
+ 
+ // use slots 70-100
+ const int PROJECTILE_NADE = 71;
+ const int PROJECTILE_NADE_BURN = 72;
+ const int PROJECTILE_NADE_NAPALM = 73;
+ const int PROJECTILE_NADE_NAPALM_BURN = 74;
+ const int PROJECTILE_NAPALM_FOUNTAIN = 75;
+ const int PROJECTILE_NADE_ICE = 76;
+ const int PROJECTILE_NADE_ICE_BURN = 77;
+ const int PROJECTILE_NADE_TRANSLOCATE = 78;
+ const int PROJECTILE_NADE_SPAWN = 79;
+ const int PROJECTILE_NADE_HEAL = 80;
+ const int PROJECTILE_NADE_HEAL_BURN = 81;
+ const int PROJECTILE_NADE_MONSTER = 82;
+ const int PROJECTILE_NADE_MONSTER_BURN = 83;
+ 
+ REGISTRY(Nades, BITS(4))
+ #define Nades_from(i) _Nades_from(i, NADE_TYPE_Null)
+ REGISTER_REGISTRY(Nades)
+ REGISTRY_CHECK(Nades)
+ 
+ #define REGISTER_NADE(id) REGISTER(Nades, NADE_TYPE, id, m_id, NEW(Nade))
+ 
+ CLASS(Nade, Object)
+     ATTRIB(Nade, m_id, int, 0)
+     ATTRIB(Nade, m_color, vector, '0 0 0')
+     ATTRIB(Nade, m_name, string, _("Grenade"))
+     ATTRIB(Nade, m_icon, string, "nade_normal")
+     ATTRIBARRAY(Nade, m_projectile, int, 2)
+     ATTRIBARRAY(Nade, m_trail, entity, 2)
+     METHOD(Nade, display, void(entity this, void(string name, string icon) returns)) {
+         returns(this.m_name, sprintf("/gfx/hud/%s/%s", cvar_string("menu_skin"), this.m_icon));
+     }
+ ENDCLASS(Nade)
+ 
+ REGISTER_NADE(Null);
+ 
+ Nade Nade_FromProjectile(int proj)
+ {
+     FOREACH(Nades, true, LAMBDA(
+         for (int j = 0; j < 2; j++)
+         {
+             if (it.m_projectile[j] == proj) return it;
+         }
+     ));
+     return NADE_TYPE_Null;
+ }
+ 
+ #ifndef MENUQC
+ #include "effects.inc"
+ #endif
+ 
+ #include "nades.inc"
+ 
+ .float healer_lifetime;
+ .float healer_radius;
+ 
+ #ifdef SVQC
+ 
+ .entity nade;
+ .entity fake_nade;
 -.float nade_timer;
++.float nade_timer = _STAT(NADE_TIMER);
+ .float nade_refire;
 -.float bonus_nades;
++.float bonus_nades = _STAT(NADE_BONUS);
+ .float nade_special_time;
 -.float bonus_nade_score;
 -.float nade_type;
++.float bonus_nade_score = _STAT(NADE_BONUS_SCORE);
++.int nade_type = _STAT(NADE_BONUS_TYPE);
+ .string pokenade_type;
+ .entity nade_damage_target;
+ .float cvar_cl_nade_type;
+ .string cvar_cl_pokenade_type;
+ .float toss_time;
 -.float stat_healing_orb;
 -.float stat_healing_orb_alpha;
++.float stat_healing_orb = _STAT(HEALING_ORB);
++.float stat_healing_orb_alpha = _STAT(HEALING_ORB_ALPHA);
+ .float nade_show_particles;
+ 
+ bool healer_send(entity this, entity to, int sf);
+ 
+ // Remove nades that are being thrown
+ void nades_Clear(entity player);
+ 
+ // Give a bonus grenade to a player
+ void(entity player, float score) nades_GiveBonus;
+ 
+ /**
+  * called to adjust nade damage and force on hit
+  */
+ #define EV_Nade_Damage(i, o) \
+  	/** weapon */ i(entity, MUTATOR_ARGV_0_entity) \
+     /** force */  i(vector, MUTATOR_ARGV_0_vector) \
+     /**/          o(vector, MUTATOR_ARGV_0_vector) \
+  	/** damage */ i(float,  MUTATOR_ARGV_0_float) \
+     /**/          o(float,  MUTATOR_ARGV_0_float) \
+     /**/
+ MUTATOR_HOOKABLE(Nade_Damage, EV_Nade_Damage);
+ 
+ #endif
+ 
+ #endif
diff --cc qcsrc/common/physics.qc
index 71bc63e1be,63e7ae7e91..0526742b4a
--- a/qcsrc/common/physics.qc
+++ b/qcsrc/common/physics.qc
@@@ -1382,20 -1476,20 +1366,20 @@@ void PM_walk(entity this, float maxspd_
  			v >= PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
  		 */
  	}
- 	float addspeed = wishspeed - self.velocity * wishdir;
+ 	const float addspeed = wishspeed - this.velocity * wishdir;
  	if (addspeed > 0)
  	{
- 		float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
- 		self.velocity += accelspeed * wishdir;
+ 		const float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
+ 		this.velocity += accelspeed * wishdir;
  	}
- 	float g = PHYS_GRAVITY(this) * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
 -	const float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(this) * PHYS_INPUT_TIMELENGTH;
++	const float g = PHYS_GRAVITY(this) * PHYS_ENTGRAVITY(this) * PHYS_INPUT_TIMELENGTH;
  	if (!(GAMEPLAYFIX_NOGRAVITYONGROUND))
- 		self.velocity_z -= g * (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1);
- 	if (self.velocity * self.velocity)
+ 		this.velocity_z -= g * (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1);
+ 	if (vdist(this.velocity, >, 0))
  		PM_ClientMovement_Move();
  	if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
- 		if (!IS_ONGROUND(self) || !GAMEPLAYFIX_NOGRAVITYONGROUND)
- 			self.velocity_z -= g * 0.5;
+ 		if (!IS_ONGROUND(this) || !GAMEPLAYFIX_NOGRAVITYONGROUND)
+ 			this.velocity_z -= g * 0.5;
  }
  
  void PM_air(float buttons_prev, float maxspd_mod)
@@@ -1513,10 -1607,10 +1497,10 @@@ void PM_Main(entity this
  	maxspeed_mod *= PHYS_HIGHSPEED;
  
  #ifdef SVQC
 -	Physics_UpdateStats(maxspeed_mod);
 +	Physics_UpdateStats(this, maxspeed_mod);
  
- 	if (self.PlayerPhysplug)
- 		if (self.PlayerPhysplug())
+ 	if (this.PlayerPhysplug)
+ 		if (this.PlayerPhysplug())
  			return;
  #endif
  
diff --cc qcsrc/common/physics.qh
index 682e58712f,639776593c..fb396d1596
--- a/qcsrc/common/physics.qh
+++ b/qcsrc/common/physics.qh
@@@ -159,11 -72,16 +159,11 @@@ bool IsFlying(entity a)
  
  	#define PHYS_DEAD(s)						s.csqcmodel_isdead
  
 -	#define GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE	boolean(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
 -	#define GAMEPLAYFIX_NOGRAVITYONGROUND			cvar("sv_gameplayfix_nogravityonground")
 -	#define GAMEPLAYFIX_Q2AIRACCELERATE				cvar("sv_gameplayfix_q2airaccelerate")
 -	#define GAMEPLAYFIX_EASIERWATERJUMP 			getstati(STAT_GAMEPLAYFIX_EASIERWATERJUMP)
 -	#define GAMEPLAYFIX_DOWNTRACEONGROUND			getstati(STAT_GAMEPLAYFIX_DOWNTRACEONGROUND)
 -	#define GAMEPLAYFIX_STEPMULTIPLETIMES			getstati(STAT_GAMEPLAYFIX_STEPMULTIPLETIMES)
 -	#define GAMEPLAYFIX_UNSTICKPLAYERS				getstati(STAT_GAMEPLAYFIX_UNSTICKPLAYERS)
 -	#define GAMEPLAYFIX_STEPDOWN					getstati(STAT_GAMEPLAYFIX_STEPDOWN)
 +	#define GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE	(boolean(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE))
 +	#define GAMEPLAYFIX_NOGRAVITYONGROUND			(boolean(moveflags & MOVEFLAG_NOGRAVITYONGROUND))
 +	#define GAMEPLAYFIX_Q2AIRACCELERATE				(boolean(moveflags & MOVEFLAG_Q2AIRACCELERATE))
  
- 	#define IS_DUCKED(s)    					!!(s.flags & FL_DUCKED)
+ 	#define IS_DUCKED(s)    					boolean(s.flags & FL_DUCKED)
  	#define SET_DUCKED(s)   					s.flags |= FL_DUCKED
  	#define UNSET_DUCKED(s) 					s.flags &= ~FL_DUCKED
  
@@@ -175,9 -93,32 +175,9 @@@
  	#define SET_ONGROUND(s)   					s.flags |= FL_ONGROUND
  	#define UNSET_ONGROUND(s) 					s.flags &= ~FL_ONGROUND
  
- 	#define WAS_ONGROUND(s)						!!(s.lastflags & FL_ONGROUND)
+ 	#define WAS_ONGROUND(s)						boolean(s.lastflags & FL_ONGROUND)
  
  	#define ITEMS_STAT(s) 						(s).items
 -	#define BUFFS_STAT(s)						getstati(STAT_BUFFS)
 -
 -	#define PHYS_AMMO_FUEL(s)					getstati(STAT_FUEL)
 -
 -	#define PHYS_FROZEN(s)						getstati(STAT_FROZEN)
 -
 -	#define PHYS_DOUBLEJUMP						getstati(STAT_DOUBLEJUMP)
 -
 -	#define PHYS_BUGRIGS						getstati(STAT_BUGRIGS)
 -	#define PHYS_BUGRIGS_ANGLE_SMOOTHING 		getstati(STAT_BUGRIGS_ANGLE_SMOOTHING)
 -	#define PHYS_BUGRIGS_PLANAR_MOVEMENT 		getstati(STAT_BUGRIGS_PLANAR_MOVEMENT)
 -	#define PHYS_BUGRIGS_REVERSE_SPEEDING 		getstati(STAT_BUGRIGS_REVERSE_SPEEDING)
 -	#define PHYS_BUGRIGS_FRICTION_FLOOR 		getstatf(STAT_BUGRIGS_FRICTION_FLOOR)
 -	#define PHYS_BUGRIGS_AIR_STEERING 			getstati(STAT_BUGRIGS_AIR_STEERING)
 -	#define PHYS_BUGRIGS_FRICTION_BRAKE 		getstatf(STAT_BUGRIGS_FRICTION_BRAKE)
 -	#define PHYS_BUGRIGS_ACCEL 					getstatf(STAT_BUGRIGS_ACCEL)
 -	#define PHYS_BUGRIGS_SPEED_REF 				getstatf(STAT_BUGRIGS_SPEED_REF)
 -	#define PHYS_BUGRIGS_SPEED_POW 				getstatf(STAT_BUGRIGS_SPEED_POW)
 -	#define PHYS_BUGRIGS_STEER 					getstatf(STAT_BUGRIGS_STEER)
 -	#define PHYS_BUGRIGS_FRICTION_AIR 			getstatf(STAT_BUGRIGS_FRICTION_AIR)
 -	#define PHYS_BUGRIGS_CAR_JUMPING 			getstatf(STAT_BUGRIGS_CAR_JUMPING)
 -	#define PHYS_BUGRIGS_REVERSE_SPINNING 		getstatf(STAT_BUGRIGS_REVERSE_SPINNING)
 -	#define PHYS_BUGRIGS_REVERSE_STOPPING 		getstatf(STAT_BUGRIGS_REVERSE_STOPPING)
  
  	#define PHYS_JUMPSPEEDCAP_MIN				cvar_string("cl_jumpspeedcap_min")
  	#define PHYS_JUMPSPEEDCAP_MAX				cvar_string("cl_jumpspeedcap_max")
@@@ -238,9 -296,32 +238,9 @@@
  	#define SET_ONGROUND(s)   					s.flags |= FL_ONGROUND
  	#define UNSET_ONGROUND(s) 					s.flags &= ~FL_ONGROUND
  
- 	#define WAS_ONGROUND(s)						!!((s).lastflags & FL_ONGROUND)
+ 	#define WAS_ONGROUND(s)						boolean((s).lastflags & FL_ONGROUND)
  
  	#define ITEMS_STAT(s)						s.items
 -	#define BUFFS_STAT(s)						(s).buffs
 -
 -	#define PHYS_AMMO_FUEL(s)					s.ammo_fuel
 -
 -	#define PHYS_FROZEN(s)						s.frozen
 -
 -	#define PHYS_DOUBLEJUMP						autocvar_sv_doublejump
 -
 -	#define PHYS_BUGRIGS						g_bugrigs
 -	#define PHYS_BUGRIGS_ANGLE_SMOOTHING		g_bugrigs_angle_smoothing
 -	#define PHYS_BUGRIGS_PLANAR_MOVEMENT		g_bugrigs_planar_movement
 -	#define PHYS_BUGRIGS_REVERSE_SPEEDING		g_bugrigs_reverse_speeding
 -	#define PHYS_BUGRIGS_FRICTION_FLOOR			g_bugrigs_friction_floor
 -	#define PHYS_BUGRIGS_AIR_STEERING			g_bugrigs_air_steering
 -	#define PHYS_BUGRIGS_FRICTION_BRAKE			g_bugrigs_friction_brake
 -	#define PHYS_BUGRIGS_ACCEL					g_bugrigs_accel
 -	#define PHYS_BUGRIGS_SPEED_REF				g_bugrigs_speed_ref
 -	#define PHYS_BUGRIGS_SPEED_POW				g_bugrigs_speed_pow
 -	#define PHYS_BUGRIGS_STEER					g_bugrigs_steer
 -	#define PHYS_BUGRIGS_FRICTION_AIR			g_bugrigs_friction_air
 -	#define PHYS_BUGRIGS_CAR_JUMPING			g_bugrigs_planar_movement_car_jumping
 -	#define PHYS_BUGRIGS_REVERSE_SPINNING		g_bugrigs_reverse_spinning
 -	#define PHYS_BUGRIGS_REVERSE_STOPPING		g_bugrigs_reverse_stopping
  
  	#define PHYS_JUMPSPEEDCAP_MIN				autocvar_sv_jumpspeedcap_min
  	#define PHYS_JUMPSPEEDCAP_MAX				autocvar_sv_jumpspeedcap_max
diff --cc qcsrc/common/weapons/all.qh
index baa11371ac,8d62426daf..eb7a48f7ff
--- a/qcsrc/common/weapons/all.qh
+++ b/qcsrc/common/weapons/all.qh
@@@ -7,9 -7,9 +7,7 @@@
  
  // weapon sets
  typedef vector WepSet;
- #define WEPSET(id) WepSet_FromWeapon(WEP_##id.m_id)
- WepSet WepSet_FromWeapon(int a);
  #ifdef SVQC
 -void WepSet_AddStat();
 -void WepSet_AddStat_InMap();
  void WriteWepSet(float dest, WepSet w);
  #endif
  
diff --cc qcsrc/common/weapons/weapon/vortex.qc
index 072bc1cbee,8a90679b81..5e31fbdbee
--- a/qcsrc/common/weapons/weapon/vortex.qc
+++ b/qcsrc/common/weapons/weapon/vortex.qc
@@@ -91,7 -91,8 +91,8 @@@ NET_HANDLE(TE_CSQC_VORTEXBEAMPARTICLE, 
  	charge = sqrt(charge); // divide evenly among trail spacing and alpha
  	particles_alphamin = particles_alphamax = particles_fade = charge;
  
- 	if (autocvar_cl_particles_oldvortexbeam && (STAT(ALLOW_OLDVORTEXBEAM) || isdemo()))
+ 	if(!MUTATOR_CALLHOOK(Particles_VortexBeam, shotorg, endpos))
 -	if(autocvar_cl_particles_oldvortexbeam && (getstati(STAT_ALLOW_OLDVORTEXBEAM) || isdemo()))
++	if(autocvar_cl_particles_oldvortexbeam && (STAT(ALLOW_OLDVORTEXBEAM) || isdemo()))
  		WarpZone_TrailParticles_WithMultiplier(world, particleeffectnum(EFFECT_VORTEX_BEAM_OLD), shotorg, endpos, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE);
  	else
  		WarpZone_TrailParticles_WithMultiplier(world, particleeffectnum(EFFECT_VORTEX_BEAM), shotorg, endpos, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE);
diff --cc qcsrc/dpdefs/csprogsdefs.qh
index 79cfe76c6a,b9bcec58e6..ce39bca1bd
--- a/qcsrc/dpdefs/csprogsdefs.qh
+++ b/qcsrc/dpdefs/csprogsdefs.qh
@@@ -33,14 -27,6 +33,12 @@@
  #undef pointparticles
  #undef setmodel
  
 +#undef STAT_FRAGLIMIT
 +#undef STAT_TIMELIMIT
 +#undef STAT_MOVEVARS_TICRATE
 +#undef STAT_MOVEVARS_TIMESCALE
 +#undef STAT_MOVEVARS_GRAVITY
 +
  #pragma noref 0
  
- #define ReadFloat() ReadCoord()
- 
  #endif
diff --cc qcsrc/lib/registry.qh
index 8ad8be56e2,40a39395bc..f2bdd1e261
--- a/qcsrc/lib/registry.qh
+++ b/qcsrc/lib/registry.qh
@@@ -29,48 -43,39 +43,49 @@@ REGISTRY(Registries, BITS(8)
   *         } \
   *         REGISTER_INIT(FOO, id)
   *
-  * Don't forget to forward declare `initfunc` and call `REGISTER_REGISTRY`:
-  *     void RegisterFoos();
-  *     REGISTER_REGISTRY(RegisterFoos)
   *
-  * @param initfunc  The global constructor to accumulate into
+  * @param registry  The registry to add each entity to.
   * @param ns        Short for namespace, prefix for each global (ns##_##id)
-  * @param array     The array to add each entity to. Also requires `array##_first` and `array##_last` to be defined
   * @param id        The identifier of the current entity being registered
-  * @param fld       The field to store the current count into
+  * @param fld       The field to store the locally unique unique entity id
   * @param inst      An expression to create a new instance, invoked for every registration
   */
- #define REGISTER(initfunc, ns, array, id, fld, inst) \
- 	entity ns##_##id; \
- 	REGISTER_INIT(ns, id) {} \
- 	REGISTER_INIT_POST(ns, id) {} \
- 	void Register_##ns##_##id() \
+ #define REGISTER(...) EVAL(OVERLOAD(REGISTER, __VA_ARGS__))
+ #define REGISTER_5(registry, ns, id, fld, inst) REGISTER_4(registry, ns##_##id, fld, inst)
+ #define REGISTER_4(registry, id, fld, inst) \
+ 	entity id; \
+ 	REGISTER_INIT(id) {} \
+ 	REGISTER_INIT_POST(id) {} \
+ 	void Register_##id() \
  	{ \
- 		if (array##_COUNT >= array##_MAX) LOG_FATALF("Registry capacity exceeded (%s)", ftos(array##_MAX)); \
- 		entity this = inst; \
- 		ns##_##id = this; \
+ 		if (registry##_COUNT >= registry##_MAX) LOG_FATALF("Registry capacity exceeded (%s)", ftos(registry##_MAX)); \
+ 		entity this = id = inst; \
  		this.registered_id = #id; \
- 		REGISTRY_PUSH(array, fld, this); \
- 		Register_##ns##_##id##_init(this); \
- 		Register_##ns##_##id##_init_post(this); \
 -		this.fld = registry##_COUNT; \
 -		_R_SET(_##registry, registry##_COUNT, this); \
 -		++registry##_COUNT; \
 -		if (!registry##_first) registry##_first = this; \
 -		if (registry##_last)   registry##_last.REGISTRY_NEXT = this; \
 -		registry##_last = this; \
++		REGISTRY_PUSH(registry, fld, this); \
+ 		Register_##id##_init(this); \
+ 		Register_##id##_init_post(this); \
  	} \
- 	ACCUMULATE_FUNCTION(initfunc, Register_##ns##_##id) \
- 	REGISTER_INIT(ns, id)
- 
- #define REGISTRY_PUSH(array, fld, it) do { \
- 	it.fld = array##_COUNT; \
- 	_##array[array##_COUNT++] = it; \
- 	if (!array##_first) array##_first = it; \
- 	if (array##_last)   array##_last.REGISTRY_NEXT = it; \
- 	array##_last = it; \
+ 	ACCUMULATE_FUNCTION(Register##registry, Register_##id) \
+ 	REGISTER_INIT(id)
+ 
++#define REGISTRY_PUSH(registry, fld, it) do { \
++	it.fld = registry##_COUNT; \
++	_R_SET(_##registry, registry##_COUNT, it); \
++	++registry##_COUNT; \
++	if (!registry##_first) registry##_first = it; \
++	if (registry##_last)   registry##_last.REGISTRY_NEXT = it; \
++	registry##_last = it; \
 +} while (0)
 +
 +#define REGISTRY_RESERVE(registry, fld, id, suffix) do { \
 +	entity e = new(registry_reserved); \
- 	e.registered_id = #id #suffix; \
++	e.registered_id = #id "/" #suffix; \
 +	REGISTRY_PUSH(registry, fld, e); \
 +} while (0)
 +
+ #define REGISTER_INIT(id) [[accumulate]] void Register_##id##_init(entity this)
+ #define REGISTER_INIT_POST(id) [[accumulate]] void Register_##id##_init_post(entity this)
+ 
  /** internal next pointer */
  #define REGISTRY_NEXT enemy
  .entity REGISTRY_NEXT;
diff --cc qcsrc/lib/stats.qh
index 32927f4bd8,12dc425cd3..da796b910f
--- a/qcsrc/lib/stats.qh
+++ b/qcsrc/lib/stats.qh
@@@ -20,19 -16,13 +20,19 @@@ typedef vector vectori
  	#define getstat_int(id) getstati(id, 0, 24)
  	#define getstat_bool(id) boolean(getstati(id))
  	#define getstat_float(id) getstatf(id)
 +	#define getstat_vector(id) vec3(getstat_float(id + 0), getstat_float(id + 1), getstat_float(id + 2))
 +	#define getstat_vectori(id) vec3(getstat_int(id + 0), getstat_int(id + 1), getstat_int(id + 2))
  
  	#define _STAT(id) g_stat_##id
 -	#define REGISTER_STAT(id, type) \
 -		type _STAT(id); \
 -		REGISTER(Stats, STAT, id, m_id, new(stat)) \
 +	#define REGISTER_STAT_2(id, T) \
 +		T _STAT(id); \
- 		REGISTER(RegisterStats, STAT, Stats, id, m_id, new(stat)) \
++		REGISTER(Stats, STAT_##id, m_id, new(stat)) \
  		{ \
  			make_pure(this); \
 +			if (#T == "vector" || #T == "vectori") { \
- 				REGISTRY_RESERVE(Stats, m_id, id, _y); \
- 				REGISTRY_RESERVE(Stats, m_id, id, _z); \
++				REGISTRY_RESERVE(Stats, m_id, STAT_##id, y); \
++				REGISTRY_RESERVE(Stats, m_id, STAT_##id, z); \
 +			} \
  		} \
  		[[accumulate]] void stats_get() \
  		{ \
@@@ -61,46 -40,26 +61,46 @@@
  	const int AS_INT = 2;
  	const int AS_FLOAT = 8;
  
 +	.int __stat_null;
 +	/** Prevent engine stats being sent */
 +	STATIC_INIT(stats_clear)
 +	{
 +		int r = 32;
 +		for (int i = 0, n = 256 - r; i < n; ++i) {
 +			addstat(r + i, AS_INT, __stat_null);
 +		}
 +	}
 +
  	#define _STAT(id) stat_##id
 -	#define REGISTER_STAT(id, type) \
 -		.type _STAT(id); \
 -		REGISTER(Stats, STAT, id, m_id, new(stat)) \
 +	#define REGISTER_STAT_2(id, T) \
 +		.T _STAT(id); \
- 		REGISTER(RegisterStats, STAT, Stats, id, m_id, new(stat)) \
++		REGISTER(Stats, STAT_##id, m_id, new(stat)) \
  		{ \
  			make_pure(this); \
 +			if (#T == "vector" || #T == "vectori") { \
- 				REGISTRY_RESERVE(Stats, m_id, id, _y); \
- 				REGISTRY_RESERVE(Stats, m_id, id, _z); \
++				REGISTRY_RESERVE(Stats, m_id, STAT_##id, y); \
++				REGISTRY_RESERVE(Stats, m_id, STAT_##id, z); \
 +			} \
  		} \
  		[[accumulate]] void stats_add() \
  		{ \
 -			addstat_##type(STAT_##id.m_id, _STAT(id)); \
 +			addstat_##T(STAT_##id.m_id, _STAT(id)); \
  		}
 +	void GlobalStats_update(entity this) {}
 +    #define REGISTER_STAT_3(x, T, expr) \
 +    	REGISTER_STAT(x, T); \
 +    	[[accumulate]] void GlobalStats_update(entity this) { STAT(x, this) = (expr); } \
 +    	STATIC_INIT(worldstat_##x) { entity this = world; STAT(x, this) = (expr); }
  #else
 -	#define REGISTER_STAT(id, type)
 +	#define REGISTER_STAT_2(id, type)
 +    #define REGISTER_STAT_3(x, T, expr)
  #endif
  
 -const int STATS_ENGINE_RESERVE = 32 + (8 * 3); // Not sure how to handle vector stats yet, reserve them too
 +const int STATS_ENGINE_RESERVE = 32;
  
 -REGISTRY(Stats, 220 - STATS_ENGINE_RESERVE)
 +REGISTRY(Stats, 256 - STATS_ENGINE_RESERVE)
- REGISTER_REGISTRY(RegisterStats)
- REGISTRY_SORT(Stats, 0)
+ REGISTER_REGISTRY(Stats)
+ REGISTRY_SORT(Stats)
  REGISTRY_CHECK(Stats)
  STATIC_INIT(RegisterStats_renumber)
  {
diff --cc qcsrc/server/g_damage.qc
index 5d9436f684,dc306eee09..dcd75023e6
--- a/qcsrc/server/g_damage.qc
+++ b/qcsrc/server/g_damage.qc
@@@ -262,6 -259,8 +259,8 @@@ float Obituary_WeaponDeath
  	return false;
  }
  
 -.int buffs; // TODO: remove
++.int buffs = _STAT(BUFFS); // TODO: remove
+ 
  void Obituary(entity attacker, entity inflictor, entity targ, int deathtype)
  {
  	// Sanity check