Merge branch 'master' into Mario/wepent_experimental
authorMario <mario@smbclan.net>
Wed, 16 Nov 2016 23:45:45 +0000 (09:45 +1000)
committerMario <mario@smbclan.net>
Wed, 16 Nov 2016 23:45:45 +0000 (09:45 +1000)
1  2 
qcsrc/common/monsters/monster.qh
qcsrc/common/mutators/mutator/buffs/sv_buffs.qc
qcsrc/common/state.qc
qcsrc/common/t_items.qc
qcsrc/server/bot/default/havocbot/havocbot.qc
qcsrc/server/bot/default/scripting.qc
qcsrc/server/player.qc

index 5572fe9102d05a1a07174cfc5255a143784e30ce,ab644988aa4d1192e60fde95bb97f28da642c0d3..8e086a085c62218ba68ee20cc7107f933f9da320
@@@ -12,9 -12,10 +12,10 @@@ const int MON_FLAG_CRUSH = BIT(11); // 
  const int MON_FLAG_RIDE = BIT(12); // monster can be ridden in special modes
  const int MONSTER_SIZE_QUAKE = BIT(13);
  const int MONSTER_TYPE_PASSIVE = BIT(14); // doesn't target or chase enemies
+ const int MONSTER_TYPE_UNDEAD = BIT(15); // monster is by most definitions a zombie (doesn't fully die unless gibbed)
  
  // entity properties of monsterinfo:
 -.bool(int, entity actor, entity targ) monster_attackfunc;
 +.bool(int, entity actor, entity targ, .entity weaponentity) monster_attackfunc;
  
  // animations
  .vector anim_blockend;
index bfd83be19ab478f4e8886c6478ba4bfda2ca942c,e2647289b75672c608da0a42b9dabf804a7b086c..f917219bd7448963b4930bcb10cbccd4681dbb5d
@@@ -826,7 -826,7 +826,7 @@@ MUTATOR_HOOKFUNCTION(buffs, PlayerPreTh
        if(player.buffs & BUFF_MAGNET.m_itemid)
        {
                vector pickup_size;
-               IL_EACH(g_items, it.classname != "item_flag_team" && it.classname != "item_kh_key",
+               IL_EACH(g_items, it.itemdef,
                {
                        if(it.buffs)
                                pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_buff;
        }
  
        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)
diff --combined qcsrc/common/state.qc
index 882b2568741f0ba9194f73285c5567891144cd52,1a2fb1e7bfb171bb0405c1f8a02f83f09bde43dd..a9bc3e5e3cf8aa379ead84d3cc37163b70e46df1
@@@ -17,6 -17,10 +17,6 @@@ void PlayerState_detach(entity this
        PS(this) = NULL;
  
        if (ps.m_client != this) return;  // don't own state, spectator
 -      ps.m_switchweapon = WEP_Null;
 -      ps.m_weapon = WEP_Null;
 -      ps.m_switchingweapon = WEP_Null;
 -      ps.ps_push(ps, this);
        
        FOREACH_CLIENT(PS(it) == ps, { PS(it) = NULL; });
        delete(ps);
@@@ -59,7 -63,7 +59,7 @@@ void ClientState_attach(entity this
  
  void bot_clientdisconnect(entity this);
  void W_HitPlotClose(entity this);
- void anticheat_report(entity this);
+ void anticheat_report_to_eventlog(entity this);
  void playerdemo_shutdown(entity this);
  void entcs_detach(entity this);
  void accuracy_free(entity this);
@@@ -76,7 -80,7 +76,7 @@@ void ClientState_detach(entity this
      bot_clientdisconnect(this);
  
      W_HitPlotClose(this);
-     anticheat_report(this);
+     anticheat_report_to_eventlog(this);
      playerdemo_shutdown(this);
      entcs_detach(this);
      accuracy_free(this);
diff --combined qcsrc/common/t_items.qc
index 17fb9fce0cba011f19ed36f4bd5d0985199c5489,46ee92d906e93cc8dc3a5c30de4750a8f7061a09..b90575a1da8cdd38e03ee8ee31833a70ca98d441
@@@ -82,6 -82,11 +82,11 @@@ void ItemDraw(entity this
                  if(this.ItemStatus & ITS_ANIMATE2)
                      this.avelocity = '0 -90 0';
              }
+             // delay is for blocking item's position for a while;
+             // it's a workaround for dropped weapons that receive the position
+             // another time right after they spawn overriding animation position
+             this.onground_time = time + 0.5;
          }
      }
      else if (autocvar_cl_animate_items)
          if(this.ItemStatus & ITS_ANIMATE1)
          {
              this.angles += this.avelocity * frametime;
-             setorigin(this, '0 0 10' + this.oldorigin + '0 0 8' * sin(time * 2));
+             float fade_in = bound(0, time - this.onground_time, 1);
+             setorigin(this, this.oldorigin + fade_in * ('0 0 10' + '0 0 8' * sin((time - this.onground_time) * 2)));
          }
  
          if(this.ItemStatus & ITS_ANIMATE2)
          {
              this.angles += this.avelocity * frametime;
-             setorigin(this, '0 0 8' + this.oldorigin + '0 0 4' * sin(time * 3));
+             float fade_in = bound(0, time - this.onground_time, 1);
+             setorigin(this, this.oldorigin + fade_in * ('0 0 8' + '0 0 4' * sin((time - this.onground_time) * 3)));
          }
      }
  
@@@ -666,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);
                                }
                        });
@@@ -768,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;
  }
@@@ -857,7 -838,7 +864,7 @@@ LABEL(pickup
                        RandomSelection_Init();
                        IL_EACH(g_items, it.team == this.team,
                        {
-                               if(it.classname != "item_flag_team" && it.classname != "item_kh_key")
+                               if(it.itemdef) // is a registered item
                                {
                                        Item_Show(it, -1);
                                        RandomSelection_AddEnt(it, it.cnt, 0);
@@@ -901,7 -882,7 +908,7 @@@ void Item_FindTeam(entity this
                RandomSelection_Init();
                IL_EACH(g_items, it.team == this.team,
                {
-                       if(it.classname != "item_flag_team" && it.classname != "item_kh_key")
+                       if(it.itemdef) // is a registered item
                                RandomSelection_AddEnt(it, it.cnt, 0);
                });
  
  
                IL_EACH(g_items, it.team == this.team,
                {
-                       if(it.classname != "item_flag_team" && it.classname != "item_kh_key")
+                       if(it.itemdef) // is a registered item
                        {
                                if(it != e)
                                {
@@@ -1665,6 -1646,7 +1672,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 0613ab403d9f73e7c9a2ba03aa42580668d740d0,5f1950774f55bd1805cba9f91c5d85870a5877d3..b6b48798e7113a401bdcf7a77023e3d7e245fb43
@@@ -13,7 -13,6 +13,7 @@@
  #include <common/physics/player.qh>
  #include <common/state.qh>
  #include <common/items/_mod.qh>
 +#include <common/wepent.qh>
  
  #include <common/triggers/teleporters.qh>
  #include <common/triggers/trigger/jumppads.qh>
@@@ -30,6 -29,10 +30,10 @@@ void havocbot_ai(entity this
        if(bot_execute_commands(this))
                return;
  
+       if(this.goalcurrent)
+       if(wasfreed(this.goalcurrent))
+               navigation_poproute(this);
        if (bot_strategytoken == this)
        if (!bot_strategytoken_taken)
        {
                return;
  
        havocbot_chooseenemy(this);
 -      if (this.bot_chooseweapontime < time )
 +
 +      for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
        {
 -              this.bot_chooseweapontime = time + autocvar_bot_ai_chooseweaponinterval;
 -              havocbot_chooseweapon(this);
 +              .entity weaponentity = weaponentities[slot];
 +              if(this.(weaponentity).m_weapon != WEP_Null || slot == 0)
 +              if(this.(weaponentity).bot_chooseweapontime < time)
 +              {
 +                      this.(weaponentity).bot_chooseweapontime = time + autocvar_bot_ai_chooseweaponinterval;
 +                      havocbot_chooseweapon(this, weaponentity);
 +              }
        }
        havocbot_aim(this);
        lag_update(this);
  
                if(this.weapons)
                {
 -                      Weapon w = PS(this).m_weapon;
 -                      w.wr_aim(w, this);
                        if (autocvar_bot_nofire || IS_INDEPENDENT_PLAYER(this))
                        {
                                PHYS_INPUT_BUTTON_ATCK(this) = false;
                        }
                        else
                        {
 -                              if(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this))
 -                                      this.lastfiredweapon = PS(this).m_weapon.m_id;
 +                              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +                              {
 +                                      .entity weaponentity = weaponentities[slot];
 +                                      Weapon w = this.(weaponentity).m_weapon;
 +                                      if(w == WEP_Null && slot != 0)
 +                                              continue;
 +                                      w.wr_aim(w, this, weaponentity);
 +                                      if(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this)) // TODO: what if we didn't fire this weapon, but the previous?
 +                                              this.(weaponentity).lastfiredweapon = this.(weaponentity).m_weapon.m_id;
 +                              }
                        }
                }
                else
        // if the bot is not attacking, consider reloading weapons
        if (!(this.aistatus & AI_STATUS_ATTACKING))
        {
 -              // we are currently holding a weapon that's not fully loaded, reload it
 -              if(skill >= 2) // bots can only reload the held weapon on purpose past this skill
 -              if(this.clip_load < this.clip_size)
 -                      this.impulse = 20; // "press" the reload button, not sure if this is done right
 -
 -              // if we're not reloading a weapon, switch to any weapon in our invnetory that's not fully loaded to reload it next
 -              // the code above executes next frame, starting the reloading then
 -              if(skill >= 5) // bots can only look for unloaded weapons past this skill
 -              if(this.clip_load >= 0) // only if we're not reloading a weapon already
 +              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                {
 -                      FOREACH(Weapons, it != WEP_Null, LAMBDA(
 -                              if((this.weapons & (it.m_wepset)) && (it.spawnflags & WEP_FLAG_RELOADABLE) && (this.weapon_load[it.m_id] < it.reloading_ammo))
 -                                      PS(this).m_switchweapon = it;
 -                      ));
 +                      .entity weaponentity = weaponentities[slot];
 +
 +                      if(this.(weaponentity).m_weapon == WEP_Null && slot != 0)
 +                              continue;
 +
 +                      // we are currently holding a weapon that's not fully loaded, reload it
 +                      if(skill >= 2) // bots can only reload the held weapon on purpose past this skill
 +                      if(this.(weaponentity).clip_load < this.(weaponentity).clip_size)
 +                              this.impulse = 20; // "press" the reload button, not sure if this is done right
 +
 +                      // if we're not reloading a weapon, switch to any weapon in our invnetory that's not fully loaded to reload it next
 +                      // the code above executes next frame, starting the reloading then
 +                      if(skill >= 5) // bots can only look for unloaded weapons past this skill
 +                      if(this.(weaponentity).clip_load >= 0) // only if we're not reloading a weapon already
 +                      {
 +                              FOREACH(Weapons, it != WEP_Null, LAMBDA(
 +                                      if((this.weapons & (it.m_wepset)) && (it.spawnflags & WEP_FLAG_RELOADABLE) && (this.(weaponentity).weapon_load[it.m_id] < it.reloading_ammo))
 +                                              this.(weaponentity).m_switchweapon = it;
 +                              ));
 +                      }
                }
        }
  }
@@@ -606,35 -589,25 +610,35 @@@ void havocbot_movetogoal(entity this
                else if(this.health>WEP_CVAR(devastator, damage)*0.5)
                {
                        if(this.velocity.z < 0)
 -                      if(client_hasweapon(this, WEP_DEVASTATOR, true, false))
                        {
 -                              this.movement_x = maxspeed;
 -
 -                              if(this.rocketjumptime)
 +                              for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                                {
 -                                      if(time > this.rocketjumptime)
 +                                      .entity weaponentity = weaponentities[slot];
 +
 +                                      if(this.(weaponentity).m_weapon == WEP_Null && slot != 0)
 +                                              continue;
 +
 +                                      if(client_hasweapon(this, WEP_DEVASTATOR, weaponentity, true, false))
                                        {
 -                                              PHYS_INPUT_BUTTON_ATCK2(this) = true;
 -                                              this.rocketjumptime = 0;
 +                                              this.movement_x = maxspeed;
 +
 +                                              if(this.rocketjumptime)
 +                                              {
 +                                                      if(time > this.rocketjumptime)
 +                                                      {
 +                                                              PHYS_INPUT_BUTTON_ATCK2(this) = true;
 +                                                              this.rocketjumptime = 0;
 +                                                      }
 +                                                      return;
 +                                              }
 +
 +                                              this.(weaponentity).m_switchweapon = WEP_DEVASTATOR;
 +                                              this.v_angle_x = 90;
 +                                              PHYS_INPUT_BUTTON_ATCK(this) = true;
 +                                              this.rocketjumptime = time + WEP_CVAR(devastator, detonatedelay);
 +                                              return;
                                        }
 -                                      return;
                                }
 -
 -                              PS(this).m_switchweapon = WEP_DEVASTATOR;
 -                              this.v_angle_x = 90;
 -                              PHYS_INPUT_BUTTON_ATCK(this) = true;
 -                              this.rocketjumptime = time + WEP_CVAR(devastator, detonatedelay);
 -                              return;
                        }
                }
                else
@@@ -1014,7 -987,7 +1018,7 @@@ LABEL(scan_targets
                this.havocbot_stickenemy = false;
  }
  
 -float havocbot_chooseweapon_checkreload(entity this, int new_weapon)
 +float havocbot_chooseweapon_checkreload(entity this, .entity weaponentity, int new_weapon)
  {
        // bots under this skill cannot find unloaded weapons to reload idly when not in combat,
        // so skip this for them, or they'll never get to reload their weapons at all.
                return false;
  
        // if this weapon is scheduled for reloading, don't switch to it during combat
 -      if (this.weapon_load[new_weapon] < 0)
 +      if (this.(weaponentity).weapon_load[new_weapon] < 0)
        {
                bool other_weapon_available = false;
                FOREACH(Weapons, it != WEP_Null, LAMBDA(
 -                      if(it.wr_checkammo1(it, this) + it.wr_checkammo2(it, this))
 +                      if(it.wr_checkammo1(it, this, weaponentity) + it.wr_checkammo2(it, this, weaponentity))
                                other_weapon_available = true;
                ));
                if(other_weapon_available)
        return false;
  }
  
 -void havocbot_chooseweapon(entity this)
 +void havocbot_chooseweapon(entity this, .entity weaponentity)
  {
        int i;
  
        // ;)
        if(g_weaponarena_weapons == WEPSET(TUBA))
        {
 -              PS(this).m_switchweapon = WEP_TUBA;
 +              this.(weaponentity).m_switchweapon = WEP_TUBA;
                return;
        }
  
        if(this.enemy==NULL)
        {
                // If no weapon was chosen get the first available weapon
 -              if(PS(this).m_weapon==WEP_Null)
 +              if(this.(weaponentity).m_weapon==WEP_Null)
                FOREACH(Weapons, it != WEP_Null, LAMBDA(
 -                      if(client_hasweapon(this, it, true, false))
 +                      if(client_hasweapon(this, it, weaponentity, true, false))
                        {
 -                              PS(this).m_switchweapon = it;
 +                              this.(weaponentity).m_switchweapon = it;
                                return;
                        }
                ));
        combo = false;
  
        if(autocvar_bot_ai_weapon_combo)
 -      if(PS(this).m_weapon.m_id == this.lastfiredweapon)
 +      if(this.(weaponentity).m_weapon.m_id == this.(weaponentity).lastfiredweapon)
        if(af > combo_time)
        {
                combo = true;
                if ( distance > bot_distance_far ) {
                        for(i=0; i < Weapons_COUNT && bot_weapons_far[i] != -1 ; ++i){
                                w = bot_weapons_far[i];
 -                              if ( client_hasweapon(this, Weapons_from(w), true, false) )
 +                              if ( client_hasweapon(this, Weapons_from(w), weaponentity, true, false) )
                                {
 -                                      if ((PS(this).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(this, w))
 +                                      if ((this.(weaponentity).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(this, weaponentity, w))
                                                continue;
 -                                      PS(this).m_switchweapon = Weapons_from(w);
 +                                      this.(weaponentity).m_switchweapon = Weapons_from(w);
                                        return;
                                }
                        }
                if ( distance > bot_distance_close) {
                        for(i=0; i < Weapons_COUNT && bot_weapons_mid[i] != -1 ; ++i){
                                w = bot_weapons_mid[i];
 -                              if ( client_hasweapon(this, Weapons_from(w), true, false) )
 +                              if ( client_hasweapon(this, Weapons_from(w), weaponentity, true, false) )
                                {
 -                                      if ((PS(this).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(this, w))
 +                                      if ((this.(weaponentity).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(this, weaponentity, w))
                                                continue;
 -                                      PS(this).m_switchweapon = Weapons_from(w);
 +                                      this.(weaponentity).m_switchweapon = Weapons_from(w);
                                        return;
                                }
                        }
                // Choose weapons for close distance
                for(i=0; i < Weapons_COUNT && bot_weapons_close[i] != -1 ; ++i){
                        w = bot_weapons_close[i];
 -                      if ( client_hasweapon(this, Weapons_from(w), true, false) )
 +                      if ( client_hasweapon(this, Weapons_from(w), weaponentity, true, false) )
                        {
 -                              if ((PS(this).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(this, w))
 +                              if ((this.(weaponentity).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(this, weaponentity, w))
                                        continue;
 -                              PS(this).m_switchweapon = Weapons_from(w);
 +                              this.(weaponentity).m_switchweapon = Weapons_from(w);
                                return;
                        }
                }
index e79c57682dec42882e3ecafc9746d8c38a4a900f,f96099087b7b345ec65aa6e14fe5fead95e5509f..252708a1189f0401c7af68b0ff4ff047759e1950
@@@ -4,7 -4,6 +4,7 @@@
  
  #include <common/state.qh>
  #include <common/physics/player.qh>
 +#include <common/wepent.qh>
  
  #include "bot.qh"
  
@@@ -569,22 -568,9 +569,22 @@@ float bot_cmd_select_weapon(entity this
        if(id < WEP_FIRST || id > WEP_LAST)
                return CMD_STATUS_ERROR;
  
 -      if(client_hasweapon(this, Weapons_from(id), true, false))
 -              PS(this).m_switchweapon = Weapons_from(id);
 -      else
 +      bool success = false;
 +
 +      for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +      {
 +              .entity weaponentity = weaponentities[slot];
 +              if(this.(weaponentity).m_weapon == WEP_Null && slot != 0)
 +                      continue;
 +
 +              if(client_hasweapon(this, Weapons_from(id), weaponentity, true, false))
 +              {
 +                      success = true;
 +                      this.(weaponentity).m_switchweapon = Weapons_from(id);
 +              }
 +      }
 +
 +      if(!success)
                return CMD_STATUS_ERROR;
  
        return CMD_STATUS_FINISHED;
@@@ -1068,7 -1054,7 +1068,7 @@@ float bot_cmd_debug_assert_canfire(enti
                if(f)
                {
                        this.colormod = '0 8 8';
 -                      LOG_INFO("Bot ", this.netname, " using ", this.weaponname, " wants to fire, inhibited by weaponentity state\n");
 +                      LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " wants to fire, inhibited by weaponentity state\n");
                }
        }
        else if(ATTACK_FINISHED(this, slot) > time)
                if(f)
                {
                        this.colormod = '8 0 8';
 -                      LOG_INFO("Bot ", this.netname, " using ", this.weaponname, " wants to fire, inhibited by ATTACK_FINISHED (", ftos(ATTACK_FINISHED(this, slot) - time), " seconds left)\n");
 +                      LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " wants to fire, inhibited by ATTACK_FINISHED (", ftos(ATTACK_FINISHED(this, slot) - time), " seconds left)\n");
                }
        }
 -      else if(this.tuba_note)
 +      else if(this.(weaponentity).tuba_note)
        {
                if(f)
                {
                        this.colormod = '8 0 0';
 -                      LOG_INFO("Bot ", this.netname, " using ", this.weaponname, " wants to fire, bot still has an active tuba note\n");
 +                      LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " wants to fire, bot still has an active tuba note\n");
                }
        }
        else
                if(!f)
                {
                        this.colormod = '8 8 0';
 -                      LOG_INFO("Bot ", this.netname, " using ", this.weaponname, " thinks it has fired, but apparently did not; ATTACK_FINISHED says ", ftos(ATTACK_FINISHED(this, slot) - time), " seconds left\n");
 +                      LOG_INFO("Bot ", this.netname, " using ", this.(weaponentity).weaponname, " thinks it has fired, but apparently did not; ATTACK_FINISHED says ", ftos(ATTACK_FINISHED(this, slot) - time), " seconds left\n");
                }
        }
  
@@@ -1174,16 -1160,17 +1174,17 @@@ float bot_execute_commands_once(entity 
        // Find command
        bot_setcurrentcommand(this);
  
-       // if we have no bot command, better return
-       // old logic kept pressing previously pressed keys, but that has problems
-       // (namely, it means you cannot make a bot "normal" ever again)
-       // to keep a bot walking for a while, use the "wait" bot command
-       if(bot_cmd == NULL)
-               return false;
        // Ignore all commands except continue when the bot is paused
-       if(this.bot_exec_status & BOT_EXEC_STATUS_PAUSED)
-       if(bot_cmd.bot_cmd_type!=BOT_CMD_CONTINUE)
+       if(!(self.bot_exec_status & BOT_EXEC_STATUS_PAUSED))
+       {
+               // if we have no bot command, better return
+               // old logic kept pressing previously pressed keys, but that has problems
+               // (namely, it means you cannot make a bot "normal" ever again)
+               // to keep a bot walking for a while, use the "wait" bot command
+               if(bot_cmd == world)
+                       return 0;
+       }
+       else if(bot_cmd.bot_cmd_type != BOT_CMD_CONTINUE)
        {
                if(bot_cmd.bot_cmd_type!=BOT_CMD_NULL)
                {
diff --combined qcsrc/server/player.qc
index 4a4d1301f58c25aa4f9220ca81c17c717f55b0f8,9125b50d7cab1969108371ed2bd744194387495f..ce71f6b5e3853e349f5fcf65aadad501d96f6d48
@@@ -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"
  
@@@ -324,6 -323,9 +324,9 @@@ void PlayerDamage(entity this, entity i
                        damage /= sqrt(bound(1.0, attacker.cvar_cl_handicap, 100.0));
        }
  
+       if (time < this.spawnshieldtime && autocvar_g_spawnshield_blockdamage < 1)
+               damage *= 1 - max(0, autocvar_g_spawnshield_blockdamage);
        if(DEATH_ISWEAPON(deathtype, WEP_TUBA))
        {
                // tuba causes blood to come out of the ears
        else
                Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
  
        v = healtharmor_applydamage(this.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
        take = v.x;
        save = v.y;
                this.istypefrag = 0;
        }
  
-       if (time < this.spawnshieldtime && autocvar_g_spawnshield_blockdamage < 1)
-       {
-               vector v = healtharmor_applydamage(this.armorvalue, max(0, autocvar_g_spawnshield_blockdamage), deathtype, damage);
-               take = v.x;
-               save = v.y;
-       }
        MUTATOR_CALLHOOK(PlayerDamage_SplitHealthArmor, inflictor, attacker, this, force, take, save, deathtype, damage);
        take = bound(0, M_ARGV(4, float), this.health);
        save = bound(0, M_ARGV(5, float), this.armorvalue);
  
        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;