]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/wepent_experimental
authorMario <mario@smbclan.net>
Tue, 3 Jan 2017 12:37:45 +0000 (22:37 +1000)
committerMario <mario@smbclan.net>
Tue, 3 Jan 2017 12:37:45 +0000 (22:37 +1000)
# Conflicts:
# qcsrc/common/mutators/mutator/overkill/sv_overkill.qc

1  2 
qcsrc/common/mutators/mutator/buffs/sv_buffs.qc
qcsrc/common/mutators/mutator/overkill/sv_overkill.qc
qcsrc/common/t_items.qc
qcsrc/common/weapons/weapon/crylink.qc
qcsrc/server/client.qc
qcsrc/server/mutators/events.qh
qcsrc/server/player.qc

index 36f34af53c39f49e441bf8a929980da7d42f72dc,a2d50b05975655cc4aced5d3295e8d9a3f2dc430..afd1ba7163325e0d45c45ed4b540ce0ae6c7689f
@@@ -6,7 -6,7 +6,7 @@@
  .float buff_time = _STAT(BUFF_TIME);
  void buffs_DelayedInit(entity this);
  
- AUTOCVAR(g_buffs, int, -1, _("Enable buffs, -1: enabled but no auto location or replacing powerups, 1: enabled and can replace them"));
+ AUTOCVAR(g_buffs, int, -1, "Enable buffs, -1: enabled but no auto location or replacing powerups, 1: enabled and can replace them");
  
  REGISTER_MUTATOR(buffs, autocvar_g_buffs)
  {
@@@ -853,14 -853,8 +853,14 @@@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreTh
        }
  
        if(player.buffs & BUFF_AMMO.m_itemid)
 -      if(player.clip_size)
 -              player.clip_load = player.(weapon_load[PS(player).m_switchweapon.m_id]) = player.clip_size;
 +      {
 +              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.buffs & BUFF_INVISIBLE.m_itemid) && (player.oldbuffs & BUFF_INVISIBLE.m_itemid))
        if(player.alpha != autocvar_g_buffs_invisible_alpha)
                        player.buff_ammo_prev_infitems = (player.items & IT_UNLIMITED_WEAPON_AMMO);
                        player.items |= IT_UNLIMITED_WEAPON_AMMO;
  
 -                      if(player.clip_load)
 -                              player.buff_ammo_prev_clipload = player.clip_load;
 -                      player.clip_load = player.(weapon_load[PS(player).m_switchweapon.m_id]) = player.clip_size;
 +                      if(player.buffs & BUFF_AMMO.m_itemid)
 +                      {
 +                              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)
                        else
                                player.items &= ~IT_UNLIMITED_WEAPON_AMMO;
  
 -                      if(player.buff_ammo_prev_clipload)
 -                              player.clip_load = player.buff_ammo_prev_clipload;
 +                      if(player.buffs & BUFF_AMMO.m_itemid)
 +                      {
 +                              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)
index ba2d98f49a5b011a8e30c664df333083e1b581c9,589765337e1f298e3d87371d43c008baefae0400..98233d2e9baad840be9ec51d734ed59b30fe73f5
@@@ -4,15 -4,24 +4,15 @@@
  #include "rpc.qh"
  
  bool autocvar_g_overkill_powerups_replace;
 -bool autocvar_g_overkill_ammo_charge;
 -float autocvar_g_overkill_ammo_charge_notice;
 -float autocvar_g_overkill_ammo_charge_limit;
  
  bool autocvar_g_overkill_filter_healthmega;
  bool autocvar_g_overkill_filter_armormedium;
  bool autocvar_g_overkill_filter_armorbig;
  bool autocvar_g_overkill_filter_armormega;
  
 -.float ok_lastwep;
  .float ok_item;
  
 -.float ok_notice_time;
 -.float ammo_charge[Weapons_MAX];
 -.float ok_use_ammocharge = _STAT(OK_AMMO_CHARGE);
 -.float ok_ammo_charge = _STAT(OK_AMMO_CHARGEPOOL);
 -
 -void(entity ent, float wep) ok_DecreaseCharge;
 +.Weapon ok_lastwep[MAX_WEAPONSLOTS];
  
  void ok_Initialize();
  
@@@ -30,10 -39,59 +30,10 @@@ REGISTER_MUTATOR(ok, cvar("g_overkill"
        }
  }
  
 -MUTATOR_HOOKFUNCTION(ok, W_DecreaseAmmo)
 -{
 -      entity actor = M_ARGV(0, entity);
 -      if (actor.ok_use_ammocharge)
 -      {
 -              ok_DecreaseCharge(actor, PS(actor).m_weapon.m_id);
 -              return true;
 -      }
 -}
 -
 -MUTATOR_HOOKFUNCTION(ok, W_Reload)
 -{
 -      entity actor = M_ARGV(0, entity);
 -      return actor.ok_use_ammocharge;
 -}
 -
  void W_Blaster_Attack(entity, .entity, float, float, float, float, float, float, float, float, float, float);
  spawnfunc(weapon_hmg);
  spawnfunc(weapon_rpc);
  
 -void ok_DecreaseCharge(entity ent, int wep)
 -{
 -      if(!ent.ok_use_ammocharge) return;
 -
 -      entity wepent = Weapons_from(wep);
 -
 -      if (wepent == WEP_Null) return;  // dummy
 -
 -      ent.ammo_charge[wep] -= max(0, cvar(sprintf("g_overkill_ammo_decharge_%s", wepent.netname)));
 -}
 -
 -void ok_IncreaseCharge(entity ent, int wep)
 -{
 -      entity wepent = Weapons_from(wep);
 -
 -      if (wepent == WEP_Null) return;  // dummy
 -
 -      if(ent.ok_use_ammocharge)
 -      if(!PHYS_INPUT_BUTTON_ATCK(ent)) // not while attacking?
 -              ent.ammo_charge[wep] = min(autocvar_g_overkill_ammo_charge_limit, ent.ammo_charge[wep] + cvar(sprintf("g_overkill_ammo_charge_rate_%s", wepent.netname)) * frametime / W_TICSPERFRAME);
 -}
 -
 -float ok_CheckWeaponCharge(entity ent, int wep)
 -{
 -      if(!ent.ok_use_ammocharge) return true;
 -
 -      entity wepent = Weapons_from(wep);
 -
 -      if(wepent == WEP_Null) return false;  // dummy
 -
 -      return (ent.ammo_charge[wep] >= cvar(sprintf("g_overkill_ammo_decharge_%s", wepent.netname)));
 -}
 -
  MUTATOR_HOOKFUNCTION(ok, Damage_Calculate, CBC_ORDER_LAST)
  {
        entity frag_attacker = M_ARGV(1, entity);
@@@ -82,12 -140,7 +82,12 @@@ MUTATOR_HOOKFUNCTION(ok, PlayerDies
  
        ok_DropItem(frag_target, targ);
  
 -      frag_target.ok_lastwep = PS(frag_target).m_switchweapon.m_id;
 +      for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +      {
 +              .entity weaponentity = weaponentities[slot];
 +
 +              frag_target.ok_lastwep[slot] = frag_target.(weaponentity).m_switchweapon;
 +      }
  }
  
  MUTATOR_HOOKFUNCTION(ok, MonsterDropItem)
@@@ -118,23 -171,8 +118,6 @@@ MUTATOR_HOOKFUNCTION(ok, PlayerPreThink
        if(IS_DEAD(player) || !IS_PLAYER(player) || STAT(FROZEN, player))
                return;
  
-       for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
-       {
-               .entity weaponentity = weaponentities[slot];
-               entity thiswep = player.(weaponentity);
-               if(player.ok_lastwep[slot] && player.ok_lastwep[slot] != WEP_Null)
-               {
-                       Weapon newwep = player.ok_lastwep[slot];
-                       if(player.ok_lastwep[slot] == WEP_HMG)
-                               newwep = WEP_MACHINEGUN;
-                       if(player.ok_lastwep[slot] == WEP_RPC)
-                               newwep = WEP_VORTEX;
-                       thiswep.m_switchweapon = newwep;
-                       player.ok_lastwep[slot] = WEP_Null;
-               }
-       }
 -      ok_IncreaseCharge(player, PS(player).m_weapon.m_id);
--
        if(PHYS_INPUT_BUTTON_ATCK2(player))
        if( !forbidWeaponUse(player) || player.weapon_blocked // allow if weapon is blocked
                || (round_handler_IsActive() && !round_handler_IsRoundStarted()) )
                player.jump_interval = time + WEP_CVAR_PRI(blaster, refire) * W_WeaponRateFactor(player);
                makevectors(player.v_angle);
  
 -              Weapon oldwep = PS(player).m_weapon;
 -              PS(player).m_weapon = WEP_BLASTER;
 -              W_Blaster_Attack(
 -                      player,
 -                      weaponentities[0], // TODO: unhardcode
 -                      WEP_BLASTER.m_id | HITTYPE_SECONDARY,
 -                      WEP_CVAR_SEC(vaporizer, shotangle),
 -                      WEP_CVAR_SEC(vaporizer, damage),
 -                      WEP_CVAR_SEC(vaporizer, edgedamage),
 -                      WEP_CVAR_SEC(vaporizer, radius),
 -                      WEP_CVAR_SEC(vaporizer, force),
 -                      WEP_CVAR_SEC(vaporizer, speed),
 -                      WEP_CVAR_SEC(vaporizer, spread),
 -                      WEP_CVAR_SEC(vaporizer, delay),
 -                      WEP_CVAR_SEC(vaporizer, lifetime)
 -              );
 -              PS(player).m_weapon = oldwep;
 -      }
 -
 -      player.weapon_blocked = false;
 -
 -      player.ok_ammo_charge = player.ammo_charge[PS(player).m_weapon.m_id];
 -
 -      if(player.ok_use_ammocharge)
 -      if(!ok_CheckWeaponCharge(player, PS(player).m_weapon.m_id))
 -      {
 -              if(autocvar_g_overkill_ammo_charge_notice && time > player.ok_notice_time && PHYS_INPUT_BUTTON_ATCK(player) && IS_REAL_CLIENT(player) && PS(player).m_weapon == PS(player).m_switchweapon)
 +              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                {
 -                      //Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_OVERKILL_CHARGE);
 -                      player.ok_notice_time = time + 2;
 -                      play2(player, SND(DRYFIRE));
 +                      .entity weaponentity = weaponentities[slot];
 +
 +                      if(player.(weaponentity).m_weapon == WEP_Null && slot != 0)
 +                              continue;
 +
 +                      Weapon oldwep = player.(weaponentity).m_weapon;
 +                      player.(weaponentity).m_weapon = WEP_BLASTER;
 +                      W_Blaster_Attack(
 +                              player,
 +                              weaponentity,
 +                              WEP_BLASTER.m_id | HITTYPE_SECONDARY,
 +                              WEP_CVAR_SEC(vaporizer, shotangle),
 +                              WEP_CVAR_SEC(vaporizer, damage),
 +                              WEP_CVAR_SEC(vaporizer, edgedamage),
 +                              WEP_CVAR_SEC(vaporizer, radius),
 +                              WEP_CVAR_SEC(vaporizer, force),
 +                              WEP_CVAR_SEC(vaporizer, speed),
 +                              WEP_CVAR_SEC(vaporizer, spread),
 +                              WEP_CVAR_SEC(vaporizer, delay),
 +                              WEP_CVAR_SEC(vaporizer, lifetime)
 +                      );
 +                      player.(weaponentity).m_weapon = oldwep;
                }
 -              Weapon wpn = PS(player).m_weapon;
 -              .entity weaponentity = weaponentities[0]; // TODO: unhardcode
 -              if(player.(weaponentity).state != WS_CLEAR)
 -                      w_ready(wpn, player, weaponentity, PHYS_INPUT_BUTTON_ATCK(player) | (PHYS_INPUT_BUTTON_ATCK2(player) << 1));
 -
 -              player.weapon_blocked = true;
        }
  
        PHYS_INPUT_BUTTON_ATCK2(player) = false;
  }
  
--MUTATOR_HOOKFUNCTION(ok, PlayerSpawn)
 -{
 -      entity player = M_ARGV(0, entity);
 -
 -      if(autocvar_g_overkill_ammo_charge)
 -      {
 -              FOREACH(Weapons, it != WEP_Null, LAMBDA(player.ammo_charge[it.m_id] = autocvar_g_overkill_ammo_charge_limit));
 -
 -              player.ok_use_ammocharge = 1;
 -              player.ok_notice_time = time;
 -      }
 -      else
 -              player.ok_use_ammocharge = 0;
 -}
 -
+ MUTATOR_HOOKFUNCTION(ok, PlayerWeaponSelect)
  {
        entity player = M_ARGV(0, entity);
  
-       // if player changed their weapon while dead, don't switch to their death weapon
-       if(player.impulse)
 -      if(player.ok_lastwep)
++      for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
        {
-               for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 -              Weapon newwep = Weapons_from(player.ok_lastwep);
 -              if(player.ok_lastwep == WEP_HMG.m_id)
 -                      newwep = WEP_MACHINEGUN;
 -              if(player.ok_lastwep == WEP_RPC.m_id)
 -                      newwep = WEP_VORTEX;
 -              PS(player).m_switchweapon = newwep;
 -              player.ok_lastwep = 0;
++              .entity weaponentity = weaponentities[slot];
++              entity thiswep = player.(weaponentity);
++
++              if(player.ok_lastwep[slot] && player.ok_lastwep[slot] != WEP_Null)
 +              {
++                      Weapon newwep = player.ok_lastwep[slot];
++                      if(player.ok_lastwep[slot] == WEP_HMG)
++                              newwep = WEP_MACHINEGUN;
++                      if(player.ok_lastwep[slot] == WEP_RPC)
++                              newwep = WEP_VORTEX;
++                      thiswep.m_switchweapon = newwep;
 +                      player.ok_lastwep[slot] = WEP_Null;
 +              }
        }
  }
  
@@@ -250,6 -318,15 +241,6 @@@ MUTATOR_HOOKFUNCTION(ok, FilterItem
        return true;
  }
  
 -MUTATOR_HOOKFUNCTION(ok, SpectateCopy)
 -{
 -      entity spectatee = M_ARGV(0, entity);
 -      entity client = M_ARGV(1, entity);
 -
 -      client.ammo_charge[PS(client).m_weapon.m_id] = spectatee.ammo_charge[PS(spectatee).m_weapon.m_id];
 -      client.ok_use_ammocharge = spectatee.ok_use_ammocharge;
 -}
 -
  MUTATOR_HOOKFUNCTION(ok, SetStartItems, CBC_ORDER_LAST)
  {
        WepSet ok_start_items = (WEPSET(MACHINEGUN) | WEPSET(VORTEX) | WEPSET(SHOTGUN));
diff --combined qcsrc/common/t_items.qc
index 116fbfcd5a4768f4d4eb4c03f2f681cece9a1241,cacc1e4f511e87ba71f3e91dd83889c0194866af..a48bb7c8c8f31c1141d085aeb43a823684dd322a
@@@ -673,30 -673,21 +673,30 @@@ LABEL(YEAH
  
  float Item_GiveTo(entity item, entity player)
  {
 -      float _switchweapon;
        float pickedup;
  
        // if nothing happens to player, just return without taking the item
        pickedup = false;
 -      _switchweapon = false;
 +      int _switchweapon = 0;
        // in case the player has autoswitch enabled do the following:
        // if the player is using their best weapon before items are given, they
        // probably want to switch to an even better weapon after items are given
 -      if (player.autoswitch)
 -      if (PS(player).m_switchweapon == w_getbestweapon(player))
 -              _switchweapon = true;
  
 -      if (!(player.weapons & WepSet_FromWeapon(PS(player).m_switchweapon)))
 -              _switchweapon = true;
 +      if(player.autoswitch)
 +      {
 +              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +              {
 +                      .entity weaponentity = weaponentities[slot];
 +                      if(player.(weaponentity).m_weapon != WEP_Null || slot == 0)
 +                      {
 +                              if(player.(weaponentity).m_switchweapon == w_getbestweapon(player, weaponentity))
 +                                      _switchweapon |= BIT(slot);
 +
 +                              if(!(player.weapons & WepSet_FromWeapon(player.(weaponentity).m_switchweapon)))
 +                                      _switchweapon |= BIT(slot);
 +                      }
 +              }
 +      }
  
        pickedup |= Item_GiveAmmoTo(item, player, ammo_fuel, g_pickup_fuel_max, ITEM_MODE_FUEL);
        pickedup |= Item_GiveAmmoTo(item, player, ammo_shells, g_pickup_shells_max, ITEM_MODE_NONE);
                        FOREACH(Weapons, it != WEP_Null, {
                                if(w & (it.m_wepset))
                                {
 -                                      W_DropEvent(wr_pickup, player, it.m_id, item);
 +                                      for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +                                      {
 +                                              .entity weaponentity = weaponentities[slot];
 +                                              if(player.(weaponentity).m_weapon != WEP_Null || slot == 0)
 +                                                      W_DropEvent(wr_pickup, player, it.m_id, item, weaponentity);
 +                                      }
                                        W_GiveWeapon(player, it.m_id);
                                }
                        });
@@@ -775,25 -761,13 +775,25 @@@ LABEL(skip
        // crude hack to enforce switching weapons
        if(g_cts && item.itemdef.instanceOfWeaponPickup)
        {
 -              W_SwitchWeapon_Force(player, Weapons_from(item.weapon));
 +              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +              {
 +                      .entity weaponentity = weaponentities[slot];
 +                      if(player.(weaponentity).m_weapon != WEP_Null || slot == 0)
 +                              W_SwitchWeapon_Force(player, Weapons_from(item.weapon), weaponentity);
 +              }
                return 1;
        }
  
 -      if (_switchweapon)
 -              if (PS(player).m_switchweapon != w_getbestweapon(player))
 -                      W_SwitchWeapon_Force(player, w_getbestweapon(player));
 +      if(_switchweapon)
 +      {
 +              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +              {
 +                      .entity weaponentity = weaponentities[slot];
 +                      if(_switchweapon & BIT(slot))
 +                      if(player.(weaponentity).m_switchweapon != w_getbestweapon(player, weaponentity))
 +                              W_SwitchWeapon_Force(player, w_getbestweapon(player, weaponentity), weaponentity);
 +              }
 +      }
  
        return 1;
  }
@@@ -1084,6 -1058,12 +1084,12 @@@ void Item_Damage(entity this, entity in
                RemoveItem(this);
  }
  
+ void item_use(entity this, entity actor, entity trigger)
+ {
+       // use the touch function to handle collection
+       gettouch(this)(this, actor);
+ }
  void _StartItem(entity this, entity def, float defaultrespawntime, float defaultrespawntimejitter)
  {
        string itemname = def.m_name;
                }
                */
  
+               if(this.targetname != "" && (this.spawnflags & 16))
+                       this.use = item_use;
                if(autocvar_spawn_debug >= 2)
                {
              // why not flags & fl_item?
@@@ -1693,6 -1676,7 +1702,6 @@@ void GiveRot(entity e, float v0, float 
  float GiveItems(entity e, float beginarg, float endarg)
  {
        float got, i, val, op;
 -      float _switchweapon;
        string cmd;
  
        val = 999;
  
        got = 0;
  
 -      _switchweapon = false;
 -      if (e.autoswitch)
 -              if (PS(e).m_switchweapon == w_getbestweapon(e))
 -                      _switchweapon = true;
 +      int _switchweapon = 0;
 +
 +      if(e.autoswitch)
 +      {
 +              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +              {
 +                      .entity weaponentity = weaponentities[slot];
 +                      if(e.(weaponentity).m_weapon != WEP_Null || slot == 0)
 +                      if(e.(weaponentity).m_switchweapon == w_getbestweapon(e, weaponentity))
 +                              _switchweapon |= BIT(slot);
 +              }
 +      }
  
        e.strength_finished = max(0, e.strength_finished - time);
        e.invincible_finished = max(0, e.invincible_finished - time);
        else
                e.superweapons_finished += time;
  
 -      if (!(e.weapons & WepSet_FromWeapon(PS(e).m_switchweapon)))
 -              _switchweapon = true;
 +      for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +      {
 +              .entity weaponentity = weaponentities[slot];
 +              if(e.(weaponentity).m_weapon != WEP_Null || slot == 0)
 +              if(!(e.weapons & WepSet_FromWeapon(e.(weaponentity).m_switchweapon)))
 +                      _switchweapon |= BIT(slot);
 +      }
 +
        if(_switchweapon)
 -              W_SwitchWeapon_Force(e, w_getbestweapon(e));
 +      {
 +              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +              {
 +                      .entity weaponentity = weaponentities[slot];
 +                      if(_switchweapon & BIT(slot))
 +                              W_SwitchWeapon_Force(e, w_getbestweapon(e, weaponentity), weaponentity);
 +              }
 +      }
  
        return got;
  }
index e4d66ebe603a7598f2ef3151a750537e8b5fef57,1af08bc12c7ded2e48b5111d19b7b66291bd7f15..dbcddf1ad0d8327aadebdc3003326897fba16abb
@@@ -3,7 -3,7 +3,7 @@@
  CLASS(Crylink, Weapon)
  /* ammotype  */ ATTRIB(Crylink, ammo_field, .int, ammo_cells);
  /* impulse   */ ATTRIB(Crylink, impulse, int, 6);
- /* flags     */ ATTRIB(Crylink, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH);
+ /* flags     */ ATTRIB(Crylink, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH | WEP_FLAG_CANCLIMB);
  /* rating    */ ATTRIB(Crylink, bot_pickupbasevalue, float, BOT_PICKUP_RATING_MID);
  /* color     */ ATTRIB(Crylink, wpcolor, vector, '1 0.5 1');
  /* modelname */ ATTRIB(Crylink, mdl, string, "crylink");
@@@ -98,9 -98,8 +98,9 @@@ void W_Crylink_CheckLinks(entity e
  void W_Crylink_Dequeue_Raw(entity own, entity prev, entity me, entity next)
  {
        W_Crylink_CheckLinks(next);
 -      if(me == own.crylink_lastgroup)
 -              own.crylink_lastgroup = ((me == next) ? NULL : next);
 +      .entity weaponentity = me.weaponentity_fld;
 +      if(me == own.(weaponentity).crylink_lastgroup)
 +              own.(weaponentity).crylink_lastgroup = ((me == next) ? NULL : next);
        prev.queuenext = next;
        next.queueprev = prev;
        me.classname = "spike_oktoremove";
@@@ -129,9 -128,8 +129,9 @@@ void W_Crylink_LinkExplode(entity e, en
  
        a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1);
  
 -      if(e == e.realowner.crylink_lastgroup)
 -              e.realowner.crylink_lastgroup = NULL;
 +      .entity weaponentity = e.weaponentity_fld;
 +      if(e == e.realowner.(weaponentity).crylink_lastgroup)
 +              e.realowner.(weaponentity).crylink_lastgroup = NULL;
  
        float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
  
@@@ -237,8 -235,7 +237,8 @@@ void W_Crylink_LinkJoinEffect_Think(ent
        // is there at least 2 projectiles very close?
        entity e, p;
        float n;
 -      e = this.owner.crylink_lastgroup;
 +      .entity weaponentity = this.weaponentity_fld;
 +      e = this.owner.(weaponentity).crylink_lastgroup;
        n = 0;
        if(e)
        {
@@@ -320,9 -317,8 +320,9 @@@ void W_Crylink_Touch(entity this, entit
  
        if(totaldamage && ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 2) || ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 1) && !W_Crylink_Touch_WouldHitFriendly(this, WEP_CVAR_BOTH(crylink, isprimary, radius)))))
        {
 -              if(this == this.realowner.crylink_lastgroup)
 -                      this.realowner.crylink_lastgroup = NULL;
 +              .entity weaponentity = this.weaponentity_fld;
 +              if(this == this.realowner.(weaponentity).crylink_lastgroup)
 +                      this.realowner.(weaponentity).crylink_lastgroup = NULL;
                W_Crylink_LinkExplode(this.queuenext, this, toucher);
                this.classname = "spike_oktoremove";
                delete(this);
@@@ -358,7 -354,7 +358,7 @@@ void W_Crylink_Attack(Weapon thiswep, e
        vector forward, right, up;
        float maxdmg;
  
 -      W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(crylink, ammo));
 +      W_DecreaseAmmo(thiswep, actor, WEP_CVAR_PRI(crylink, ammo), weaponentity);
  
        maxdmg = WEP_CVAR_PRI(crylink, damage) * WEP_CVAR_PRI(crylink, shots);
        maxdmg *= 1 + WEP_CVAR_PRI(crylink, bouncedamagefactor) * WEP_CVAR_PRI(crylink, bounces);
                proj = new(spike);
                proj.reset = W_Crylink_Reset;
                proj.realowner = proj.owner = actor;
 +              proj.weaponentity_fld = weaponentity;
                proj.bot_dodge = true;
                proj.bot_dodgerating = WEP_CVAR_PRI(crylink, damage);
                if(shots == 1) {
        }
        if(WEP_CVAR_PRI(crylink, joinspread) != 0)
        {
 -              actor.crylink_lastgroup = proj;
 +              actor.(weaponentity).crylink_lastgroup = proj;
                W_Crylink_CheckLinks(proj);
 -              actor.crylink_waitrelease = 1;
 +              actor.(weaponentity).crylink_waitrelease = 1;
        }
  }
  
@@@ -469,7 -464,7 +469,7 @@@ void W_Crylink_Attack2(Weapon thiswep, 
        vector forward, right, up;
        float maxdmg;
  
 -      W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(crylink, ammo));
 +      W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(crylink, ammo), weaponentity);
  
        maxdmg = WEP_CVAR_SEC(crylink, damage) * WEP_CVAR_SEC(crylink, shots);
        maxdmg *= 1 + WEP_CVAR_SEC(crylink, bouncedamagefactor) * WEP_CVAR_SEC(crylink, bounces);
        for(counter = 0; counter < shots; ++counter)
        {
                proj = new(spike);
 +              proj.weaponentity_fld = weaponentity;
                proj.reset = W_Crylink_Reset;
                proj.realowner = proj.owner = actor;
                proj.bot_dodge = true;
        }
        if(WEP_CVAR_SEC(crylink, joinspread) != 0)
        {
 -              actor.crylink_lastgroup = proj;
 +              actor.(weaponentity).crylink_lastgroup = proj;
                W_Crylink_CheckLinks(proj);
 -              actor.crylink_waitrelease = 2;
 +              actor.(weaponentity).crylink_waitrelease = 2;
        }
  }
  
 -METHOD(Crylink, wr_aim, void(entity thiswep, entity actor))
 +METHOD(Crylink, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
  {
      if(random() < 0.10)
 -        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false);
 +        PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false);
      else
 -        PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false);
 +        PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false);
  }
  METHOD(Crylink, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
  {
 -    if(autocvar_g_balance_crylink_reload_ammo && actor.clip_load < min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo))) { // forced reload
 +    if(autocvar_g_balance_crylink_reload_ammo && actor.(weaponentity).clip_load < min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo))) { // forced reload
          thiswep.wr_reload(thiswep, actor, weaponentity);
      }
  
      if(fire & 1)
      {
 -        if(actor.crylink_waitrelease != 1)
 +        if(actor.(weaponentity).crylink_waitrelease != 1)
          if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(crylink, refire)))
          {
              W_Crylink_Attack(thiswep, actor, weaponentity);
  
      if((fire & 2) && autocvar_g_balance_crylink_secondary)
      {
 -        if(actor.crylink_waitrelease != 2)
 +        if(actor.(weaponentity).crylink_waitrelease != 2)
          if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(crylink, refire)))
          {
              W_Crylink_Attack2(thiswep, actor, weaponentity);
          }
      }
  
 -    if((actor.crylink_waitrelease == 1 && !(fire & 1)) || (actor.crylink_waitrelease == 2 && !(fire & 2)))
 +    if((actor.(weaponentity).crylink_waitrelease == 1 && !(fire & 1)) || (actor.(weaponentity).crylink_waitrelease == 2 && !(fire & 2)))
      {
 -        if(!actor.crylink_lastgroup || time > actor.crylink_lastgroup.teleport_time)
 +        if(!actor.(weaponentity).crylink_lastgroup || time > actor.(weaponentity).crylink_lastgroup.teleport_time)
          {
              // fired and released now!
 -            if(actor.crylink_lastgroup)
 +            if(actor.(weaponentity).crylink_lastgroup)
              {
                  vector pos;
                  entity linkjoineffect;
 -                float isprimary = (actor.crylink_waitrelease == 1);
 +                float isprimary = (actor.(weaponentity).crylink_waitrelease == 1);
  
 -                pos = W_Crylink_LinkJoin(actor.crylink_lastgroup, WEP_CVAR_BOTH(crylink, isprimary, joinspread) * WEP_CVAR_BOTH(crylink, isprimary, speed));
 +                pos = W_Crylink_LinkJoin(actor.(weaponentity).crylink_lastgroup, WEP_CVAR_BOTH(crylink, isprimary, joinspread) * WEP_CVAR_BOTH(crylink, isprimary, speed));
  
                  linkjoineffect = new(linkjoineffect);
 +                linkjoineffect.weaponentity_fld = weaponentity;
                  setthink(linkjoineffect, W_Crylink_LinkJoinEffect_Think);
                  linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
                  linkjoineffect.owner = actor;
                  setorigin(linkjoineffect, pos);
              }
 -            actor.crylink_waitrelease = 0;
 -            if(!thiswep.wr_checkammo1(thiswep, actor) && !thiswep.wr_checkammo2(thiswep, actor))
 +            actor.(weaponentity).crylink_waitrelease = 0;
 +            if(!thiswep.wr_checkammo1(thiswep, actor, weaponentity) && !thiswep.wr_checkammo2(thiswep, actor, weaponentity))
              if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
              {
                  // ran out of ammo!
                  actor.cnt = WEP_CRYLINK.m_id;
 -                PS(actor).m_switchweapon = w_getbestweapon(actor);
 +                actor.(weaponentity).m_switchweapon = w_getbestweapon(actor, weaponentity);
              }
          }
      }
  }
 -METHOD(Crylink, wr_checkammo1, bool(entity thiswep, entity actor))
 +METHOD(Crylink, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
  {
      // don't "run out of ammo" and switch weapons while waiting for release
 -    if(actor.crylink_lastgroup && actor.crylink_waitrelease)
 +    if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
          return true;
  
      float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_PRI(crylink, ammo);
 -    ammo_amount += actor.(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
 +    ammo_amount += actor.(weaponentity).(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_PRI(crylink, ammo);
      return ammo_amount;
  }
 -METHOD(Crylink, wr_checkammo2, bool(entity thiswep, entity actor))
 +METHOD(Crylink, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
  {
      // don't "run out of ammo" and switch weapons while waiting for release
 -    if(actor.crylink_lastgroup && actor.crylink_waitrelease)
 +    if(actor.(weaponentity).crylink_lastgroup && actor.(weaponentity).crylink_waitrelease)
          return true;
  
      float ammo_amount = actor.(thiswep.ammo_field) >= WEP_CVAR_SEC(crylink, ammo);
 -    ammo_amount += actor.(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
 +    ammo_amount += actor.(weaponentity).(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_SEC(crylink, ammo);
      return ammo_amount;
  }
  METHOD(Crylink, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
diff --combined qcsrc/server/client.qc
index 3b0da8234043f96fbe3b7c21c1949136abe2b578,c1cce0c0a6b310f5fe77a14e0d152fa9a5c3b7c0..47064506f3f55a937240dc308e7fe006632229c4
@@@ -23,7 -23,6 +23,7 @@@
  #include "bot/api.qh"
  
  #include "../common/ent_cs.qh"
 +#include "../common/wepent.qh"
  #include <common/state.qh>
  
  #include <common/effects/qc/globalsound.qh>
@@@ -113,6 -112,7 +113,6 @@@ bool ClientData_Send(entity this, entit
        if (e.race_completed)       sf |= 1; // forced scoreboard
        if (to.spectatee_status)    sf |= 2; // spectator ent number follows
        if (e.zoomstate)            sf |= 4; // zoomed
 -      if (e.porto_v_angle_held)   sf |= 8; // angles held
        if (autocvar_sv_showspectators) sf |= 16; // show spectators
  
        WriteHeader(MSG_ENTITY, ENT_CLIENT_CLIENTDATA);
        {
                WriteByte(MSG_ENTITY, to.spectatee_status);
        }
 -      if (sf & 8)
 -      {
 -              WriteAngle(MSG_ENTITY, e.v_angle.x);
 -              WriteAngle(MSG_ENTITY, e.v_angle.y);
 -      }
  
        if(sf & 16)
        {
@@@ -252,7 -257,7 +252,7 @@@ void PutObserverInServer(entity this
          this.view_ofs = '0 0 0';
      }
  
 -    RemoveGrapplingHook(this);
 +    RemoveGrapplingHooks(this);
        Portal_ClearAll(this);
        Unfreeze(this);
        SetSpectatee(this, NULL);
        this.istypefrag = 0;
        setthink(this, func_null);
        this.nextthink = 0;
 -      this.hook_time = 0;
        this.deadflag = DEAD_NO;
        this.crouch = false;
        this.revive_progress = 0;
        this.weapons = '0 0 0';
        this.drawonlytoclient = this;
  
 -      this.weaponname = "";
        this.weaponmodel = "";
        for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
        {
        this.oldvelocity = this.velocity;
        this.fire_endtime = -1;
        this.event_damage = func_null;
 -
 -      STAT(ACTIVEWEAPON, this) = WEP_Null.m_id;
 -      STAT(SWITCHINGWEAPON, this) = WEP_Null.m_id;
 -      STAT(SWITCHWEAPON, this) = WEP_Null.m_id;
  }
  
  int player_getspecies(entity this)
@@@ -653,8 -664,7 +653,8 @@@ void PutClientInServer(entity this
  
                for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                {
 -                      CL_SpawnWeaponentity(this, weaponentities[slot]);
 +                      .entity weaponentity = weaponentities[slot];
 +                      CL_SpawnWeaponentity(this, weaponentity);
                }
                this.alpha = default_player_alpha;
                this.colormod = '1 1 1' * autocvar_g_player_brightness;
                        it.wr_resetplayer(it, this);
                        // reload all reloadable weapons
                        if (it.spawnflags & WEP_FLAG_RELOADABLE) {
 -                              this.weapon_load[it.m_id] = it.reloading_ammo;
 +                              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +                              {
 +                                      .entity weaponentity = weaponentities[slot];
 +                                      this.(weaponentity).weapon_load[it.m_id] = it.reloading_ammo;
 +                              }
                        }
                ));
  
                        delete(spot); // usefull for checking if there are spawnpoints, that let drop through the floor
                }
  
 -              PS(this).m_switchweapon = w_getbestweapon(this);
 -              this.cnt = -1; // W_LastWeapon will not complain
 -              PS(this).m_weapon = WEP_Null;
 -              this.weaponname = "";
 -              PS(this).m_switchingweapon = WEP_Null;
 +              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +              {
 +                      .entity weaponentity = weaponentities[slot];
 +                      if(slot == 0)
 +                              this.(weaponentity).m_switchweapon = w_getbestweapon(this, weaponentity);
 +                      else
 +                              this.(weaponentity).m_switchweapon = WEP_Null;
 +                      this.(weaponentity).m_weapon = WEP_Null;
 +                      this.(weaponentity).weaponname = "";
 +                      this.(weaponentity).m_switchingweapon = WEP_Null;
 +                      this.(weaponentity).cnt = -1;
 +              }
  
+               MUTATOR_CALLHOOK(PlayerWeaponSelect, this);
                if (!warmup_stage && !this.alivetime)
                        this.alivetime = time;
  
@@@ -1262,7 -1263,7 +1264,7 @@@ void ClientDisconnect(entity this
  
        Unfreeze(this);
  
 -      RemoveGrapplingHook(this);
 +      RemoveGrapplingHooks(this);
  
        // Here, everything has been done that requires this player to be a client.
  
@@@ -1693,12 -1694,6 +1695,12 @@@ void SpectateCopy(entity this, entity s
        setsize(this, spectatee.mins, spectatee.maxs);
        SetZoomState(this, spectatee.zoomstate);
  
 +      for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +      {
 +              .entity weaponentity = weaponentities[slot];
 +              this.(weaponentity) = spectatee.(weaponentity);
 +      }
 +
      anticheat_spectatecopy(this, spectatee);
        this.hud = spectatee.hud;
        if(spectatee.vehicle)
@@@ -2414,18 -2409,8 +2416,18 @@@ void PlayerPreThink (entity this
  
                this.prevorigin = this.origin;
  
 +              bool have_hook = false;
 +              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +              {
 +                      .entity weaponentity = weaponentities[slot];
 +                      if(this.(weaponentity).hook.state)
 +                      {
 +                              have_hook = true;
 +                              break;
 +                      }
 +              }
                bool do_crouch = PHYS_INPUT_BUTTON_CROUCH(this);
 -              if (this.hook.state) {
 +              if (have_hook) {
                        do_crouch = false;
                } else if (this.waterlevel >= WATERLEVEL_SWIMMING) {
                        do_crouch = false;
                {
                        this.items &= ~this.items_added;
  
 -                      //for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 -                      //{
 -                              //.entity weaponentity = weaponentities[slot];
 -                              //W_WeaponFrame(this, weaponentity);
 -                      //}
 -                      .entity weaponentity = weaponentities[0]; // TODO
 -                      W_WeaponFrame(this, weaponentity);
 +                      for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +                      {
 +                              .entity weaponentity = weaponentities[slot];
 +                              W_WeaponFrame(this, weaponentity);
 +
 +                              if(slot == 0)
 +                              {
 +                                      this.clip_load = this.(weaponentity).clip_load;
 +                                      this.clip_size = this.(weaponentity).clip_size;
 +                              }
 +                      }
  
                        this.items_added = 0;
                        if (this.items & ITEM_Jetpack.m_itemid && (this.items & ITEM_JetpackRegen.m_itemid || this.ammo_fuel >= 0.01))
  
                // WEAPONTODO: Add a weapon request for this
                // rot vortex charge to the charge limit
 -              if (WEP_CVAR(vortex, charge_rot_rate) && this.vortex_charge > WEP_CVAR(vortex, charge_limit) && this.vortex_charge_rottime < time)
 -                      this.vortex_charge = bound(WEP_CVAR(vortex, charge_limit), this.vortex_charge - WEP_CVAR(vortex, charge_rot_rate) * frametime / W_TICSPERFRAME, 1);
 +              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +              {
 +                      .entity weaponentity = weaponentities[slot];
 +                      if (WEP_CVAR(vortex, charge_rot_rate) && this.(weaponentity).vortex_charge > WEP_CVAR(vortex, charge_limit) && this.(weaponentity).vortex_charge_rottime < time)
 +                              this.(weaponentity).vortex_charge = bound(WEP_CVAR(vortex, charge_limit), this.(weaponentity).vortex_charge - WEP_CVAR(vortex, charge_rot_rate) * frametime / W_TICSPERFRAME, 1);
 +              }
  
                if (frametime) player_anim(this);
  
  
        // WEAPONTODO: Add weapon request for this
        if (!zoomstate_set) {
 -              SetZoomState(this,
 -                      PHYS_INPUT_BUTTON_ZOOM(this) || PHYS_INPUT_BUTTON_ZOOMSCRIPT(this)
 -                      || (PHYS_INPUT_BUTTON_ATCK2(this) && PS(this).m_weapon == WEP_VORTEX)
 -                      || (PHYS_INPUT_BUTTON_ATCK2(this) && PS(this).m_weapon == WEP_RIFLE && WEP_CVAR(rifle, secondary) == 0)
 -              );
 +              bool wep_zoomed = false;
 +              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +              {
 +                      .entity weaponentity = weaponentities[slot];
 +                      Weapon thiswep = this.(weaponentity).m_weapon;
 +                      if(thiswep != WEP_Null && thiswep.wr_zoom)
 +                              wep_zoomed += thiswep.wr_zoom(thiswep, this);
 +              }
 +              SetZoomState(this, PHYS_INPUT_BUTTON_ZOOM(this) || PHYS_INPUT_BUTTON_ZOOMSCRIPT(this) || wep_zoomed);
      }
  
        if (this.teamkill_soundtime && time > this.teamkill_soundtime)
  
        // WEAPONTODO: Move into weaponsystem somehow
        // if a player goes unarmed after holding a loaded weapon, empty his clip size and remove the crosshair ammo ring
 -      if (PS(this).m_weapon == WEP_Null)
 -              this.clip_load = this.clip_size = 0;
 +      for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +      {
 +              .entity weaponentity = weaponentities[slot];
 +              if(this.(weaponentity).m_weapon == WEP_Null)
 +                      this.(weaponentity).clip_load = this.(weaponentity).clip_size = 0;
 +      }
  }
  
  void DrownPlayer(entity this)
index 125a285be910707de26af1b15c4d2506d1de2758,2bea200d55348205aebef1512781eb100410929f..7bce389c3a458b2ca98256d210cbe19bb9d1554d
@@@ -31,6 -31,12 +31,12 @@@ MUTATOR_HOOKABLE(ForbidSpawn, EV_Forbid
      /**/
  MUTATOR_HOOKABLE(PlayerSpawn, EV_PlayerSpawn);
  
+ /** called after a player's weapon is chosen so it can be overriden here */
+ #define EV_PlayerWeaponSelect(i, o) \
+       /** player spawning */ i(entity, MUTATOR_ARGV_0_entity) \
+     /**/
+ MUTATOR_HOOKABLE(PlayerWeaponSelect, EV_PlayerWeaponSelect);
  /** called in reset_map */
  #define EV_reset_map_global(i, o) \
      /**/
@@@ -153,14 -159,12 +159,14 @@@ MUTATOR_HOOKABLE(PreFormatMessage, EV_P
  /** returns true if throwing the current weapon shall not be allowed */
  #define EV_ForbidThrowCurrentWeapon(i, o) \
      /** player        */ i(entity, MUTATOR_ARGV_0_entity) \
 +    /** weapon entity */ i(entity, MUTATOR_ARGV_1_entity) \
      /**/
  MUTATOR_HOOKABLE(ForbidThrowCurrentWeapon, EV_ForbidThrowCurrentWeapon);
  
  /** returns true if dropping the current weapon shall not be allowed at any time including death */
  #define EV_ForbidDropCurrentWeapon(i, o) \
 -    /** player */ i(entity, MUTATOR_ARGV_0_entity) \
 +    /** player */        i(entity, MUTATOR_ARGV_0_entity) \
 +    /** weapon id */     i(int, MUTATOR_ARGV_1_int) \
      /**/
  MUTATOR_HOOKABLE(ForbidDropCurrentWeapon, EV_ForbidDropCurrentWeapon);
  
@@@ -385,8 -389,7 +391,8 @@@ MUTATOR_HOOKABLE(PlayerDamaged, EV_Play
   * Called by W_DecreaseAmmo
   */
  #define EV_W_DecreaseAmmo(i, o) \
 -    /** actor */ i(entity, MUTATOR_ARGV_0_entity) \
 +    /** actor */            i(entity, MUTATOR_ARGV_0_entity) \
 +    /** weapon entity */    i(entity, MUTATOR_ARGV_1_entity) \
      /**/
  MUTATOR_HOOKABLE(W_DecreaseAmmo, EV_W_DecreaseAmmo);
  
diff --combined qcsrc/server/player.qc
index 7074c0633f40e8d2e1f9c6efa15f96243e13d62f,f23e6021dda913622990607875680f802152d608..0fcf89fc7bf9735e1a200c020ed407eb3e4519c0
@@@ -24,7 -24,6 +24,7 @@@
  #include "../common/effects/qc/all.qh"
  #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
  #include "../common/triggers/include.qh"
 +#include "../common/wepent.qh"
  
  #include "weapons/weaponstats.qh"
  
@@@ -436,7 -435,7 +436,7 @@@ void PlayerDamage(entity this, entity i
                                        }
  
                                        if(sound_allowed(MSG_BROADCAST, attacker))
-                                       if((this.health < 2 * WEP_CVAR_PRI(blaster, damage) * autocvar_g_balance_selfdamagepercent + 1) || !(DEATH_WEAPONOF(deathtype).spawnflags & WEP_FLAG_CANCLIMB) || attacker != this) // WEAPONTODO: create separate limit for pain notification with laser
+                                       if(this.health < 25 || !(DEATH_WEAPONOF(deathtype).spawnflags & WEP_FLAG_CANCLIMB) || take > 20 || attacker != this)
                                        if(this.health > 1)
                                        // exclude pain sounds for laserjumps as long as you aren't REALLY low on health and would die of the next two
                                        {
  
        valid_damage_for_weaponstats = 0;
        Weapon awep = WEP_Null;
 +      .entity weaponentity = weaponentities[0]; // TODO: unhardcode
  
        if(vbot || IS_REAL_CLIENT(this))
        if(abot || IS_REAL_CLIENT(attacker))
        if(DIFF_TEAM(this, attacker))
        {
                if(DEATH_ISSPECIAL(deathtype))
 -                      awep = PS(attacker).m_weapon;
 +                      awep = attacker.(weaponentity).m_weapon;
                else
                        awep = DEATH_WEAPONOF(deathtype);
                valid_damage_for_weaponstats = 1;
        da = da - max(this.armorvalue, 0);
        if(valid_damage_for_weaponstats)
        {
 -              WeaponStats_LogDamage(awep.m_id, abot, PS(this).m_weapon.m_id, vbot, dh + da);
 +              WeaponStats_LogDamage(awep.m_id, abot, this.(weaponentity).m_weapon.m_id, vbot, dh + da);
        }
        if (damage)
        {
                }
  
                if(valid_damage_for_weaponstats)
 -                      WeaponStats_LogKill(awep.m_id, abot, PS(this).m_weapon.m_id, vbot);
 +                      WeaponStats_LogKill(awep.m_id, abot, this.(weaponentity).m_weapon.m_id, vbot);
  
                if(autocvar_sv_gentle < 1)
                if(sound_allowed(MSG_BROADCAST, attacker))
                MUTATOR_CALLHOOK(PlayerDies, inflictor, attacker, this, deathtype, damage);
                excess = M_ARGV(4, float);
  
 -              Weapon wep = PS(this).m_weapon;
 -              /*for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +              Weapon wep = this.(weaponentity).m_weapon;
 +              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                {
 -                      .entity weaponentity = weaponentities[slot];
 -                      wep.wr_playerdeath(wep, this, weaponentity);
 -              }*/
 -              .entity weaponentity = weaponentities[0]; // TODO: unhardcode
 -              wep.wr_playerdeath(wep, this, weaponentity);
 +                      .entity went = weaponentities[slot];
 +                      wep.wr_playerdeath(wep, this, went);
 +              }
  
 -              RemoveGrapplingHook(this);
 +              RemoveGrapplingHooks(this);
  
                Portal_ClearAllLater(this);
  
                // clear waypoints
                WaypointSprite_PlayerDead(this);
                // throw a weapon
 -              SpawnThrownWeapon(this, this.origin + (this.mins + this.maxs) * 0.5, PS(this).m_switchweapon.m_id);
 +              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +              {
 +                      .entity went = weaponentities[slot];
 +                      SpawnThrownWeapon(this, this.origin + (this.mins + this.maxs) * 0.5, this.(went).m_switchweapon.m_id, went);
 +              }
  
                // become fully visible
                this.alpha = default_player_alpha;