]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Resolve conflicts 2: Merge branch 'master' into bones_was_here/q3compat
authorbones_was_here <bones_was_here@xa.org.au>
Sun, 18 Jul 2021 00:48:31 +0000 (10:48 +1000)
committerbones_was_here <bones_was_here@xa.org.au>
Sun, 18 Jul 2021 00:48:31 +0000 (10:48 +1000)
15 files changed:
1  2 
qcsrc/common/items/item.qh
qcsrc/common/mutators/mutator/buffs/all.inc
qcsrc/common/mutators/mutator/buffs/buffs.qh
qcsrc/common/mutators/mutator/buffs/sv_buffs.qc
qcsrc/common/mutators/mutator/buffs/sv_buffs.qh
qcsrc/common/physics/player.qc
qcsrc/common/stats.qh
qcsrc/lib/spawnfunc.qh
qcsrc/lib/warpzone/common.qc
qcsrc/server/client.qc
qcsrc/server/compat/quake3.qc
qcsrc/server/items/items.qc
qcsrc/server/items/items.qh
qcsrc/server/world.qc
xonotic-server.cfg

index 5873c5831847961616cf40ed4dbcc1093f0e28a7,9e322c811d86354b1800e3e9dd79ab975382aa12..350421465459bdd57312511b2cd935c565327a1d
@@@ -24,11 -24,15 +24,15 @@@ const int IT_RESOURCE                              =  BIT(5
  const int IT_KEY1                                             = BIT(6);
  const int IT_KEY2                                             = BIT(7);
  
+ const int IT_BUFF                                             = BIT(8); // unused bit for buff items
  // special colorblend meaning in engine
- const int IT_INVISIBILITY                             = BIT(9);
- const int IT_INVINCIBLE                               = BIT(10);
+ // legacy bitflags for powerups
+ const int IT_INVISIBILITY                             = BIT(9);
+ const int IT_INVINCIBLE                                       = BIT(10);
  const int IT_SUPERWEAPON                              = BIT(11); // suit
- const int IT_STRENGTH                                 = BIT(12);
+ const int IT_STRENGTH                                 = BIT(12);
+ const int IT_SPEED                                            = BIT(13);
  
  // item masks
  const int IT_PICKUPMASK                       = IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS | IT_JETPACK | IT_FUEL_REGEN; // strength and invincible are handled separately
@@@ -65,23 -69,27 +69,23 @@@ const int ITS_GLOW              = BIT(6
  .float invincible_finished; // ditto
  .float buffs_finished; // ditts
  
 -#define spawnfunc_body(item) \
 -      if (!Item_IsDefinitionAllowed(item)) \
 +#define SPAWNFUNC_BODY(item) \
 +      if (item && Item_IsDefinitionAllowed(item)) \
 +              StartItem(this, item); \
 +      else \
        { \
                startitem_failed = true; \
                delete(this); \
 -              return; \
 -      } \
 -      StartItem(this, item)
 +      }
  
  #define SPAWNFUNC_ITEM(name, item) \
        spawnfunc(name) \
        { \
 -              spawnfunc_body(item); \
 +              SPAWNFUNC_BODY(item) \
        }
  
  #define SPAWNFUNC_ITEM_COND(name, cond, item1, item2) \
 -      spawnfunc(name) \
 -      { \
 -              entity item = (cond) ? item1 : item2; \
 -              spawnfunc_body(item); \
 -      }
 +      SPAWNFUNC_ITEM(name, (cond ? item1 : item2))
  
  #else
  
index 3d540eebfdf3c715472f63f792a5e3205a131482,72f54dcd28cb658a9a99c9ad842c5300e16c2218..3a4031ef8e052e8e9b27674a6961040c9f197361
@@@ -2,165 -2,162 +2,171 @@@ string Buff_UndeprecateName(string buff
  {
      switch(buffname)
      {
 -        case "ammoregen": return "ammo";
 -        case "guard": return "resistance";
 -        case "revival": case "regen": return "medic";
 -        case "jumper": return "jump";
 +        case "ammoregen": return "ammo";              // Q3TA ammoregen
-         case "haste": return "speed";                 // Q3A haste
 +        case "doubler": return "inferno";             // Q3TA doubler
 +        case "scout": return "bash";                  // Q3TA scout
 +        case "guard": return "resistance";            // Q3TA guard
 +        case "revival": case "regen": return "medic"; // WOP revival, Q3A regen
-         case "invis": return "invisible";             // Q3A invis
 +        case "jumper": return "jump";                 // WOP jumper
 +        case "invulnerability": return "vampire";     // Q3TA invulnerability
 +        case "kamikaze": return "vengeance";          // Q3TA kamikaze
 +        case "teleporter": return "swapper";          // Q3A personal teleporter
          default: return buffname;
      }
  }
  
- REGISTER_BUFF(AMMO) {
-     this.m_name = _("Ammo");
-     this.netname = "ammo";
-     this.m_icon = "buff_ammo";
-     this.m_skin = 3;
-     this.m_color = '0.76 1 0.1';
- }
+ CLASS(AmmoBuff, Buff)
+     ATTRIB(AmmoBuff, m_name, string, _("Ammo"));
+     ATTRIB(AmmoBuff, netname, string, "ammo");
+     ATTRIB(AmmoBuff, m_icon, string, "buff_ammo");
+     ATTRIB(AmmoBuff, m_skin, int, 3);
+     ATTRIB(AmmoBuff, m_color, vector, '0.76 1 0.1');
+ ENDCLASS(AmmoBuff)
+ REGISTER_BUFF(AMMO, NEW(AmmoBuff));
  BUFF_SPAWNFUNCS(ammo, BUFF_AMMO)
 -BUFF_SPAWNFUNC_Q3TA_COMPAT(ammoregen, BUFF_AMMO)
 +BUFF_SPAWNFUNC_Q3COMPAT(item_ammoregen, BUFF_AMMO)
  
- REGISTER_BUFF(RESISTANCE) {
-     this.m_name = _("Resistance");
-     this.netname = "resistance";
-     this.m_icon = "buff_resistance";
-     this.m_skin = 0;
-     this.m_color = '0.36 1 0.07';
- }
+ CLASS(ResistanceBuff, Buff)
+     ATTRIB(ResistanceBuff, m_name, string, _("Resistance"));
+     ATTRIB(ResistanceBuff, netname, string, "resistance");
+     ATTRIB(ResistanceBuff, m_icon, string, "buff_resistance");
+     ATTRIB(ResistanceBuff, m_skin, int, 0);
+     ATTRIB(ResistanceBuff, m_color, vector, '0.36 1 0.07');
+ ENDCLASS(ResistanceBuff)
+ REGISTER_BUFF(RESISTANCE, NEW(ResistanceBuff));
  BUFF_SPAWNFUNCS(resistance, BUFF_RESISTANCE)
 -BUFF_SPAWNFUNC_Q3TA_COMPAT(guard, BUFF_RESISTANCE)
 +BUFF_SPAWNFUNC_Q3COMPAT(item_guard, BUFF_RESISTANCE)
  
- REGISTER_BUFF(SPEED) {
-     this.m_name = _("Speed");
-     this.netname = "speed";
-     this.m_icon = "buff_speed";
-     this.m_skin = 9;
-     this.m_color = '0.1 1 0.84';
- }
- BUFF_SPAWNFUNCS(speed, BUFF_SPEED)
- BUFF_SPAWNFUNC_Q3COMPAT(item_haste, BUFF_SPEED)
- REGISTER_BUFF(MEDIC) {
-     this.m_name = _("Medic");
-     this.netname = "medic";
-     this.m_icon = "buff_medic";
-     this.m_skin = 1;
-     this.m_color = '1 0.12 0';
- }
+ CLASS(MedicBuff, Buff)
+     ATTRIB(MedicBuff, m_name, string, _("Medic"));
+     ATTRIB(MedicBuff, netname, string, "medic");
+     ATTRIB(MedicBuff, m_icon, string, "buff_medic");
+     ATTRIB(MedicBuff, m_skin, int, 1);
+     ATTRIB(MedicBuff, m_color, vector, '1 0.12 0');
+ ENDCLASS(MedicBuff)
+ REGISTER_BUFF(MEDIC, NEW(MedicBuff));
  BUFF_SPAWNFUNCS(medic, BUFF_MEDIC)
 -BUFF_SPAWNFUNC_Q3TA_COMPAT(regen, BUFF_MEDIC)
 -BUFF_SPAWNFUNC_Q3TA_COMPAT(revival, BUFF_MEDIC)
 +BUFF_SPAWNFUNC_Q3COMPAT(item_regen, BUFF_MEDIC)
 +BUFF_SPAWNFUNC_Q3COMPAT(item_revival, BUFF_MEDIC)
  
- REGISTER_BUFF(BASH) {
-     this.m_name = _("Bash");
-     this.netname = "bash";
-     this.m_icon = "buff_bash";
-     this.m_skin = 5;
-     this.m_color = '1 0.39 0';
- }
+ CLASS(BashBuff, Buff)
+     ATTRIB(BashBuff, m_name, string, _("Bash"));
+     ATTRIB(BashBuff, netname, string, "bash");
+     ATTRIB(BashBuff, m_icon, string, "buff_bash");
+     ATTRIB(BashBuff, m_skin, int, 5);
+     ATTRIB(BashBuff, m_color, vector, '1 0.39 0');
+ ENDCLASS(BashBuff)
+ REGISTER_BUFF(BASH, NEW(BashBuff));
  BUFF_SPAWNFUNCS(bash, BUFF_BASH)
 -BUFF_SPAWNFUNC_Q3TA_COMPAT(doubler, BUFF_BASH)
 +BUFF_SPAWNFUNC_Q3COMPAT(item_scout, BUFF_BASH)
  
- REGISTER_BUFF(VAMPIRE) {
-     this.m_name = _("Vampire");
-     this.netname = "vampire";
-     this.m_icon = "buff_vampire";
-     this.m_skin = 2;
-     this.m_color = '1 0 0.24';
- }
+ CLASS(VampireBuff, Buff)
+     ATTRIB(VampireBuff, m_name, string, _("Vampire"));
+     ATTRIB(VampireBuff, netname, string, "vampire");
+     ATTRIB(VampireBuff, m_icon, string, "buff_vampire");
+     ATTRIB(VampireBuff, m_skin, int, 2);
+     ATTRIB(VampireBuff, m_color, vector, '1 0 0.24');
+ ENDCLASS(VampireBuff)
+ REGISTER_BUFF(VAMPIRE, NEW(VampireBuff));
  BUFF_SPAWNFUNCS(vampire, BUFF_VAMPIRE)
 +BUFF_SPAWNFUNC_Q3COMPAT(holdable_invulnerability, BUFF_VAMPIRE)
  
- REGISTER_BUFF(DISABILITY) {
-     this.m_name = _("Disability");
-     this.netname = "disability";
-     this.m_icon = "buff_disability";
-     this.m_skin = 7;
-     this.m_color = '0.94 0.3 1';
- }
+ CLASS(DisabilityBuff, Buff)
+     ATTRIB(DisabilityBuff, m_name, string, _("Disability"));
+     ATTRIB(DisabilityBuff, netname, string, "disability");
+     ATTRIB(DisabilityBuff, m_icon, string, "buff_disability");
+     ATTRIB(DisabilityBuff, m_skin, int, 7);
+     ATTRIB(DisabilityBuff, m_color, vector, '0.94 0.3 1');
+ ENDCLASS(DisabilityBuff)
+ REGISTER_BUFF(DISABILITY, NEW(DisabilityBuff));
  BUFF_SPAWNFUNCS(disability, BUFF_DISABILITY)
+ // status effect applied to targets by the disability buff
+ CLASS(Disabled, StatusEffects)
+     ATTRIB(Disabled, netname, string, "disabled");
+ #if 0
+     // NOTE: status effect name and icon disabled as they are not displayed
+     // re-enable if status effects are given a visual element
+     ATTRIB(Disabled, m_name, string, _("Disabled"));
+     ATTRIB(Disabled, m_icon, string, "buff_disability");
+ #endif
+     ATTRIB(Disabled, m_color, vector, '0.94 0.3 1');
+     ATTRIB(Disabled, m_hidden, bool, true);
+     ATTRIB(Disabled, m_lifetime, float, 10);
+     ATTRIB(Disabled, disabled_effect_time, float, 0); // TODO: handle this effect client side like EF_FLAME!
+ ENDCLASS(Disabled)
+ REGISTER_STATUSEFFECT(Disabled, NEW(Disabled));
  
- REGISTER_BUFF(VENGEANCE) {
-     this.m_name = _("Vengeance");
-     this.netname = "vengeance";
-     this.m_icon = "buff_vengeance";
-     this.m_skin = 15;
-     this.m_color = '1 0.23 0.61';
- }
+ CLASS(VengeanceBuff, Buff)
+     ATTRIB(VengeanceBuff, m_name, string, _("Vengeance"));
+     ATTRIB(VengeanceBuff, netname, string, "vengeance");
+     ATTRIB(VengeanceBuff, m_icon, string, "buff_vengeance");
+     ATTRIB(VengeanceBuff, m_skin, int, 15);
+     ATTRIB(VengeanceBuff, m_color, vector, '1 0.23 0.61');
+ ENDCLASS(VengeanceBuff)
+ REGISTER_BUFF(VENGEANCE, NEW(VengeanceBuff));
  BUFF_SPAWNFUNCS(vengeance, BUFF_VENGEANCE)
 +BUFF_SPAWNFUNC_Q3COMPAT(holdable_kamikaze, BUFF_VENGEANCE)
  
- REGISTER_BUFF(JUMP) {
-     this.m_name = _("Jump");
-     this.netname = "jump";
-     this.m_icon = "buff_jump";
-     this.m_skin = 10;
-     this.m_color = '0.24 0.78 1';
- }
+ CLASS(JumpBuff, Buff)
+     ATTRIB(JumpBuff, m_name, string, _("Jump"));
+     ATTRIB(JumpBuff, netname, string, "jump");
+     ATTRIB(JumpBuff, m_icon, string, "buff_jump");
+     ATTRIB(JumpBuff, m_skin, int, 10);
+     ATTRIB(JumpBuff, m_color, vector, '0.24 0.78 1');
+ ENDCLASS(JumpBuff)
+ REGISTER_BUFF(JUMP, NEW(JumpBuff));
  BUFF_SPAWNFUNCS(jump, BUFF_JUMP)
 -BUFF_SPAWNFUNC_Q3TA_COMPAT(jumper, BUFF_JUMP)
 +BUFF_SPAWNFUNC_Q3COMPAT(item_jumper, BUFF_JUMP)
  
- REGISTER_BUFF(INVISIBLE) {
-     this.m_name = _("Invisible");
-     this.netname = "invisible";
-     this.m_icon = "buff_invisible";
-     this.m_skin = 12;
-     this.m_color = '0.5 0.5 1';
- }
- BUFF_SPAWNFUNCS(invisible, BUFF_INVISIBLE)
- BUFF_SPAWNFUNC_Q3COMPAT(item_invis, BUFF_INVISIBLE)
- REGISTER_BUFF(INFERNO) {
-     this.m_name = _("Inferno");
-     this.netname = "inferno";
-     this.m_icon = "buff_inferno";
-     this.m_skin = 16;
-     this.m_color = '1 0.62 0';
- }
+ CLASS(InfernoBuff, Buff)
+     ATTRIB(InfernoBuff, m_name, string, _("Inferno"));
+     ATTRIB(InfernoBuff, netname, string, "inferno");
+     ATTRIB(InfernoBuff, m_icon, string, "buff_inferno");
+     ATTRIB(InfernoBuff, m_skin, int, 16);
+     ATTRIB(InfernoBuff, m_color, vector, '1 0.62 0');
+ ENDCLASS(InfernoBuff)
+ REGISTER_BUFF(INFERNO, NEW(InfernoBuff));
  BUFF_SPAWNFUNCS(inferno, BUFF_INFERNO)
 +BUFF_SPAWNFUNC_Q3COMPAT(item_doubler, BUFF_INFERNO)
  
- REGISTER_BUFF(SWAPPER) {
-     this.m_name = _("Swapper");
-     this.netname = "swapper";
-     this.m_icon = "buff_swapper";
-     this.m_skin = 17;
-     this.m_color = '0.63 0.36 1';
- }
+ CLASS(SwapperBuff, Buff)
+     ATTRIB(SwapperBuff, m_name, string, _("Swapper"));
+     ATTRIB(SwapperBuff, netname, string, "swapper");
+     ATTRIB(SwapperBuff, m_icon, string, "buff_swapper");
+     ATTRIB(SwapperBuff, m_skin, int, 17);
+     ATTRIB(SwapperBuff, m_color, vector, '0.63 0.36 1');
+ ENDCLASS(SwapperBuff)
+ REGISTER_BUFF(SWAPPER, NEW(SwapperBuff));
  BUFF_SPAWNFUNCS(swapper, BUFF_SWAPPER)
 +BUFF_SPAWNFUNC_Q3COMPAT(holdable_teleporter, BUFF_SWAPPER)
  
- REGISTER_BUFF(MAGNET) {
-     this.m_name = _("Magnet");
-     this.netname = "magnet";
-     this.m_icon = "buff_magnet";
-     this.m_skin = 18;
-     this.m_color = '1 0.95 0.18';
- }
+ CLASS(MagnetBuff, Buff)
+     ATTRIB(MagnetBuff, m_name, string, _("Magnet"));
+     ATTRIB(MagnetBuff, netname, string, "magnet");
+     ATTRIB(MagnetBuff, m_icon, string, "buff_magnet");
+     ATTRIB(MagnetBuff, m_skin, int, 18);
+     ATTRIB(MagnetBuff, m_color, vector, '1 0.95 0.18');
+ ENDCLASS(MagnetBuff)
+ REGISTER_BUFF(MAGNET, NEW(MagnetBuff));
  BUFF_SPAWNFUNCS(magnet, BUFF_MAGNET)
  
- REGISTER_BUFF(LUCK) {
-     this.m_name = _("Luck");
-     this.netname = "luck";
-     this.m_icon = "buff_luck";
-     this.m_skin = 19;
-     this.m_color = '1 0.23 0.44';
- }
+ CLASS(LuckBuff, Buff)
+     ATTRIB(LuckBuff, m_name, string, _("Luck"));
+     ATTRIB(LuckBuff, netname, string, "luck");
+     ATTRIB(LuckBuff, m_icon, string, "buff_luck");
+     ATTRIB(LuckBuff, m_skin, int, 19);
+     ATTRIB(LuckBuff, m_color, vector, '1 0.23 0.44');
+ ENDCLASS(LuckBuff)
+ REGISTER_BUFF(LUCK, NEW(LuckBuff));
  BUFF_SPAWNFUNCS(luck, BUFF_LUCK)
  
- REGISTER_BUFF(FLIGHT) {
-     this.m_name = _("Flight");
-     this.netname = "flight";
-     this.m_icon = "buff_flight";
-     this.m_skin = 11;
-     this.m_color = '0.23 0.44 1';
- }
+ CLASS(FlightBuff, Buff)
+     ATTRIB(FlightBuff, m_name, string, _("Flight"));
+     ATTRIB(FlightBuff, netname, string, "flight");
+     ATTRIB(FlightBuff, m_icon, string, "buff_flight");
+     ATTRIB(FlightBuff, m_skin, int, 11);
+     ATTRIB(FlightBuff, m_color, vector, '0.23 0.44 1');
+ ENDCLASS(FlightBuff)
+ REGISTER_BUFF(FLIGHT, NEW(FlightBuff));
  BUFF_SPAWNFUNCS(flight, BUFF_FLIGHT)
 -BUFF_SPAWNFUNC_Q3TA_COMPAT(flight, BUFF_FLIGHT)
 +BUFF_SPAWNFUNC_Q3COMPAT(item_flight, BUFF_FLIGHT)
index 14a8ea01d0f5aced3625820ae4891e9131557289,e3e4e5edd3113132574116fca1736efc21e737ef..9851cd8f7a55c9988b2470bc320b25f6a7c41463
@@@ -4,6 -4,7 +4,7 @@@
  #include <common/util.qh>
  
  #ifdef GAMEQC
+ #include <common/items/item.qh>
  #include <common/mutators/mutator/waypoints/all.qh>
  #endif
  
@@@ -12,13 -13,14 +13,14 @@@ REGISTER_WAYPOINT(Buff, _("Buff"), "", 
  REGISTER_RADARICON(Buff, 1);
  #endif
  
- #define REGISTER_BUFF(id) \
-     REGISTER(StatusEffect, BUFF_##id, m_id, NEW(Buff))
+ #define REGISTER_BUFF(id, inst) \
+     REGISTER(StatusEffect, BUFF_##id, m_id, inst)
  
  #include <common/mutators/mutator/status_effects/_mod.qh>
  CLASS(Buff, StatusEffects)
-       /** bit index */
-       ATTRIB(Buff, m_itemid, int, 0);
+ #ifdef GAMEQC
+       ATTRIB(Buff, m_itemid, int, IT_BUFF);
+ #endif
        ATTRIB(Buff, netname, string, "buff");
        ATTRIB(Buff, m_icon, string, "buff");
        ATTRIB(Buff, m_color, vector, '1 1 1');
@@@ -37,7 -39,6 +39,6 @@@ ENDCLASS(Buff
  
  STATIC_INIT(REGISTER_BUFFS) {
        FOREACH(StatusEffect, it.instanceOfBuff, {
-               it.m_itemid = BIT(it.m_id - 1);
                it.m_sprite = strzone(strcat("buff-", it.netname));
        });
  }
@@@ -48,8 -49,7 +49,8 @@@
        void buff_Init_Compat(entity ent, entity replacement);
        #define BUFF_SPAWNFUNC(e, b, t) spawnfunc(item_buff_##e) { \
                this.buffdef = b; \
 -              this.team = t; \
 +              if(teamplay) \
 +                      this.team_forced = t; \
                buff_Init(this); \
        }
        #define BUFF_SPAWNFUNCS(e, b)                       \
                        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(this, r); }
 +      #define BUFF_SPAWNFUNC_Q3COMPAT(o, r) spawnfunc(o) { buff_Init_Compat(this, r); }
  #else
        #define BUFF_SPAWNFUNC(e, b, t)
        #define BUFF_SPAWNFUNCS(e, b)
 -      #define BUFF_SPAWNFUNC_Q3TA_COMPAT(o, r)
 +      #define BUFF_SPAWNFUNC_Q3COMPAT(o, r)
  #endif
  
  string Buff_UndeprecateName(string buffname);
index 2c32b57e421688f56311ae2de16f08325c63c810,11176f48f3846271812ef8174358dc7940cbed1c..49f70a8fc9ac2742430112355448b53581ccc0f6
@@@ -1,7 -1,7 +1,7 @@@
  #include "sv_buffs.qh"
  
  #include <common/mapobjects/target/music.qh>
- #include <common/mutators/mutator/instagib/_mod.qh>
+ #include <common/mutators/mutator/powerups/_mod.qh>
  #include <common/gamemodes/_mod.qh>
  #include <server/items/items.qh>
  
@@@ -9,6 -9,10 +9,10 @@@ bool buffs_BuffModel_Customize(entity t
  {
        entity player = WaypointSprite_getviewentity(client);
        entity myowner = this.owner;
+       entity heldbuff = buff_FirstFromFlags(myowner);
+       if(!heldbuff)
+               return false;
  
        if(myowner.alpha <= 0.5 && DIFF_TEAM(player, myowner) && myowner.alpha != 0)
                return false;
        else
        {
                this.effects = EF_FULLBRIGHT | EF_LOWPRECISION;
-               this.alpha = 1;
+               this.alpha = myowner.alpha;
        }
        return true;
  }
  
+ void buffs_BuffModel_Think(entity this)
+ {
+       this.nextthink = time;
+       entity player = this.owner;
+       if(player.alpha < 0 || player.buff_model != this)
+       {
+               if(player) // remnant from ChatBubbleThink, same question... WHY?!
+                       player.buff_model = NULL;
+               delete(this);
+               return;
+       }
+       entity heldbuff = buff_FirstFromFlags(player);
+       if(!heldbuff)
+       {
+               this.effects = EF_NODRAW;
+               return;
+       }
+       this.color = heldbuff.m_color;
+       this.glowmod = heldbuff.m_color;
+       this.skin = heldbuff.m_skin;
+       this.effects = player.effects;
+       this.effects |= EF_LOWPRECISION;
+       this.effects = this.effects & EFMASK_CHEAP; // eat performance
+       this.alpha = player.alpha;
+ }
+ void buffs_BuffModel_Remove(entity player)
+ {
+       if(player.buff_model)
+               delete(player.buff_model);
+       player.buff_model = NULL;
+ }
  void buffs_BuffModel_Spawn(entity player)
  {
        player.buff_model = new(buff_model);
        setattachment(player.buff_model, player, "");
        setorigin(player.buff_model, '0 0 1' * (player.buff_model.maxs.z * 1));
        player.buff_model.owner = player;
+       player.buff_model.exteriormodeltoclient = player;
        player.buff_model.scale = 0.7;
        player.buff_model.pflags = PFLAGS_FULLDYNAMIC;
        player.buff_model.light_lev = 200;
+       setthink(player.buff_model, buffs_BuffModel_Think);
+       player.buff_model.nextthink = time;
        setcefc(player.buff_model, buffs_BuffModel_Customize);
  }
  
- void buffs_BuffModel_Remove(entity player)
+ void buffs_BuffModel_Update(entity this)
  {
-       if(player.buff_model)
-               delete(player.buff_model);
-       player.buff_model = NULL;
- }
- vector buff_GlowColor(entity buff)
- {
-       //if(buff.team_forced) { return Team_ColorRGB(buff.team_forced); }
-       return buff.m_color;
+       if (this.alpha < 0)
+               return;
+       // spawn a buff model entity if needed
+       if (!this.buff_model)
+               buffs_BuffModel_Spawn(this);
  }
  
  void buff_Effect(entity player, string eff)
@@@ -88,7 -129,7 +129,7 @@@ void buff_Waypoint_Spawn(entity e
        if(autocvar_g_buffs_waypoint_distance <= 0) return;
  
        entity buff = e.buffdef;
 -      entity wp = WaypointSprite_Spawn(WP_Buff, 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs.z, NULL, e.team, e, buff_waypoint, true, RADARICON_Buff);
 +      entity wp = WaypointSprite_Spawn(WP_Buff, 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs.z, NULL, e.team_forced, e, buff_waypoint, true, RADARICON_Buff);
        wp.wp_extra = buff.m_id;
        WaypointSprite_UpdateTeamRadar(e.buff_waypoint, RADARICON_Buff, e.glowmod);
        e.buff_waypoint.waypointsprite_visible_for_player = buff_Waypoint_visible_for_player;
@@@ -163,11 -204,11 +204,11 @@@ void buff_Touch(entity this, entity tou
        if(!IS_PLAYER(toucher))
                return; // incase mutator changed toucher
  
 -      if((this.team && DIFF_TEAM(toucher, this))
 +      if((this.team_forced && toucher.team != this.team_forced)
        || (STAT(FROZEN, toucher))
        || (toucher.vehicle)
        || (!this.buffdef) // TODO: error out or maybe reset type if this occurs?
-       || (time < PS(toucher).buff_shield)
+       || (time < toucher.buff_shield)
        )
        {
                // can't touch this
@@@ -269,7 -310,7 +310,7 @@@ void buff_Think(entity this
        {
                entity buff = this.buffdef;
                this.color = buff.m_color;
-               this.glowmod = buff_GlowColor(buff);
+               this.glowmod = buff.m_color;
                this.skin = buff.m_skin;
  
                setmodel(this, MDL_BUFF);
  
        if(this.buff_active)
        {
 -              if(this.team && !this.buff_waypoint)
 +              if(this.team_forced && !this.buff_waypoint)
                        buff_Waypoint_Spawn(this);
  
                if(this.lifetime && time >= this.lifetime)
@@@ -358,7 -399,7 +399,7 @@@ void buff_Reset(entity this
  bool buff_Customize(entity this, entity client)
  {
        entity player = WaypointSprite_getviewentity(client);
 -      if((!this.buff_active || !this.buffdef) || (this.team && DIFF_TEAM(player, this)))
 +      if((!this.buff_active || !this.buffdef) || (this.team_forced && player.team != this.team_forced))
        {
                this.alpha = 0.3;
                if(this.effects & EF_FULLBRIGHT) { this.effects &= ~(EF_FULLBRIGHT); }
@@@ -384,6 -425,8 +425,6 @@@ void buff_Init(entity this
  {
        if(!cvar("g_buffs")) { delete(this); return; }
  
 -      if(!teamplay && this.team) { this.team = 0; }
 -
        entity buff = this.buffdef;
  
        if(!buff || !buff_Available(buff))
        setcefc(this, buff_Customize);
        //this.gravity = 100;
        this.color = buff.m_color;
-       this.glowmod = buff_GlowColor(this);
+       this.glowmod = buff.m_color;
        buff_SetCooldown(this, autocvar_g_buffs_cooldown_activate + max(0, game_starttime - time));
        this.buff_active = !this.buff_activetime;
        this.pflags = PFLAGS_FULLDYNAMIC;
  
  void buff_Init_Compat(entity ent, entity replacement)
  {
 -      if (ent.spawnflags & 2)
 -              ent.team = NUM_TEAM_1;
 -      else if (ent.spawnflags & 4)
 -              ent.team = NUM_TEAM_2;
 +      if (teamplay)
 +      {
 +              if (ent.spawnflags & 2)
 +                      ent.team_forced = NUM_TEAM_1;
 +              else if (ent.spawnflags & 4)
 +                      ent.team_forced = NUM_TEAM_2;
 +      }
  
        ent.buffdef = replacement;
  
@@@ -468,6 -508,149 +509,149 @@@ float buff_Inferno_CalculateTime(float 
        return offset_y + (intersect_y - offset_y) * logn(((damg - offset_x) * ((base - 1) / intersect_x)) + 1, base);
  }
  
+ METHOD(Buff, m_apply, void(StatusEffects this, entity actor, float eff_time, float eff_flags))
+ {
+     if(IS_PLAYER(actor))
+       actor.effects |= EF_NOSHADOW; // does not play well with buff icon
+     SUPER(Buff).m_apply(this, actor, eff_time, eff_flags);
+ }
+ METHOD(Buff, m_remove, void(StatusEffects this, entity actor, int removal_type))
+ {
+       bool wasactive = (actor.statuseffects && (actor.statuseffects.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE));
+       if(wasactive)
+       {
+               int buffid = this.m_id;
+               if(removal_type == STATUSEFFECT_REMOVE_TIMEOUT)
+               {
+                       Send_Notification(NOTIF_ONE, actor, MSG_MULTI, ITEM_BUFF_DROP, buffid); // TODO: special timeout message?
+                       sound(actor, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
+               }
+               else if(removal_type == STATUSEFFECT_REMOVE_NORMAL && !IS_INDEPENDENT_PLAYER(actor))
+                       Send_Notification(NOTIF_ALL_EXCEPT, actor, MSG_INFO, INFO_ITEM_BUFF_LOST, actor.netname, buffid);
+               actor.buff_shield = time + max(0, autocvar_g_buffs_pickup_delay); // always put in a delay, even if small
+       }
+       if(IS_PLAYER(actor))
+               actor.effects &= ~EF_NOSHADOW;
+       SUPER(Buff).m_remove(this, actor, removal_type);
+ }
+ METHOD(Disabled, m_tick, void(StatusEffects this, entity actor))
+ {
+       if(time >= actor.disabled_effect_time)
+       {
+               Send_Effect(EFFECT_SMOKING, actor.origin + ((actor.mins + actor.maxs) * 0.5), '0 0 0', 1);
+               actor.disabled_effect_time = time + 0.5;
+       }
+       SUPER(Disabled).m_tick(this, actor);
+ }
+ METHOD(Disabled, m_remove, void(StatusEffects this, entity actor, int removal_type))
+ {
+       actor.disabled_effect_time = 0;
+       SUPER(Disabled).m_remove(this, actor, removal_type);
+ }
+ METHOD(AmmoBuff, m_apply, void(StatusEffects this, entity actor, float eff_time, float eff_flags))
+ {
+     bool wasactive = (actor.statuseffects && (actor.statuseffects.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE));
+     if(!wasactive)
+     {
+         actor.buff_ammo_prev_infitems = (actor.items & IT_UNLIMITED_AMMO);
+         actor.items |= IT_UNLIMITED_AMMO;
+         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+               {
+                       .entity weaponentity = weaponentities[slot];
+                       if(!actor.(weaponentity))
+                               continue;
+                       if(actor.(weaponentity).clip_load)
+                               actor.(weaponentity).buff_ammo_prev_clipload = actor.(weaponentity).clip_load;
+                       if(actor.(weaponentity).clip_size)
+                               actor.(weaponentity).clip_load = actor.(weaponentity).(weapon_load[actor.(weaponentity).m_switchweapon.m_id]) = actor.(weaponentity).clip_size;
+               }
+     }
+     SUPER(AmmoBuff).m_apply(this, actor, eff_time, eff_flags);
+ }
+ METHOD(AmmoBuff, m_remove, void(StatusEffects this, entity actor, int removal_type))
+ {
+       bool wasactive = (actor.statuseffects && (actor.statuseffects.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE));
+       if(wasactive)
+       {
+               actor.items = BITSET(actor.items, IT_UNLIMITED_AMMO, actor.buff_ammo_prev_infitems);
+               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+               {
+                       .entity weaponentity = weaponentities[slot];
+                       if(!actor.(weaponentity))
+                               continue;
+                       if(actor.(weaponentity).buff_ammo_prev_clipload)
+                       {
+                               actor.(weaponentity).clip_load = actor.(weaponentity).buff_ammo_prev_clipload;
+                               actor.(weaponentity).buff_ammo_prev_clipload = 0;
+                       }
+               }
+       }
+       actor.buff_ammo_prev_infitems = 0;
+       SUPER(AmmoBuff).m_remove(this, actor, removal_type);
+ }
+ METHOD(AmmoBuff, m_tick, void(StatusEffects this, entity actor))
+ {
+       if(IS_PLAYER(actor))
+       {
+               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+               {
+                       .entity weaponentity = weaponentities[slot];
+                       if(actor.(weaponentity).clip_size)
+                               actor.(weaponentity).clip_load = actor.(weaponentity).(weapon_load[actor.(weaponentity).m_switchweapon.m_id]) = actor.(weaponentity).clip_size;
+               }
+       }
+       SUPER(AmmoBuff).m_tick(this, actor);
+ }
+ METHOD(FlightBuff, m_apply, void(StatusEffects this, entity actor, float eff_time, float eff_flags))
+ {
+     bool wasactive = (actor.statuseffects && (actor.statuseffects.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE));
+     if(!wasactive)
+     {
+         actor.buff_flight_oldgravity = actor.gravity;
+               if(!actor.gravity)
+                       actor.gravity = 1;
+     }
+     SUPER(FlightBuff).m_apply(this, actor, eff_time, eff_flags);
+ }
+ METHOD(FlightBuff, m_remove, void(StatusEffects this, entity actor, int removal_type))
+ {
+       bool wasactive = (actor.statuseffects && (actor.statuseffects.statuseffect_flags[this.m_id] & STATUSEFFECT_FLAG_ACTIVE));
+       if(wasactive)
+       {
+               actor.gravity = ((actor.trigger_gravity_check) ? actor.trigger_gravity_check.enemy.gravity : actor.buff_flight_oldgravity);
+       }
+       actor.buff_flight_oldgravity = 0;
+       SUPER(FlightBuff).m_remove(this, actor, removal_type);
+ }
+ METHOD(MagnetBuff, m_tick, void(StatusEffects this, entity actor))
+ {
+       if(IS_PLAYER(actor))
+       {
+               vector pickup_size;
+               IL_EACH(g_items, it.itemdef,
+               {
+                       if(it.buffdef)
+                               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(actor.absmin - pickup_size, actor.absmax + pickup_size, it.absmin, it.absmax))
+                       {
+                               if(gettouch(it))
+                                       gettouch(it)(it, actor);
+                       }
+               });
+       }
+       SUPER(MagnetBuff).m_tick(this, actor);
+ }
  // mutator hooks
  MUTATOR_HOOKFUNCTION(buffs, Damage_Calculate)
  {
                frag_damage = bound(0, frag_damage - reduced, frag_damage);
        }
  
-       if(StatusEffects_active(BUFF_SPEED, frag_target))
-       if(frag_target != frag_attacker)
-               frag_damage *= autocvar_g_buffs_speed_damage_take;
        if(StatusEffects_active(BUFF_MEDIC, frag_target))
        if((GetResource(frag_target, RES_HEALTH) - frag_damage) <= 0)
        if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
  
        if(StatusEffects_active(BUFF_DISABILITY, frag_attacker))
        if(frag_target != frag_attacker)
-               frag_target.buff_disability_time = time + autocvar_g_buffs_disability_slowtime;
+               StatusEffects_apply(STATUSEFFECT_Disabled, frag_target, time + autocvar_g_buffs_disability_slowtime, 0);
  
        if(StatusEffects_active(BUFF_INFERNO, frag_target))
        {
  
  MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_SplitHealthArmor)
  {
+       // NOTE: vampire PlayerDamage_SplitHealthArmor code is similar
        entity frag_attacker = M_ARGV(1, entity);
        entity frag_target = M_ARGV(2, entity);
        if(!StatusEffects_active(BUFF_VAMPIRE, frag_attacker))
                return;
        float health_take = bound(0, M_ARGV(4, float), GetResource(frag_target, RES_HEALTH));
  
-       if(time >= frag_target.spawnshieldtime &&
-               frag_target != frag_attacker &&
-               IS_PLAYER(frag_attacker) &&
-               !IS_DEAD(frag_target) && !STAT(FROZEN, frag_target))
+       if (!StatusEffects_active(STATUSEFFECT_SpawnShield, frag_target) && frag_target != frag_attacker
+               && IS_PLAYER(frag_attacker) && !IS_DEAD(frag_target) && !STAT(FROZEN, frag_target))
        {
-               GiveResource(frag_attacker, RES_HEALTH,
-                       autocvar_g_buffs_vampire_damage_steal * health_take);
+               GiveResource(frag_attacker, RES_HEALTH, autocvar_g_buffs_vampire_damage_steal * health_take);
        }
  }
  
- MUTATOR_HOOKFUNCTION(buffs, PlayerSpawn)
- {
-       entity player = M_ARGV(0, entity);
-       buffs_BuffModel_Remove(player);
-       player.oldbuffs = NULL;
-       // reset timers here to prevent them continuing after re-spawn
-       player.buff_disability_time = 0;
-       player.buff_disability_effect_time = 0;
- }
  MUTATOR_HOOKFUNCTION(buffs, PlayerPhysics_UpdateStats)
  {
        entity player = M_ARGV(0, entity);
        // these automatically reset, no need to worry
  
-       if(StatusEffects_active(BUFF_SPEED, player))
-               STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_buffs_speed_speed;
-       if(time < player.buff_disability_time)
+       if(StatusEffects_active(STATUSEFFECT_Disabled, player))
                STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_buffs_disability_speed;
  }
  
@@@ -616,28 -779,13 +780,13 @@@ MUTATOR_HOOKFUNCTION(buffs, MonsterMove
  {
        entity mon = M_ARGV(0, entity);
  
-       if(time < mon.buff_disability_time)
+       if(StatusEffects_active(STATUSEFFECT_Disabled, mon))
        {
                M_ARGV(1, float) *= autocvar_g_buffs_disability_speed; // run speed
                M_ARGV(2, float) *= autocvar_g_buffs_disability_speed; // walk speed
        }
  }
  
- MUTATOR_HOOKFUNCTION(buffs, PlayerDies)
- {
-       entity frag_target = M_ARGV(2, entity);
-       entity heldbuff = buff_FirstFromFlags(frag_target);
-       if(heldbuff)
-       {
-               int buffid = heldbuff.m_id;
-               if(!IS_INDEPENDENT_PLAYER(frag_target))
-                       Send_Notification(NOTIF_ALL_EXCEPT, frag_target, MSG_INFO, INFO_ITEM_BUFF_LOST, frag_target.netname, buffid);
-               buffs_BuffModel_Remove(frag_target);
-       }
- }
  MUTATOR_HOOKFUNCTION(buffs, PlayerUseKey, CBC_ORDER_FIRST)
  {
        if(MUTATOR_RETURNVALUE || game_stopped || !autocvar_g_buffs_drop) return;
                        Send_Notification(NOTIF_ALL_EXCEPT, player, MSG_INFO, INFO_ITEM_BUFF_LOST, player.netname, buffid);
  
                buff_RemoveAll(player, STATUSEFFECT_REMOVE_NORMAL);
-               PS(player).buff_shield = time + max(0, autocvar_g_buffs_pickup_delay);
+               player.buff_shield = time + max(0, autocvar_g_buffs_pickup_delay);
                sound(player, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
                return true;
        }
@@@ -742,29 -890,11 +891,11 @@@ bool buffs_RemovePlayer(entity player
  {
        buffs_BuffModel_Remove(player);
  
-       // also reset timers here to prevent them continuing after spectating
-       player.buff_disability_time = 0;
-       player.buff_disability_effect_time = 0;
        return false;
  }
  MUTATOR_HOOKFUNCTION(buffs, MakePlayerObserver) { entity player = M_ARGV(0, entity); return buffs_RemovePlayer(player); }
  MUTATOR_HOOKFUNCTION(buffs, ClientDisconnect) { entity player = M_ARGV(0, entity); return buffs_RemovePlayer(player); }
  
- MUTATOR_HOOKFUNCTION(buffs, CustomizeWaypoint)
- {
-       entity wp = M_ARGV(0, entity);
-       entity player = M_ARGV(1, entity);
-       entity e = WaypointSprite_getviewentity(player);
-       // if you have the invisibility powerup, sprites ALWAYS are restricted to your team
-       // but only apply this to real players, not to spectators
-       if((wp.owner.flags & FL_CLIENT) && (e == player) && StatusEffects_active(BUFF_INVISIBLE, wp.owner))
-       if(DIFF_TEAM(wp.owner, e))
-               return true;
- }
  MUTATOR_HOOKFUNCTION(buffs, FilterItem)
  {
        if(autocvar_g_buffs < 0)
  
        entity item = M_ARGV(0, entity);
  
-       if(autocvar_g_buffs_replace_powerups)
+       if(autocvar_g_buffs_replace_powerups && item.itemdef.instanceOfPowerup)
        {
-               switch(item.classname)
-               {
-                       case "item_strength":
-                       case "item_shield":
-                       {
-                               entity e = spawn();
-                               buff_SpawnReplacement(e, item);
-                               return true;
-                       }
-               }
+               entity e = spawn();
+               buff_SpawnReplacement(e, item);
+               return true;
        }
  
        return false;
@@@ -793,10 -916,7 +917,7 @@@ MUTATOR_HOOKFUNCTION(buffs, WeaponRateF
  {
        entity player = M_ARGV(1, entity);
  
-       if(StatusEffects_active(BUFF_SPEED, player))
-               M_ARGV(0, float) *= autocvar_g_buffs_speed_rate;
-       if(time < player.buff_disability_time)
+       if(StatusEffects_active(STATUSEFFECT_Disabled, player))
                M_ARGV(0, float) *= autocvar_g_buffs_disability_rate;
  }
  
@@@ -804,14 -924,15 +925,15 @@@ MUTATOR_HOOKFUNCTION(buffs, WeaponSpeed
  {
        entity player = M_ARGV(1, entity);
  
-       if(StatusEffects_active(BUFF_SPEED, player))
-               M_ARGV(0, float) *= autocvar_g_buffs_speed_weaponspeed;
-       if(time < player.buff_disability_time)
+       if(StatusEffects_active(STATUSEFFECT_Disabled, player))
                M_ARGV(0, float) *= autocvar_g_buffs_disability_weaponspeed;
  }
  
- .bool buff_flight_crouchheld;
+ MUTATOR_HOOKFUNCTION(buffs, Freeze)
+ {
+       entity targ = M_ARGV(0, entity);
+       buff_RemoveAll(targ, STATUSEFFECT_REMOVE_NORMAL);
+ }
  
  MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink)
  {
  
        if(game_stopped || IS_DEAD(player) || !IS_PLAYER(player)) return;
  
+       // NOTE: this is kept here to ensure crouches are picked up each player movement frame
        if(StatusEffects_active(BUFF_FLIGHT, player))
        {
                if(!PHYS_INPUT_BUTTON_CROUCH(player))
                }
        }
  
-       if(time < player.buff_disability_time)
-       if(time >= player.buff_disability_effect_time)
-       {
-               Send_Effect(EFFECT_SMOKING, player.origin + ((player.mins + player.maxs) * 0.5), '0 0 0', 1);
-               player.buff_disability_effect_time = time + 0.5;
-       }
-       // handle buff lost status
-       // 1: notify everyone else
-       // 2: notify carrier as well
-       int buff_lost = 0;
-       entity heldbuff = buff_FirstFromFlags(player);
-       float bufftime = StatusEffects_gettime(heldbuff, player);
-       if(heldbuff && bufftime && time >= bufftime)
-               buff_lost = 2;
-       if(STAT(FROZEN, player)) { buff_lost = 1; }
-       if(buff_lost && heldbuff)
-       {
-               int buffid = heldbuff.m_id;
-               if(buff_lost == 2)
-               {
-                       Send_Notification(NOTIF_ONE, player, MSG_MULTI, ITEM_BUFF_DROP, buffid); // TODO: special timeout message?
-                       sound(player, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM);
-               }
-               else if(!IS_INDEPENDENT_PLAYER(player))
-                       Send_Notification(NOTIF_ALL_EXCEPT, player, MSG_INFO, INFO_ITEM_BUFF_LOST, player.netname, buffid);
-               buff_RemoveAll(player, STATUSEFFECT_REMOVE_TIMEOUT); // TODO: remove only the currently active buff?
-               heldbuff = NULL;
-               PS(player).buff_shield = time + max(0, autocvar_g_buffs_pickup_delay); // always put in a delay, even if small
-       }
-       if(StatusEffects_active(BUFF_MAGNET, player))
-       {
-               vector pickup_size;
-               IL_EACH(g_items, it.itemdef,
-               {
-                       if(it.buffdef)
-                               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(player.absmin - pickup_size, player.absmax + pickup_size, it.absmin, it.absmax))
-                       {
-                               if(gettouch(it))
-                                       gettouch(it)(it, player);
-                       }
-               });
-       }
-       if(StatusEffects_active(BUFF_AMMO, player))
-       {
-               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
-               {
-                       .entity weaponentity = weaponentities[slot];
-                       if(player.(weaponentity).clip_size)
-                               player.(weaponentity).clip_load = player.(weaponentity).(weapon_load[player.(weaponentity).m_switchweapon.m_id]) = player.(weaponentity).clip_size;
-               }
-       }
-       if(!player.vehicle && StatusEffects_active(BUFF_INVISIBLE, player) && player.oldbuffs == BUFF_INVISIBLE)
-               player.alpha = ((autocvar_g_buffs_invisible_alpha) ? autocvar_g_buffs_invisible_alpha : -1); // powerups reset alpha, so we must enforce this (TODO)
- #define BUFF_ONADD(b) if ( (heldbuff == (b)) && (player.oldbuffs != (b)))
- #define BUFF_ONREM(b) if ( (heldbuff != (b)) && (player.oldbuffs == (b)))
-       if(heldbuff != player.oldbuffs)
-       {
-               bufftime = heldbuff ? heldbuff.m_time(heldbuff) : 0;
-               if(StatusEffects_gettime(heldbuff, player) <= time) // if the player still has a buff countdown, don't reset it!
-               {
-                       player.statuseffects.statuseffect_time[heldbuff.m_id] = (bufftime) ? time + bufftime : 0;
-                       StatusEffects_update(player);
-               }
-               BUFF_ONADD(BUFF_AMMO)
-               {
-                       player.buff_ammo_prev_infitems = (player.items & IT_UNLIMITED_AMMO);
-                       player.items |= IT_UNLIMITED_AMMO;
-                       if(StatusEffects_active(BUFF_AMMO, player))
-                       {
-                               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
-                               {
-                                       .entity weaponentity = weaponentities[slot];
-                                       if(player.(weaponentity).clip_load)
-                                               player.(weaponentity).buff_ammo_prev_clipload = player.(weaponentity).clip_load;
-                                       if(player.(weaponentity).clip_size)
-                                               player.(weaponentity).clip_load = player.(weaponentity).(weapon_load[player.(weaponentity).m_switchweapon.m_id]) = player.(weaponentity).clip_size;
-                               }
-                       }
-               }
-               BUFF_ONREM(BUFF_AMMO)
-               {
-                       if(player.buff_ammo_prev_infitems)
-                               player.items |= IT_UNLIMITED_AMMO;
-                       else
-                               player.items &= ~IT_UNLIMITED_AMMO;
-                       if(StatusEffects_active(BUFF_AMMO, player))
-                       {
-                               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
-                               {
-                                       .entity weaponentity = weaponentities[slot];
-                                       if(player.(weaponentity).buff_ammo_prev_clipload)
-                                               player.(weaponentity).clip_load = player.(weaponentity).buff_ammo_prev_clipload;
-                               }
-                       }
-               }
-               BUFF_ONADD(BUFF_INVISIBLE)
-               {
-                       if(StatusEffects_active(STATUSEFFECT_Strength, player) && MUTATOR_IS_ENABLED(mutator_instagib))
-                               player.buff_invisible_prev_alpha = default_player_alpha; // we don't want to save the powerup's alpha, as player may lose the powerup while holding the buff
-                       else
-                               player.buff_invisible_prev_alpha = player.alpha;
-                       if(!player.vehicle)
-                               player.alpha = autocvar_g_buffs_invisible_alpha;
-               }
-               BUFF_ONREM(BUFF_INVISIBLE)
-               {
-                       if(!player.vehicle)
-                       {
-                               if(StatusEffects_active(STATUSEFFECT_Strength, player) && MUTATOR_IS_ENABLED(mutator_instagib))
-                                       player.alpha = autocvar_g_instagib_invis_alpha;
-                               else
-                                       player.alpha = player.buff_invisible_prev_alpha;
-                       }
-               }
-               BUFF_ONADD(BUFF_FLIGHT)
-               {
-                       player.buff_flight_oldgravity = player.gravity;
-                       if(!player.gravity)
-                               player.gravity = 1;
-               }
-               BUFF_ONREM(BUFF_FLIGHT)
-                       player.gravity = ((player.trigger_gravity_check) ? player.trigger_gravity_check.enemy.gravity : player.buff_flight_oldgravity);
-               player.oldbuffs = heldbuff;
-               if(heldbuff)
-               {
-                       if(!player.buff_model)
-                               buffs_BuffModel_Spawn(player);
-                       player.buff_model.color = heldbuff.m_color;
-                       player.buff_model.glowmod = buff_GlowColor(heldbuff);
-                       player.buff_model.skin = heldbuff.m_skin;
-                       player.effects |= EF_NOSHADOW;
-               }
-               else
-               {
-                       buffs_BuffModel_Remove(player);
-                       player.effects &= ~(EF_NOSHADOW);
-               }
-       }
-       if(player.buff_model)
-       {
-               player.buff_model.effects = player.effects;
-               player.buff_model.effects |= EF_LOWPRECISION;
-               player.buff_model.effects = player.buff_model.effects & EFMASK_CHEAP; // eat performance
-               player.buff_model.alpha = player.alpha;
-       }
- #undef BUFF_ONADD
- #undef BUFF_ONREM
+       if(IS_PLAYER(player))
+               buffs_BuffModel_Update(player);
  }
  
  MUTATOR_HOOKFUNCTION(buffs, PlayerRegen)
                M_ARGV(4, float) = M_ARGV(1, float) = autocvar_g_buffs_medic_max; // limit_mod = max_mod
                M_ARGV(2, float) = autocvar_g_buffs_medic_regen; // regen_mod
        }
-       if(StatusEffects_active(BUFF_SPEED, player))
-               M_ARGV(2, float) = autocvar_g_buffs_speed_regen; // regen_mod
  }
  
  REPLICATE(cvar_cl_buffs_autoreplace, bool, "cl_buffs_autoreplace");
index 4eb536b609d00ea4d316ed84ec0fdb6fd8649f4a,ea2beb584df8f7902ad236d0822029da6e94c24b..7635df6b64eab1510011481e35042e1cbcb8f460
@@@ -43,13 -43,7 +43,7 @@@ float autocvar_g_buffs_disability_slowt
  float autocvar_g_buffs_disability_speed;
  float autocvar_g_buffs_disability_rate;
  float autocvar_g_buffs_disability_weaponspeed;
- float autocvar_g_buffs_speed_speed;
- float autocvar_g_buffs_speed_rate;
- float autocvar_g_buffs_speed_weaponspeed;
- 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_jump_height;
  float autocvar_g_buffs_inferno_burntime_factor;
  float autocvar_g_buffs_inferno_burntime_min_time;
@@@ -65,20 -59,16 +59,16 @@@ float autocvar_g_buffs_luck_damagemulti
  // ammo
  .float buff_ammo_prev_infitems;
  .int buff_ammo_prev_clipload;
- // invisible
- .float buff_invisible_prev_alpha;
- // disability
- .float buff_disability_time;
- .float buff_disability_effect_time;
  // flight
  .float buff_flight_oldgravity;
+ .bool buff_flight_crouchheld;
  // common buff variables
  .float buff_effect_delay;
  
  // buff definitions
 -.float buff_active;
 +.bool buff_active;
  .float buff_activetime;
 -.float buff_activetime_updated;
 +.bool buff_activetime_updated;
  .entity buff_waypoint;
  .entity oldbuffs; // for updating effects
  .float buff_shield; // delay for players to keep them from spamming buff pickups
index d1ceef8dced15a8fc803a5c56d877961450b6d14,d562ab64bde15bffc3923e8e385553bd59fbdc64..2b3c51374dfad689a9da0e46c520bb5abe37db34
@@@ -54,13 -54,13 +54,13 @@@ void Physics_UpdateStats(entity this
              : 0;
            STAT(MOVEVARS_AIRSPEEDLIMIT_NONQW, this) = Physics_ClientOption(this, "airspeedlimit_nonqw", autocvar_sv_airspeedlimit_nonqw) * maxspd_mod;
          }
 -      bool q3dfcompat = autocvar_sv_q3defragcompat && autocvar_sv_q3defragcompat_changehitbox; // NOTE: these hitboxes are off by 1 due to engine differences
 -      STAT(PL_MIN, this) = (q3dfcompat) ? '-15 -15 -20' : autocvar_sv_player_mins;
 -      STAT(PL_MAX, this) = (q3dfcompat) ? '15 15 36' : autocvar_sv_player_maxs;
 -      STAT(PL_VIEW_OFS, this) = (q3dfcompat) ? '0 0 30' : autocvar_sv_player_viewoffset;
 -      STAT(PL_CROUCH_MIN, this) = (q3dfcompat) ? '-15 -15 -20' : autocvar_sv_player_crouch_mins;
 -      STAT(PL_CROUCH_MAX, this) = (q3dfcompat) ? '15 15 20' : autocvar_sv_player_crouch_maxs;
 -      STAT(PL_CROUCH_VIEW_OFS, this) = (q3dfcompat) ? '0 0 16' : autocvar_sv_player_crouch_viewoffset;
 +      bool q3hb = q3compat && autocvar_sv_q3compat_changehitbox;
 +      STAT(PL_MIN, this) = (q3hb) ? '-15 -15 -20' : autocvar_sv_player_mins;
 +      STAT(PL_MAX, this) = (q3hb) ? '15 15 36' : autocvar_sv_player_maxs;
 +      STAT(PL_VIEW_OFS, this) = (q3hb) ? '0 0 30' : autocvar_sv_player_viewoffset;
 +      STAT(PL_CROUCH_MIN, this) = (q3hb) ? '-15 -15 -20' : autocvar_sv_player_crouch_mins;
 +      STAT(PL_CROUCH_MAX, this) = (q3hb) ? '15 15 20' : autocvar_sv_player_crouch_maxs;
 +      STAT(PL_CROUCH_VIEW_OFS, this) = (q3hb) ? '0 0 16' : autocvar_sv_player_crouch_viewoffset;
  
        // old stats
        // fix some new settings
@@@ -450,6 -450,7 +450,7 @@@ void CheckWaterJump(entity this
                {       // open at eye level
                        this.velocity_z = 225;
                        this.flags |= FL_WATERJUMP;
+                       this.teleport_time = time + 2; // safety net
                        SET_JUMP_HELD(this);
                }
        }
@@@ -517,8 -518,6 +518,6 @@@ void SpecialCommand(entity this
                if (!CheatImpulse(this, CHIMPULSE_GIVE_ALL.impulse))
                        LOG_INFO("A hollow voice says \"Plugh\".");
        }
-       else
-               STAT(MOVEVARS_SPECIALCOMMAND, this) = true;
  }
  #endif
  
@@@ -526,22 -525,18 +525,18 @@@ bool PM_check_specialcommand(entity thi
  {
  #ifdef SVQC
        string c;
-       if (!buttons)
-               c = "x";
-       else if (buttons == 1)
-               c = "1";
-       else if (buttons == 2)
-               c = " ";
-       else if (buttons == 128)
-               c = "s";
-       else if (buttons == 256)
-               c = "w";
-       else if (buttons == 512)
-               c = "a";
-       else if (buttons == 1024)
-               c = "d";
-       else
-               c = "?";
+       switch (buttons)
+       {
+               // buttons mapped in PHYS_INPUT_BUTTON_MASK
+               case 0: c = "x"; break;
+               case BIT(0): c = "1"; break;
+               case BIT(2): c = " "; break;
+               case BIT(7): c = "s"; break;
+               case BIT(8): c = "w"; break;
+               case BIT(9): c = "a"; break;
+               case BIT(10): c = "d"; break;
+               default: c = "?";
+       }
  
        if (c == substring(specialcommand, CS(this).specialcommand_pos, 1))
        {
diff --combined qcsrc/common/stats.qh
index 0b1b30eea2e54380376a51c80af0659744853f6c,12879a7531b1acd3f6bff24b18654d54bd1569cb..463d98c850157f9c595dd4e57759ce75fb57c60c
@@@ -4,7 -4,6 +4,7 @@@
  
  #ifdef SVQC
  #include <server/client.qh>
 +#include <server/compat/quake3.qh>
  #include <server/main.qh>
  #include <common/gamemodes/sv_rules.qh>
  #include <common/mapobjects/teleporters.qh>
@@@ -67,14 -66,17 +67,17 @@@ REGISTER_STAT(PL_CROUCH_MIN, vector
  REGISTER_STAT(PL_MAX, vector)
  REGISTER_STAT(PL_CROUCH_MAX, vector)
  
- REGISTER_STAT(KH_KEYS, int)
+ // networked bitflag for game objective display (modicons)
+ REGISTER_STAT(OBJECTIVE_STATUS, int)
+ #ifdef SVQC
+ SPECTATE_COPYFIELD(_STAT(OBJECTIVE_STATUS))
+ #endif
  
  #ifdef SVQC
  float W_WeaponRateFactor(entity this);
  float game_stopped;
  float game_starttime; //point in time when the countdown to game start is over
  float round_starttime; //point in time when the countdown to round start is over
- bool autocvar_g_allow_oldvortexbeam;
  int autocvar_leadlimit;
  // TODO: world.qh can't be included here due to circular includes!
  #define autocvar_fraglimit cvar("fraglimit")
@@@ -87,8 -89,6 +90,6 @@@ REGISTER_STAT(GAME_STOPPED, int, game_s
  REGISTER_STAT(GAMESTARTTIME, float, game_starttime)
  /** arc heat in [0,1] */
  REGISTER_STAT(PRESSED_KEYS, int)
- /** this stat could later contain some other bits of info, like, more server-side particle config */
- REGISTER_STAT(ALLOW_OLDVORTEXBEAM, bool, autocvar_g_allow_oldvortexbeam)
  REGISTER_STAT(FUEL, int)
  REGISTER_STAT(NB_METERSTART, float)
  /** compressShotOrigin */
@@@ -126,7 -126,6 +127,6 @@@ REGISTER_STAT(PLASMA, int
  REGISTER_STAT(FROZEN, int)
  REGISTER_STAT(REVIVE_PROGRESS, float)
  REGISTER_STAT(ROUNDLOST, int)
- REGISTER_STAT(CTF_FLAGSTATUS, int)
  REGISTER_STAT(CAPTURE_PROGRESS, float)
  REGISTER_STAT(ENTRAP_ORB, float)
  REGISTER_STAT(ENTRAP_ORB_ALPHA, float)
@@@ -180,6 -179,7 +180,7 @@@ float g_bugrigs_speed_ref
  float g_bugrigs_speed_pow;
  float g_bugrigs_steer;
  #endif
+ #if 0
  REGISTER_STAT(BUGRIGS, int, g_bugrigs)
  REGISTER_STAT(BUGRIGS_ACCEL, float, g_bugrigs_accel)
  REGISTER_STAT(BUGRIGS_AIR_STEERING, int, g_bugrigs_air_steering)
@@@ -195,6 -195,7 +196,7 @@@ REGISTER_STAT(BUGRIGS_REVERSE_STOPPING
  REGISTER_STAT(BUGRIGS_SPEED_POW, float, g_bugrigs_speed_pow)
  REGISTER_STAT(BUGRIGS_SPEED_REF, float, g_bugrigs_speed_ref)
  REGISTER_STAT(BUGRIGS_STEER, float, g_bugrigs_steer)
+ #endif
  
  #ifdef SVQC
  int autocvar_sv_gameplayfix_downtracesupportsongroundflag = 1;
@@@ -351,7 -352,10 +353,7 @@@ bool autocvar_sv_slick_applygravity
  #endif
  REGISTER_STAT(SLICK_APPLYGRAVITY, bool, autocvar_sv_slick_applygravity)
  
 -#ifdef SVQC
 -bool autocvar_sv_q3defragcompat;
 -#endif
 -REGISTER_STAT(Q3DEFRAGCOMPAT, bool, autocvar_sv_q3defragcompat)
 +REGISTER_STAT(Q3COMPAT, int, q3compat)
  
  #ifdef SVQC
  #include "physics/movetypes/movetypes.qh"
@@@ -417,7 -421,6 +419,6 @@@ REGISTER_STAT(MOVEVARS_MAXAIRSPEED, flo
  REGISTER_STAT(MOVEVARS_STEPHEIGHT, float, autocvar_sv_stepheight)
  REGISTER_STAT(MOVEVARS_AIRACCEL_QW, float)
  REGISTER_STAT(MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, float)
- REGISTER_STAT(MOVEVARS_SPECIALCOMMAND, bool)
  #ifdef SVQC
  int autocvar_sv_wallclip;
  #endif
diff --combined qcsrc/lib/spawnfunc.qh
index 7e8c025a2a3100b1f338fc2c153e038c856f2379,5d35089e5ab1daf83c1441691f5a3538d63c4555..fc15a0285bb36f41b5c87eb5ca6d27f579ed860e
@@@ -7,8 -7,7 +7,8 @@@
  noref bool require_spawnfunc_prefix;
  .bool spawnfunc_checked;
  /** Not for production use, provides access to a dump of the entity's fields when it is parsed from map data */
 -//noref string __fullspawndata;
 +noref string __fullspawndata;
 +.string fullspawndata;
  
  // Optional type checking; increases compile time too much to be enabled by default
  #if 0
                FIELD_SCALAR(fld, height) \
                FIELD_SCALAR(fld, impulse) \
                FIELD_SCALAR(fld, invincible_finished) \
+               FIELD_SCALAR(fld, invisibility_finished) \
                FIELD_SCALAR(fld, item_pickupsound) \
                FIELD_SCALAR(fld, killtarget) \
                FIELD_SCALAR(fld, lerpfrac) \
                FIELD_SCALAR(fld, noise2) \
                FIELD_SCALAR(fld, noise3) \
                FIELD_SCALAR(fld, noise) \
 +              FIELD_SCALAR(fld, notcpm) \
 +              FIELD_SCALAR(fld, notfree) \
 +              FIELD_SCALAR(fld, notta) \
 +              FIELD_SCALAR(fld, notteam) \
 +              FIELD_SCALAR(fld, notvq3) \
                FIELD_SCALAR(fld, phase) \
                FIELD_SCALAR(fld, platmovetype) \
                FIELD_SCALAR(fld, race_place) \
+               FIELD_SCALAR(fld, speed_finished) \
                FIELD_SCALAR(fld, strength_finished) \
                FIELD_SCALAR(fld, radius) \
                FIELD_SCALAR(fld, respawntimestart) \
@@@ -290,11 -286,6 +292,11 @@@ noref bool __spawnfunc_first
                this.classname = #id; \
                if (!this.spawnfunc_checked) { \
                        _checkWhitelisted(this, #id); \
 +                      if (__fullspawndata) { \
 +                              /* not supported in old DP */ \
 +                              /* must be read inside the real spawnfunc */ \
 +                              this.fullspawndata = __fullspawndata; \
 +                      } \
                        this.spawnfunc_checked = true; \
                        if (this) { \
                                /* not worldspawn, delay spawn */ \
index 20735d99fccfcbe8c2b23efea27edffeb2129c96,66658a3901d1dd7837e5883675a328c26e35b927..46710db66c92c041fd447a2ea3e00c2edde83356
@@@ -585,6 -585,7 +585,7 @@@ bool WarpZoneLib_BadEntity(entity e
                case "spawnfunc":
                case "weaponchild":
                case "chatbubbleentity":
+               case "buff_model":
                //case "net_linked": // actually some real entities are linked without classname, fail
                case "":
                        return true;
@@@ -797,13 -798,10 +798,13 @@@ entity WarpZone_RefSys_SpawnSameRefSys(
  bool WarpZoneLib_ExactTrigger_Touch(entity this, entity toucher)
  {
        vector emin = toucher.absmin, emax = toucher.absmax;
 -      // the engine offsets absolute bounding boxes by a single quake unit
 -      // we must undo that here to allow accurate touching
 -      emin += '1 1 1';
 -      emax -= '1 1 1';
 +      if(STAT(Q3COMPAT))
 +      {
 +              // DP's tracebox enlarges absolute bounding boxes by a single quake unit
 +              // we must undo that here to allow accurate touching
 +              emin += '1 1 1';
 +              emax -= '1 1 1';
 +      }
        return !WarpZoneLib_BoxTouchesBrush(emin, emax, this, toucher);
  }
  
diff --combined qcsrc/server/client.qc
index 2652b16a9ae715dd07da5417d67dea2772dc9c79,a9758146f5dddfaca618b7ddc6b6a624fa7da1ef..69e416826174e1bd04397cfac2b4c1d8033e2f52
@@@ -6,6 -6,7 +6,7 @@@
  #include <common/effects/qc/globalsound.qh>
  #include <common/ent_cs.qh>
  #include <common/gamemodes/_mod.qh>
+ #include <common/gamemodes/gamemode/lms/sv_lms.qh>
  #include <common/gamemodes/gamemode/nexball/sv_nexball.qh>
  #include <common/items/_mod.qh>
  #include <common/items/inventory.qh>
@@@ -590,9 -591,13 +591,13 @@@ void PutPlayerInServer(entity this
  
        PS(this).dual_weapons = '0 0 0';
  
+       if(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS)
+               StatusEffects_apply(STATUSEFFECT_Superweapons, this, time + autocvar_g_balance_superweapons_time, 0);
        this.items = start_items;
  
-       this.spawnshieldtime = time + autocvar_g_spawnshieldtime;
+       float shieldtime = time + autocvar_g_spawnshieldtime;
        this.pauserotarmor_finished = time + autocvar_g_balance_pause_armor_rot_spawn;
        this.pauserothealth_finished = time + autocvar_g_balance_pause_health_rot_spawn;
        this.pauserotfuel_finished = time + autocvar_g_balance_pause_fuel_rot_spawn;
        if (!sv_ready_restart_after_countdown && time < game_starttime)
        {
                float f = game_starttime - time;
-               this.spawnshieldtime += f;
+               shieldtime += f;
                this.pauserotarmor_finished += f;
                this.pauserothealth_finished += f;
                this.pauseregen_finished += f;
        }
  
+       StatusEffects_apply(STATUSEFFECT_SpawnShield, this, shieldtime, 0);
        this.damageforcescale = autocvar_g_player_damageforcescale;
        this.death_time = 0;
        this.respawn_flags = 0;
        this.respawn_time = 0;
        STAT(RESPAWN_TIME, this) = 0;
 -      bool q3dfcompat = autocvar_sv_q3defragcompat && autocvar_sv_q3defragcompat_changehitbox;
 -      this.scale = ((q3dfcompat) ? 0.9 : autocvar_sv_player_scale);
 +      this.scale = ((q3compat && autocvar_sv_q3compat_changehitbox) ? 0.9 : autocvar_sv_player_scale);
        this.fade_time = 0;
        this.pain_finished = 0;
        this.pushltime = 0;
@@@ -1036,14 -1044,10 +1043,10 @@@ string getwelcomemessage(entity this
                modifications = strcat(modifications, ", Weapons stay");
        if(autocvar_g_jetpack)
                modifications = strcat(modifications, ", Jet pack");
-       if(autocvar_g_powerups == 0)
-               modifications = strcat(modifications, ", No powerups");
-       if(autocvar_g_powerups > 0)
-               modifications = strcat(modifications, ", Powerups");
        modifications = substring(modifications, 2, strlen(modifications) - 2);
  
        string versionmessage = GetClientVersionMessage(this);
-       string s = strcat(versionmessage, "^8\n^8\nhost is ^9", autocvar_hostname, "^8\n");
+       string s = strcat(versionmessage, "^8\n^8\nserver is ^9", autocvar_hostname, "^8\n");
  
        s = strcat(s, "^8\nmatch type is ^1", gamemode_name, "^8\n");
  
        return s;
  }
  
- bool autocvar_sv_qcphysics = true; // TODO this is for testing - remove when qcphysics work
  /**
  =============
  ClientConnect
@@@ -1444,14 -1446,12 +1445,12 @@@ void play_countdown(entity this, float 
  
  void player_powerups_remove_all(entity this)
  {
-       if (this.items & (ITEM_Strength.m_itemid | ITEM_Shield.m_itemid | IT_SUPERWEAPON))
+       if (this.items & IT_SUPERWEAPON)
        {
                // don't play the poweroff sound when the game restarts or the player disconnects
                if (time > game_starttime + 1 && IS_CLIENT(this))
                        sound(this, CH_INFO, SND_POWEROFF, VOL_BASE, ATTEN_NORM);
                stopsound(this, CH_TRIGGER_SINGLE); // get rid of the pickup sound
-               this.items &= ~ITEM_Strength.m_itemid;
-               this.items &= ~ITEM_Shield.m_itemid;
                this.items -= (this.items & IT_SUPERWEAPON);
        }
  }
@@@ -1463,7 -1463,7 +1462,7 @@@ void player_powerups(entity this
        else
                this.modelflags &= ~MF_ROCKET;
  
-       this.effects &= ~(EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT | EF_NODEPTHTEST);
+       this.effects &= ~EF_NODEPTHTEST;
  
        if (IS_DEAD(this))
                player_powerups_remove_all(this);
  
        if (!MUTATOR_IS_ENABLED(mutator_instagib))
        {
-               if (this.items & ITEM_Strength.m_itemid)
-               {
-                       play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Strength, this), SND_POWEROFF);
-                       this.effects = this.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT);
-                       if (time > StatusEffects_gettime(STATUSEFFECT_Strength, this))
-                       {
-                               this.items = this.items - (this.items & ITEM_Strength.m_itemid);
-                               //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERDOWN_STRENGTH, this.netname);
-                               Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERDOWN_STRENGTH);
-                       }
-               }
-               else
-               {
-                       if (time < StatusEffects_gettime(STATUSEFFECT_Strength, this))
-                       {
-                               this.items = this.items | ITEM_Strength.m_itemid;
-                               if(!g_cts)
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_STRENGTH, this.netname);
-                               Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERUP_STRENGTH);
-                       }
-               }
-               if (this.items & ITEM_Shield.m_itemid)
-               {
-                       play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Shield, this), SND_POWEROFF);
-                       this.effects = this.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT);
-                       if (time > StatusEffects_gettime(STATUSEFFECT_Shield, this))
-                       {
-                               this.items = this.items - (this.items & ITEM_Shield.m_itemid);
-                               //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERDOWN_SHIELD, this.netname);
-                               Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERDOWN_SHIELD);
-                       }
-               }
-               else
-               {
-                       if (time < StatusEffects_gettime(STATUSEFFECT_Shield, this))
-                       {
-                               this.items = this.items | ITEM_Shield.m_itemid;
-                               if(!g_cts)
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_SHIELD, this.netname);
-                               Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERUP_SHIELD);
-                       }
-               }
+               // NOTE: superweapons are a special case and as such are handled here instead of the status effects system
                if (this.items & IT_SUPERWEAPON)
                {
                        if (!(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS))
        if(autocvar_g_fullbrightplayers)
                this.effects = this.effects | EF_FULLBRIGHT;
  
-       if (time >= game_starttime)
-       if (time < this.spawnshieldtime)
-               this.effects = this.effects | (EF_ADDITIVE | EF_FULLBRIGHT);
        MUTATOR_CALLHOOK(PlayerPowerups, this, items_prev);
  }
  
@@@ -2724,6 -2679,7 +2678,7 @@@ void PlayerPostThink (entity this
                                        Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_IDLING, this.netname, maxidle_time);
                                        if (this.caplayer)
                                                this.caplayer = 0;
+                                       this.lms_spectate_warning = 2; // TODO: mutator hook for players forcibly moved to spectator?
                                        PutObserverInServer(this);
                                }
                                else
index d578994968796c6baf86f8cb816c7c7504f74f16,121df69265166023ff4188cf1cd6810183d184aa..effbbc10caa304c64e65617abc4f8e30dec2a9cc
@@@ -6,6 -6,7 +6,7 @@@
  #include <common/mapobjects/triggers.qh>
  #include <common/mutators/mutator/buffs/buffs.qh>
  #include <common/mutators/mutator/buffs/sv_buffs.qh>
+ #include <common/mutators/mutator/powerups/_mod.qh>
  #include <common/mutators/mutator/status_effects/_mod.qh>
  #include <common/notifications/all.qh>
  #include <common/stats.qh>
  #include <server/resources.qh>
  #include <server/world.qh>
  
 -//***********************
 -//QUAKE 3 ENTITIES - So people can play quake3 maps with the xonotic weapons
 -//***********************
 +/***********************
 + * QUAKE 3 ENTITIES - So people can play quake3 maps with the xonotic weapons
 + ***********************
 +
 + * Map entities NOT handled in this file:
 + holdable_invulnerability     Q3TA    buffs mutator
 + holdable_kamikaze            Q3TA    buffs mutator
 + holdable_teleporter          Q3A     buffs mutator
 + item_ammoregen                       Q3TA    buffs mutator
 + item_doubler                 Q3TA    buffs mutator
 + item_guard                   Q3TA    buffs mutator
 + item_scout                   Q3TA    buffs mutator
 + item_armor_jacket            CPMA    quake2.qc
 + item_flight                  Q3A     buffs mutator
 + item_haste                   Q3A     buffs mutator
 + item_health                  Q3A     quake.qc
 + item_health_large            Q3A     items.qc
 + item_health_small            Q3A     health.qh
 + item_health_mega             Q3A     health.qh
 + item_invis                   Q3A     buffs mutator
 + item_quad                    Q3A     items.qc
 + item_regen                   Q3A     buffs mutator
 + weapon_machinegun            Q3A     machinegun.qh
 + weapon_grenadelauncher               Q3A     mortar.qh
 + weapon_rocketlauncher                Q3A     devastator.qh
 + CTF spawnfuncs handled in sv_ctf.qc
 +
 + NOTE: for best experience, you need to swap MGs with SGs in the map or it won't have a MG
 +*/
 +
 +// SG -> MG || SG
 +SPAWNFUNC_Q3_COND(weapon_shotgun, ammo_shells, (q3compat & Q3COMPAT_ARENA), WEP_MACHINEGUN, WEP_SHOTGUN)
 +
 +// MG -> SG || MG
 +// Technically we should replace weapon_machinegun with WEP_SHOTGUN if Q3COMPAT_ARENA, but it almost never occurs on Q3 maps
 +SPAWNFUNC_Q3AMMO_COND(ammo_bullets, (q3compat & Q3COMPAT_ARENA), WEP_SHOTGUN, WEP_MACHINEGUN)
  
 -// NOTE: for best experience, you need to swap MGs with SGs in the map or it won't have a MG
 +// GL -> Mortar
 +SPAWNFUNC_Q3AMMO(ammo_grenades, WEP_MORTAR)
  
 -// SG -> SG
 -SPAWNFUNC_ITEM(ammo_shells, ITEM_Shells)
 +// Team Arena Proximity Launcher -> Mortar
 +// It's more accurate to spawn Mine Layer but players prefer Mortar, and weapon_grenadelauncher is usually disabled by "notta" and weapon_prox_launcher placed at the same origin
 +SPAWNFUNC_Q3(weapon_prox_launcher, ammo_mines, WEP_MORTAR)
  
 -// MG -> MG
 -SPAWNFUNC_ITEM(ammo_bullets, ITEM_Bullets)
 +// Team Arena Chaingun -> HLAC
 +SPAWNFUNC_Q3(weapon_chaingun, ammo_belt, WEP_HLAC)
  
 -// GL -> Mortar
 -SPAWNFUNC_ITEM(ammo_grenades, ITEM_Rockets)
 +// Quake Live Heavy Machine Gun -> HLAC
 +SPAWNFUNC_Q3(weapon_hmg, ammo_hmg, WEP_HLAC)
  
 -// Mines -> Rockets
 -SPAWNFUNC_WEAPON(weapon_prox_launcher, WEP_MINE_LAYER)
 -SPAWNFUNC_ITEM(ammo_mines, ITEM_Rockets)
 +// Team Arena Nailgun -> Crylink || Quake Nailgun -> Electro
 +SPAWNFUNC_Q3_COND(weapon_nailgun, ammo_nails, cvar("sv_mapformat_is_quake3"), WEP_CRYLINK, WEP_ELECTRO)
  
 -// LG -> Lightning
 -SPAWNFUNC_WEAPON(weapon_lightning, WEP_ELECTRO)
 -SPAWNFUNC_ITEM(ammo_lightning, ITEM_Cells)
 +// LG -> Electro
 +SPAWNFUNC_Q3(weapon_lightning, ammo_lightning, WEP_ELECTRO)
  
  // Plasma -> Hagar
 -SPAWNFUNC_WEAPON(weapon_plasmagun, WEP_HAGAR)
 -SPAWNFUNC_ITEM(ammo_cells, ITEM_Rockets)
 +SPAWNFUNC_Q3(weapon_plasmagun, ammo_cells, WEP_HAGAR)
  
  // Rail -> Vortex
 -SPAWNFUNC_WEAPON(weapon_railgun, WEP_VORTEX)
 -SPAWNFUNC_ITEM(ammo_slugs, ITEM_Cells)
 +SPAWNFUNC_Q3(weapon_railgun, ammo_slugs, WEP_VORTEX)
  
 -// BFG -> Crylink
 -SPAWNFUNC_WEAPON(weapon_bfg, WEP_CRYLINK)
 -SPAWNFUNC_ITEM(ammo_bfg, ITEM_Cells)
 +// BFG -> Crylink || Fireball
 +SPAWNFUNC_Q3_COND(weapon_bfg, ammo_bfg, cvar_string("g_mod_balance") == "XDF", WEP_CRYLINK, WEP_FIREBALL)
 +      // FIXME: WEP_FIREBALL has no ammo_type field so ammo_bfg is deleted by SPAWNFUNC_BODY
  
  // grappling hook -> hook
  SPAWNFUNC_WEAPON(weapon_grapplinghook, WEP_HOOK)
  
  // RL -> RL
 -SPAWNFUNC_ITEM(ammo_rockets, ITEM_Rockets)
 +SPAWNFUNC_Q3AMMO(ammo_rockets, WEP_DEVASTATOR)
 +
 +// Gauntlet -> Tuba
 +SPAWNFUNC_ITEM(weapon_gauntlet, WEP_TUBA)
  
  // Armor
  SPAWNFUNC_ITEM(item_armor_body, ITEM_ArmorMega)
  SPAWNFUNC_ITEM(item_armor_combat, ITEM_ArmorBig)
  SPAWNFUNC_ITEM(item_armor_shard, ITEM_ArmorSmall)
 +SPAWNFUNC_ITEM(item_armor_green, ITEM_ArmorMedium) // CCTF
 +
 +// Battle Suit
  SPAWNFUNC_ITEM(item_enviro, ITEM_Shield)
  
  // medkit -> armor (we have no holdables)
@@@ -169,8 -133,10 +170,10 @@@ void target_init_use(entity this, entit
  
        if (!(this.spawnflags & 8))
        {
-               StatusEffects_remove(STATUSEFFECT_Strength, actor, STATUSEFFECT_REMOVE_NORMAL);
-               StatusEffects_remove(STATUSEFFECT_Shield, actor, STATUSEFFECT_REMOVE_NORMAL);
+               FOREACH(StatusEffect, it.instanceOfPowerups,
+               {
+                       it.m_remove(it, actor, STATUSEFFECT_REMOVE_NORMAL);
+               });
                entity heldbuff = buff_FirstFromFlags(actor);
                if(heldbuff) // TODO: make a dropbuffs function to handle this
                {
@@@ -197,42 -163,52 +200,42 @@@ spawnfunc(target_init
        InitializeEntity(this, target_init_verify, INITPRIO_FINDTARGET);
  }
  
 -// weapon give ent from defrag
 +// weapon give ent from Q3
  void target_give_init(entity this)
  {
        IL_EACH(g_items, it.targetname == this.target,
        {
 -              if (it.classname == "weapon_devastator") {
 -                      SetResourceExplicit(this, RES_ROCKETS, GetResource(this, RES_ROCKETS) + it.count * WEP_CVAR_PRI(devastator, ammo)); // WEAPONTODO
 -                      this.netname = cons(this.netname, "devastator");
 -              }
 -              else if (it.classname == "weapon_vortex") {
 -                      SetResourceExplicit(this, RES_CELLS, GetResource(this, RES_CELLS) + it.count * WEP_CVAR_PRI(vortex, ammo)); // WEAPONTODO
 -                      this.netname = cons(this.netname, "vortex");
 -              }
 -              else if (it.classname == "weapon_electro") {
 -                      SetResourceExplicit(this, RES_CELLS, GetResource(this, RES_CELLS) + it.count * WEP_CVAR_PRI(electro, ammo)); // WEAPONTODO
 -                      this.netname = cons(this.netname, "electro");
 -              }
 -              else if (it.classname == "weapon_hagar") {
 -                      SetResourceExplicit(this, RES_ROCKETS, GetResource(this, RES_ROCKETS) + it.count * WEP_CVAR_PRI(hagar, ammo)); // WEAPONTODO
 -                      this.netname = cons(this.netname, "hagar");
 -              }
 -              else if (it.classname == "weapon_crylink") {
 -                      SetResourceExplicit(this, RES_CELLS, GetResource(this, RES_CELLS) + it.count * WEP_CVAR_PRI(crylink, ammo)); // WEAPONTODO
 -                      this.netname = cons(this.netname, "crylink");
 -              }
 -              else if (it.classname == "weapon_mortar") {
 -                      SetResourceExplicit(this, RES_ROCKETS, GetResource(this, RES_ROCKETS) + it.count * WEP_CVAR_PRI(mortar, ammo)); // WEAPONTODO
 -                      this.netname = cons(this.netname, "mortar");
 -              }
 -              else if (it.classname == "weapon_shotgun") {
 -                      SetResourceExplicit(this, RES_SHELLS, GetResource(this, RES_SHELLS) + it.count * WEP_CVAR_PRI(shotgun, ammo)); // WEAPONTODO
 -                      this.netname = cons(this.netname, "shotgun");
 -              }
 -              else if (it.classname == "item_armor_mega")
 -                      SetResourceExplicit(this, RES_ARMOR, 100);
 -              else if (it.classname == "item_health_mega")
 -                      SetResourceExplicit(this, RES_HEALTH, 200);
 -              else if (it.classname == "item_buff") {
 +              if (it.classname == "item_buff")
 +              {
                        entity buff = it.buffdef;
                        this.netname = cons(this.netname, buff.netname);
 -                      this.buffs_finished = it.count;
 +                      this.buffs_finished += it.count;
 +              }
 +              else
 +              {
 +                      if (it.ammo_rockets)
 +                              this.ammo_rockets += it.ammo_rockets;
 +                      else if (it.ammo_cells)
 +                              this.ammo_cells += it.ammo_cells;
 +                      else if (it.ammo_shells)
 +                              this.ammo_shells += it.ammo_shells;
 +                      else if (it.ammo_nails)
 +                              this.ammo_nails += it.ammo_nails;
 +                      else if (it.invincible_finished)
 +                              this.invincible_finished += it.invincible_finished;
 +                      else if (it.strength_finished)
 +                              this.strength_finished += it.strength_finished;
 +                      else if (it.health)
 +                              this.health += it.health;
 +                      else if (it.armorvalue)
 +                              this.armorvalue += it.armorvalue;
 +
 +                      this.netname = cons(this.netname, it.netname);
                }
  
                //remove(it); // removing ents in init functions causes havoc, workaround:
 -        setthink(it, SUB_Remove);
 -        it.nextthink = time;
 +              setthink(it, SUB_Remove);
 +              it.nextthink = time;
        });
        this.spawnflags = 2;
        this.spawnfunc_checked = true;
@@@ -276,31 -252,35 +279,31 @@@ spawnfunc(target_fragsFilter
        this.use = fragsfilter_use;
  }
  
 -//spawnfunc(item_flight)       /* handled by buffs mutator */
 -//spawnfunc(item_doubler)        /* handled by buffs mutator */
 -//spawnfunc(item_haste)        /* handled by powerups mutator */
 -//spawnfunc(item_health)       /* handled in t_quake.qc */
 -//spawnfunc(item_health_large) /* handled in items.qc */
 -//spawnfunc(item_health_small) /* handled in items.qc */
 -//spawnfunc(item_health_mega)  /* handled in items.qc */
 -//spawnfunc(item_invis)        /* handled by powerups mutator */
 -//spawnfunc(item_regen)        /* handled by buffs mutator */
 -
 -// CTF spawnfuncs handled in mutators/gamemode_ctf.qc now
 -
 -.float notteam;
 -.float notsingle;
 -.float notfree;
 -.float notq3a;
 -.float notta;
 +.bool notteam;
 +.bool notsingle;
 +.bool notfree;
 +.bool notta;
 +.bool notvq3;
 +.bool notcpm;
  .string gametype;
  bool DoesQ3ARemoveThisEntity(entity this)
  {
        // Q3 style filters (DO NOT USE, THIS IS COMPAT ONLY)
  
 -      if(this.notq3a)
 -              if(!teamplay || g_tdm || g_ctf)
 +      // DeFRaG mappers use "notcpm" or "notvq3" to disable an entity in CPM or VQ3 physics
 +      // Xonotic is usually played with a CPM-based physics so we default to CPM mode
 +      if(cvar_string("g_mod_physics") == "Q3")
 +      {
 +              if(this.notvq3)
                        return true;
 +      }
 +      else if(this.notcpm)
 +              return true;
  
 +      // Q3 mappers use "notq3a" or "notta" to disable an entity in Q3A or Q3TA
 +      // Xonotic has ~equivalent features to Team Arena
        if(this.notta)
 -              if (!(!teamplay || g_tdm || g_ctf))
 -                      return true;
 +              return true;
  
        if(this.notsingle)
                if(maxclients == 1)
        if(this.gametype)
        {
                string gametypename;
 -              // static char *gametypeNames[] = {"ffa", "tournament", "single", "team", "ctf", "oneflag", "obelisk", "harvester", "teamtournament"}
 +              // From ioq3 g_spawn.c: static char *gametypeNames[] = {"ffa", "tournament", "single", "team", "ctf", "oneflag", "obelisk", "harvester"};
                gametypename = "ffa";
                if(teamplay)
                        gametypename = "team";
                        gametypename = "tournament";
                if(maxclients == 1)
                        gametypename = "single";
 -              // we do not have the other types (obelisk, harvester, teamtournament)
 +              // we do not have the other types (obelisk, harvester)
                if(strstrofs(this.gametype, gametypename, 0) < 0)
                        return true;
        }
  
        return false;
  }
 +
 +int GetAmmoConsumptionQ3(string netname)
 +// Returns ammo consumed per shot by the primary/default fire mode
 +// Returns 0 if the netname has no ammo cvar
 +{
 +      switch (netname)
 +      {
 +              case "arc":        return autocvar_g_balance_arc_beam_ammo;
 +              case "devastator": return autocvar_g_balance_devastator_ammo;
 +              case "machinegun": return autocvar_g_balance_machinegun_sustained_ammo;
 +              case "minelayer":  return autocvar_g_balance_minelayer_ammo;
 +              case "seeker":     return autocvar_g_balance_seeker_tag_ammo;
 +              default:           return cvar(strcat("g_balance_", netname, "_primary_ammo"));
 +      }
 +}
 +
index 66d0d296394b39f95e029e4117ef411709c101a6,610c864b121e30b52e449e99a4eb3ab4a1c90505..f2056cbc25c19d40bdd8e705c7d8ad6b86fadcf5
@@@ -9,6 -9,7 +9,7 @@@
  #include <common/monsters/_mod.qh>
  #include <common/mutators/mutator/buffs/buffs.qh>
  #include <common/mutators/mutator/buffs/sv_buffs.qh>
+ #include <common/mutators/mutator/powerups/_mod.qh>
  #include <common/mutators/mutator/status_effects/_mod.qh>
  #include <common/notifications/all.qh>
  #include <common/util.qh>
@@@ -55,8 -56,8 +56,8 @@@ bool ItemSend(entity this, entity to, i
  
        if(sf & ISF_MODEL)
        {
-               WriteShort(MSG_ENTITY, this.fade_end);
-               WriteShort(MSG_ENTITY, this.fade_start);
+               WriteShort(MSG_ENTITY, bound(0, this.fade_end, 32767));
+               WriteShort(MSG_ENTITY, bound(0, this.fade_start, 32767));
  
                if(this.mdl == "")
                        LOG_TRACE("^1WARNING!^7 this.mdl is unset for item ", this.classname, "expect a crash just about now");
@@@ -557,6 -558,16 +558,16 @@@ bool Item_GiveTo(entity item, entity pl
                pickedup = true;
                StatusEffects_apply(STATUSEFFECT_Shield, player, max(StatusEffects_gettime(STATUSEFFECT_Shield, player), time) + item.invincible_finished, 0);
        }
+       if (item.speed_finished)
+       {
+               pickedup = true;
+               StatusEffects_apply(STATUSEFFECT_Speed, player, max(StatusEffects_gettime(STATUSEFFECT_Speed, player), time) + item.speed_finished, 0);
+       }
+       if (item.invisibility_finished)
+       {
+               pickedup = true;
+               StatusEffects_apply(STATUSEFFECT_Invisibility, player, max(StatusEffects_gettime(STATUSEFFECT_Invisibility, player), time) + item.invisibility_finished, 0);
+       }
        if (item.superweapons_finished)
        {
                pickedup = true;
@@@ -628,6 -639,8 +639,8 @@@ void Item_Touch(entity this, entity tou
        {
                this.strength_finished = max(0, this.strength_finished - time);
                this.invincible_finished = max(0, this.invincible_finished - time);
+               this.speed_finished = max(0, this.speed_finished - time);
+               this.invisibility_finished = max(0, this.invisibility_finished - time);
                this.superweapons_finished = max(0, this.superweapons_finished - time);
        }
        bool gave = ITEM_HANDLE(Pickup, this.itemdef, this, toucher);
                        // undo what we did above
                        this.strength_finished += time;
                        this.invincible_finished += time;
+                       this.speed_finished += time;
+                       this.invisibility_finished += time;
                        this.superweapons_finished += time;
                }
                return;
@@@ -950,6 -965,9 +965,9 @@@ void _StartItem(entity this, entity def
  
                this.takedamage = DAMAGE_YES;
                this.event_damage = Item_Damage;
+               // enable this to have thrown items burn in lava
+               //this.damagedbycontents = true;
+               //IL_PUSH(g_damagedbycontents, this);
  
                if (Item_IsExpiring(this))
                {
  
                if(autocvar_spawn_debug >= 2)
                {
 -            // why not flags & fl_item?
 -                  FOREACH_ENTITY_RADIUS(this.origin, 3, it.is_item, {
 -                LOG_TRACE("XXX Found duplicated item: ", itemname, vtos(this.origin));
 -                LOG_TRACE(" vs ", it.netname, vtos(it.origin));
 -                error("Mapper sucks.");
 -            });
 +                      // why not flags & fl_item?
 +                      FOREACH_ENTITY_RADIUS(this.origin, 3, it.is_item, {
 +                              LOG_TRACE("XXX Found duplicated item: ", itemname, vtos(this.origin));
 +                              LOG_TRACE(" vs ", it.netname, vtos(it.origin));
 +                              error("Mapper sucks.");
 +                      });
                        this.is_item = true;
                }
  
                weaponsInMap |= WepSet_FromWeapon(REGISTRY_GET(Weapons, weaponid));
  
 -              if (   def.instanceOfPowerup
 -                      || def.instanceOfWeaponPickup
 +              if (        def.instanceOfPowerup
 +                      ||  def.instanceOfWeaponPickup
                        || (def.instanceOfHealth && def != ITEM_HealthSmall)
                        || (def.instanceOfArmor && def != ITEM_ArmorSmall)
                        || (itemid & (IT_KEY1 | IT_KEY2))
        this.bot_pickupevalfunc = pickupevalfunc;
        this.bot_pickupbasevalue = pickupbasevalue;
        this.mdl = this.model ? this.model : strzone(this.item_model_ent.model_str());
 -      this.netname = itemname;
 +      this.netname = (def.m_weapon) ? def.m_weapon.netname : def.netname;
        settouch(this, Item_Touch);
        setmodel(this, MDL_Null); // precision set below
        //this.effects |= EF_LOWPRECISION;
  
  void StartItem(entity this, GameItem def)
  {
 -    def = def.m_spawnfunc_hookreplace(def, this);
 -    if (def.spawnflags & ITEM_FLAG_MUTATORBLOCKED)
 -    {
 -        delete(this);
 -        return;
 -    }
 -    this.classname = def.m_canonical_spawnfunc;
 -    _StartItem(
 -      this,
 -      this.itemdef = def,
 -      def.m_respawntime(), // defaultrespawntime
 -      def.m_respawntimejitter() // defaultrespawntimejitter
 +      def = def.m_spawnfunc_hookreplace(def, this);
 +
 +      if (def.spawnflags & ITEM_FLAG_MUTATORBLOCKED)
 +      {
 +              delete(this);
 +              return;
 +      }
 +
 +      this.classname = def.m_canonical_spawnfunc;
 +
 +      _StartItem(
 +              this,
 +              this.itemdef = def,
 +              def.m_respawntime(), // defaultrespawntime
 +              def.m_respawntimejitter() // defaultrespawntimejitter
        );
  }
  
@@@ -1203,6 -1218,10 +1221,10 @@@ spawnfunc(target_items
                this.strength_finished = autocvar_g_balance_powerup_strength_time;
        if(!this.invincible_finished)
                this.invincible_finished = autocvar_g_balance_powerup_invincible_time;
+       if(!this.speed_finished)
+               this.speed_finished = autocvar_g_balance_powerup_speed_time;
+       if(!this.invisibility_finished)
+               this.invisibility_finished = autocvar_g_balance_powerup_invisibility_time;
        if(!this.superweapons_finished)
                this.superweapons_finished = autocvar_g_balance_superweapons_time;
  
                        else if(argv(j) == "unlimited_superweapons") this.items |= IT_UNLIMITED_SUPERWEAPONS;
                        else if(argv(j) == "strength")               this.items |= ITEM_Strength.m_itemid;
                        else if(argv(j) == "invincible")             this.items |= ITEM_Shield.m_itemid;
+                       else if(argv(j) == "speed")                  this.items |= ITEM_Speed.m_itemid;
+                       else if(argv(j) == "invisibility")           this.items |= ITEM_Invisibility.m_itemid;
                        else if(argv(j) == "superweapons")           this.items |= IT_SUPERWEAPON;
                        else if(argv(j) == "jetpack")                this.items |= ITEM_Jetpack.m_itemid;
                        else if(argv(j) == "fuel_regen")             this.items |= ITEM_JetpackRegen.m_itemid;
                str = sprintf("%s %s%d %s", str, itemprefix, boolean(this.items & IT_UNLIMITED_SUPERWEAPONS), "unlimited_superweapons");
                str = sprintf("%s %s%d %s", str, valueprefix, this.strength_finished * boolean(this.items & ITEM_Strength.m_itemid), "strength");
                str = sprintf("%s %s%d %s", str, valueprefix, this.invincible_finished * boolean(this.items & ITEM_Shield.m_itemid), "invincible");
+               str = sprintf("%s %s%d %s", str, valueprefix, this.invisibility_finished * boolean(this.items & ITEM_Invisibility.m_itemid), "invisibility");
+               str = sprintf("%s %s%d %s", str, valueprefix, this.speed_finished * boolean(this.items & ITEM_Speed.m_itemid), "speed");
                str = sprintf("%s %s%d %s", str, valueprefix, this.superweapons_finished * boolean(this.items & IT_SUPERWEAPON), "superweapons");
                str = sprintf("%s %s%d %s", str, itemprefix, boolean(this.items & ITEM_Jetpack.m_itemid), "jetpack");
                str = sprintf("%s %s%d %s", str, itemprefix, boolean(this.items & ITEM_JetpackRegen.m_itemid), "fuel_regen");
@@@ -1365,7 -1388,8 +1391,8 @@@ bool GiveBuff(entity e, Buff thebuff, i
        }
        if(new_buff_time <= 0)
        {
-               StatusEffects_remove(thebuff, e, STATUSEFFECT_REMOVE_TIMEOUT);
+               if(had_buff) // only trigger removal mechanics if there is an effect to remove!
+                       StatusEffects_remove(thebuff, e, STATUSEFFECT_REMOVE_NORMAL);
        }
        else
        {
@@@ -1439,7 -1463,10 +1466,10 @@@ bool GiveStatusEffect(entity e, StatusE
                        break;
        }
        if(new_eff_time <= 0)
-               StatusEffects_remove(this, e, STATUSEFFECT_REMOVE_TIMEOUT);
+       {
+               if(had_eff) // only trigger removal mechanics if there is an effect to remove!
+                       StatusEffects_remove(this, e, STATUSEFFECT_REMOVE_NORMAL);
+       }
        else
                StatusEffects_apply(this, e, new_eff_time, 0);
        bool have_eff = StatusEffects_active(this, e);
@@@ -1481,6 -1508,8 +1511,8 @@@ float GiveItems(entity e, float beginar
        PREGIVE_WEAPONS(e);
        PREGIVE_STATUSEFFECT(e, STATUSEFFECT_Strength);
        PREGIVE_STATUSEFFECT(e, STATUSEFFECT_Shield);
+       PREGIVE_STATUSEFFECT(e, STATUSEFFECT_Speed);
+       PREGIVE_STATUSEFFECT(e, STATUSEFFECT_Invisibility);
        //PREGIVE_STATUSEFFECT(e, STATUSEFFECT_Superweapons);
        PREGIVE_RESOURCE(e, RES_BULLETS);
        PREGIVE_RESOURCE(e, RES_CELLS);
                                continue;
                        case "ALL":
                                got += GiveBit(e, items, ITEM_JetpackRegen.m_itemid, op, val);
-                               got += GiveStatusEffect(e, STATUSEFFECT_Strength, op, val);
-                               got += GiveStatusEffect(e, STATUSEFFECT_Shield, op, val);
-                               got += GiveStatusEffect(e, STATUSEFFECT_Superweapons, op, val);
+                               FOREACH(StatusEffect, it.instanceOfPowerups, got += GiveStatusEffect(e, it, op, val));
                                got += GiveBit(e, items, IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS, op, val);
                        case "all":
                                got += GiveBit(e, items, ITEM_Jetpack.m_itemid, op, val);
                                got += GiveStatusEffect(e, STATUSEFFECT_Strength, op, val);
                                break;
                        case "invincible":
+                       case "shield":
                                got += GiveStatusEffect(e, STATUSEFFECT_Shield, op, val);
                                break;
+                       case "speed":
+                               got += GiveStatusEffect(e, STATUSEFFECT_Speed, op, val);
+                               break;
+                       case "invisibility":
+                               got += GiveStatusEffect(e, STATUSEFFECT_Invisibility, op, val);
+                               break;
                        case "superweapons":
                                got += GiveStatusEffect(e, STATUSEFFECT_Superweapons, op, val);
                                break;
                        if(STAT(WEAPONS, e) & (it.m_wepset))
                                it.wr_init(it);
        });
-       POSTGIVE_STATUSEFFECT(e, STATUSEFFECT_Strength, 1, SND_POWERUP, SND_POWEROFF);
-       POSTGIVE_STATUSEFFECT(e, STATUSEFFECT_Shield, 1, SND_POWERUP, SND_POWEROFF);
+       POSTGIVE_STATUSEFFECT(e, STATUSEFFECT_Strength, SND_POWERUP, SND_POWEROFF);
+       POSTGIVE_STATUSEFFECT(e, STATUSEFFECT_Shield, SND_POWERUP, SND_POWEROFF);
+       POSTGIVE_STATUSEFFECT(e, STATUSEFFECT_Speed, SND_POWERUP, SND_POWEROFF);
+       POSTGIVE_STATUSEFFECT(e, STATUSEFFECT_Invisibility, SND_POWERUP, SND_POWEROFF);
        POSTGIVE_RESOURCE(e, RES_BULLETS, 0, SND_ITEMPICKUP, SND_Null);
        POSTGIVE_RESOURCE(e, RES_CELLS, 0, SND_ITEMPICKUP, SND_Null);
        POSTGIVE_RESOURCE(e, RES_PLASMA, 0, SND_ITEMPICKUP, SND_Null);
index aaa5f93058b1c7919e7eb0ecfbda8ebcaf6de7c9,ff2d138437658ed1e941f2c06dc5f0b10eb15a33..7a472aa94cd7f9ccbec9ab96548ca71f508a1227
@@@ -4,7 -4,6 +4,6 @@@
  
  float autocvar_g_balance_superweapons_time;
  bool autocvar_g_fullbrightitems;
- int autocvar_g_powerups;
  float autocvar_g_items_mindist;
  float autocvar_g_items_maxdist;
  int autocvar_g_pickup_items;
@@@ -29,7 -28,6 +28,7 @@@ const float ITEM_RESPAWN_TICKS = 10
  
  .float max_armorvalue;
  .float pickup_anyway;
 +.int count;
  
  .float scheduledrespawntime;
  .float respawntime;
@@@ -110,11 -108,11 +109,11 @@@ spawnfunc(target_items)
  
  #define PREGIVE_WEAPONS(e) WepSet save_weapons; save_weapons = STAT(WEAPONS, e)
  #define PREGIVE(e,f) float save_##f; save_##f = (e).f
- #define PREGIVE_STATUSEFFECT(e,f) float save_##f = StatusEffects_gettime((f), (e))
+ #define PREGIVE_STATUSEFFECT(e,f) bool save_##f = StatusEffects_active(f, (e))
  #define PREGIVE_RESOURCE(e,f) float save_##f = GetResource((e), (f))
  #define POSTGIVE_WEAPON(e,b,snd_incr,snd_decr) GiveSound((e), !!(save_weapons & WepSet_FromWeapon(b)), !!(STAT(WEAPONS, e) & WepSet_FromWeapon(b)), 0, snd_incr, snd_decr)
  #define POSTGIVE_BIT(e,f,b,snd_incr,snd_decr) GiveSound((e), save_##f & (b), (e).f & (b), 0, snd_incr, snd_decr)
- #define POSTGIVE_STATUSEFFECT(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, StatusEffects_gettime((f), (e)), t, snd_incr, snd_decr)
+ #define POSTGIVE_STATUSEFFECT(e,f,snd_incr,snd_decr) GiveSound((e), save_##f, StatusEffects_active(f, (e)), 0, snd_incr, snd_decr)
  #define POSTGIVE_RESOURCE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, GetResource((e), (f)), t, snd_incr, snd_decr)
  #define POSTGIVE_RES_ROT(e,f,t,rotfield,rottime,regenfield,regentime,snd_incr,snd_decr) GiveRot((e),save_##f,GetResource((e),(f)),rotfield,rottime,regenfield,regentime);GiveSound((e),save_##f,GetResource((e),(f)),t,snd_incr,snd_decr)
  #define POSTGIVE_VALUE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr)
diff --combined qcsrc/server/world.qc
index c5032d9453f0d885492832807f9aa163b0801150,6c397d05353850515f0b2af7ce2870ef1a1875fb..02c059ea808c0d00ab4c3113c5fccb9e1249990a
@@@ -69,7 -69,7 +69,7 @@@ void PingPLReport_Think(entity this
        {
                WriteHeader(MSG_BROADCAST, TE_CSQC_PINGPLREPORT);
                WriteByte(MSG_BROADCAST, this.cnt);
-               WriteShort(MSG_BROADCAST, bound(1, CS(e).ping, 32767));
+               WriteShort(MSG_BROADCAST, bound(1, rint(CS(e).ping), 32767));
                WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_packetloss * 255), 255));
                WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_movementloss * 255), 255));
  
@@@ -353,6 -353,7 +353,7 @@@ void cvar_changes_init(
                BADCVAR("g_ctf_flag_glowtrails");
                BADCVAR("g_ctf_dynamiclights");
                BADCVAR("g_ctf_flag_pickup_verbosename");
+               BADCVAR("g_ctf_flagcarrier_auto_helpme_damage");
                BADPRESUFFIX("g_ctf_flag_", "_model");
                BADPRESUFFIX("g_ctf_flag_", "_skin");
                BADCVAR("g_domination_point_leadlimit");
                BADCVAR("sv_minigames");
                BADCVAR("sv_namechangetimer");
                BADCVAR("sv_precacheplayermodels");
+               BADCVAR("sv_qcphysics");
                BADCVAR("sv_radio");
                BADCVAR("sv_stepheight");
                BADCVAR("sv_timeout");
                BADCVAR("g_ctf_leaderboard");
                BADCVAR("g_domination_point_limit");
                BADCVAR("g_domination_teams_override");
+               BADCVAR("g_freezetag_revive_spawnshield");
                BADCVAR("g_freezetag_teams_override");
                BADCVAR("g_friendlyfire");
                BADCVAR("g_fullbrightitems");
                BADCVAR("g_player_brightness");
                BADCVAR("g_rocket_flying");
                BADCVAR("g_rocket_flying_disabledelays");
-               BADCVAR("g_spawnshieldtime");
+               BADPREFIX("g_spawnshield");
                BADCVAR("g_start_delay");
                BADCVAR("g_superspectate");
                BADCVAR("g_tdm_teams_override");
@@@ -838,7 -841,8 +841,8 @@@ spawnfunc(worldspawn
        // character set: ASCII 33-126 without the following characters: : ; ' " \ $
        if(autocvar_sv_eventlog)
        {
-               string s = sprintf("%s.%s.%06d", itos(autocvar_sv_eventlog_files_counter), strftime(false, "%s"), floor(random() * 1000000));
+               string num = strftime_s(); // strftime(false, "%s") isn't reliable, see strftime_s description
+               string s = sprintf("%s.%s.%06d", itos(autocvar_sv_eventlog_files_counter), num, floor(random() * 1000000));
                matchid = strzone(s);
  
                GameLogEcho(strcat(":gamestart:", GetGametype(), "_", GetMapname(), ":", s));
                if(autocvar_g_norecoil)
                        s = strcat(s, ":norecoil");
  
-               // TODO to mutator system
-               if(autocvar_g_powerups == 0)
-                       s = strcat(s, ":no_powerups");
-               if(autocvar_g_powerups > 0)
-                       s = strcat(s, ":powerups");
                GameLogEcho(s);
                GameLogEcho(":gameinfo:end");
        }
        MapInfo_Enumerate();
        MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1);
  
 -      if(fexists(strcat("scripts/", mapname, ".arena")))
 -              cvar_settemp("sv_q3acompat_machineshotgunswap", "1");
 -
 -      if(fexists(strcat("scripts/", mapname, ".defi")))
 -              cvar_settemp("sv_q3defragcompat", "1");
 +      q3compat = BITSET(q3compat, Q3COMPAT_ARENA, fexists(strcat("scripts/", mapname, ".arena")));
 +      q3compat = BITSET(q3compat, Q3COMPAT_DEFI, fexists(strcat("scripts/", mapname, ".defi")));
  
        if(whichpack(strcat("maps/", mapname, ".cfg")) != "")
        {
@@@ -2206,11 -2207,11 +2204,11 @@@ void droptofloor(entity this
  }
  
  bool autocvar_sv_gameplayfix_multiplethinksperframe = true;
- void RunThink(entity this)
+ void RunThink(entity this, float dt)
  {
        // don't let things stay in the past.
        // it is possible to start that way by a trigger with a local time.
-       if(this.nextthink <= 0 || this.nextthink > time + frametime)
+       if(this.nextthink <= 0 || this.nextthink > time + dt)
                return;
  
        float oldtime = time; // do we need to save this?
                // we don't want to loop in that case, so exit if the new nextthink is
                // <= the time the qc was told, also exit if it is past the end of the
                // frame
-               if(this.nextthink <= time || this.nextthink > oldtime + frametime || !autocvar_sv_gameplayfix_multiplethinksperframe)
+               if(this.nextthink <= time || this.nextthink > oldtime + dt || !autocvar_sv_gameplayfix_multiplethinksperframe)
                        break;
        }
  
@@@ -2256,8 -2257,8 +2254,8 @@@ void Physics_Frame(
                        if(it.move_movetype == MOVETYPE_PUSH || it.move_movetype == MOVETYPE_FAKEPUSH)
                                continue; // these movetypes have no regular think function
                        // handle thinking here
-                       if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + frametime)
-                               RunThink(it);
+                       if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + PHYS_INPUT_TIMELENGTH)
+                               RunThink(it, PHYS_INPUT_TIMELENGTH);
                }
        });
  
diff --combined xonotic-server.cfg
index 52cacc25c2388d8ec5ce745042572203c0bb91bb,09cb0e85672d579c1731b64f888628359ce471ee..61ba473dd024870b85b43ed2fd773be7b2f6bd66
@@@ -49,8 -49,6 +49,6 @@@ set sv_timeout_number 2 "how many timeo
  set sv_timeout_leadtime 4 "how long the players will be informed that a timeout was called before it starts, in seconds"
  set sv_timeout_resumetime 3 "how long the remaining timeout-time will be after a player called the timein command"
  
- set g_allow_oldvortexbeam 1 "If enabled, clients are allowed to use old v2.3 Vortex beam"
  set g_telefrags 1 "telefragging, i.e. killing someone who stands in the way of someone who is teleporting"
  set g_telefrags_teamplay 1 "never telefrag team mates"
  set g_telefrags_avoid 1 "when teleporters have a random destination, avoid teleporting to locations where a telefrag would happen"
@@@ -496,7 -494,8 +494,7 @@@ sv_gameplayfix_consistentplayerprethin
  sv_gameplayfix_gravityunaffectedbyticrate 1
  sv_gameplayfix_nogravityonground 1
  
 -set sv_q3acompat_machineshotgunswap 0 "shorthand for swapping machinegun and shotgun (for Q3A map compatibility in mapinfo files)"
 -set sv_q3defragcompat 0 "toggle for some compatibility hacks (for Q3DF map compatibility)"
 +set sv_q3compat_changehitbox 0 "use Q3 player hitbox dimensions and camera height on Q3 maps (maps with an entry in a .arena or .defi file)
  
  set g_movement_highspeed 1 "multiplier scale for movement speed (applies to sv_maxspeed and sv_maxairspeed, also applies to air acceleration when g_movement_highspeed_q3_compat is set to 0)"
  set g_movement_highspeed_q3_compat 0 "apply speed modifiers to air movement in a more Q3-compatible way (only apply speed buffs and g_movement_highspeed to max air speed, not to acceleration)"