]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/wepent_experimental
authorMario <mario@smbclan.net>
Sat, 31 Dec 2016 01:00:59 +0000 (11:00 +1000)
committerMario <mario@smbclan.net>
Sat, 31 Dec 2016 01:00:59 +0000 (11:00 +1000)
1  2 
defaultXonotic.cfg
mutators.cfg
qcsrc/common/mutators/mutator/buffs/sv_buffs.qc
qcsrc/common/t_items.qc
qcsrc/server/bot/default/havocbot/havocbot.qc
qcsrc/server/weapons/throwing.qc

diff --combined defaultXonotic.cfg
index fa093b04721433b92a930f0288c6a1ed4864c391,bbdbdcb74a4873406ac60d3c4ac72fc158aba72e..ac3b51613967c33f4c98dbd64d0869c6530bf89e
@@@ -60,7 -60,7 +60,7 @@@ _cl_playerskin 
  
  seta cl_reticle 1 "enable zoom reticles"
  seta cl_reticle_stretch 0 "stretch reticles so they fit the screen (breaks image proportions)"
 -seta cl_reticle_normal 1 "draw an aiminig reticle when zooming with the zoom button"
 +seta cl_reticle_normal 1 "draw an aiming reticle when zooming with the zoom button"
  seta cl_reticle_normal_alpha 1 "alpha of the normal reticle"
  seta cl_reticle_weapon 1 "draw custom aiming reticle when zooming with certain weapons"
  seta cl_reticle_weapon_alpha 1 "alpha of the custom reticle"
@@@ -382,7 -382,7 +382,7 @@@ set bot_ai_friends_aware_pickup_radius 
  set bot_ai_ignoregoal_timeout 3       "Ignore goals making bots to get stuck in front of a wall for N seconds"
  set bot_ai_bunnyhop_skilloffset 7     "Bots with skill equal or greater than this value will perform the  \"bunnyhop\" technique"
  set bot_ai_bunnyhop_startdistance 200 "Run to goals located further than this distance"
- set bot_ai_bunnyhop_stopdistance 200 "Stop jumping after reaching this distance to the goal"
+ set bot_ai_bunnyhop_stopdistance 300 "Stop jumping after reaching this distance to the goal"
  set bot_ai_bunnyhop_firstjumpdelay 0.2 "Start running to the goal only if it was seen for more than N seconds"
  set bot_god 0 "god mode for bots"
  set bot_ai_navigation_jetpack 0 "Enable bots to navigate maps using the jetpack"
diff --combined mutators.cfg
index f36a5a77494e9508a79bc8d083574b408c2f1262,cd6a9e231a2598dec4472d8b5e4b64da40bbc2eb..d487327bc690c3becc73ee20dbcaab171aa21dad
@@@ -55,6 -55,22 +55,6 @@@ set g_overkill_filter_armormedium 
  set g_overkill_filter_armorbig 0
  set g_overkill_filter_armormega 0
  
 -set g_overkill_ammo_charge 0
 -set g_overkill_ammo_charge_notice 1
 -set g_overkill_ammo_charge_limit 1
 -set g_overkill_ammo_charge_rate 0.5
 -set g_overkill_ammo_charge_rate_vortex 0.5
 -set g_overkill_ammo_charge_rate_machinegun 0.5
 -set g_overkill_ammo_charge_rate_shotgun 0.5
 -set g_overkill_ammo_charge_rate_hmg 0.25
 -set g_overkill_ammo_charge_rate_rpc 1.5
 -set g_overkill_ammo_decharge 0.1
 -set g_overkill_ammo_decharge_machinegun 0.025
 -set g_overkill_ammo_decharge_shotgun 0.15
 -set g_overkill_ammo_decharge_vortex 0.2
 -set g_overkill_ammo_decharge_rpc 1
 -set g_overkill_ammo_decharge_hmg 0.01
 -
  
  // =========
  //  vampire
@@@ -288,7 -304,7 +288,7 @@@ set g_new_toys_use_pickupsound 1 "play 
  //  buffs
  // =======
  set cl_buffs_autoreplace 1 "automatically drop current buff when picking up another"
- set g_buffs 0 "enable buffs (requires buff items or powerups)"
+ set g_buffs -1 "enable buffs (requires buff items or powerups)"
  set g_buffs_effects 1 "show particle effects from carried buffs"
  set g_buffs_waypoint_distance 1024 "maximum distance at which buff waypoint can be seen from item"
  set g_buffs_randomize 1 "randomize buff type when player drops buff"
@@@ -296,7 -312,7 +296,7 @@@ set g_buffs_random_lifetime 30 "re-spaw
  set g_buffs_random_location 0 "randomize buff location on start and when reset"
  set g_buffs_random_location_attempts 10 "number of random locations a single buff will attempt to respawn at before giving up"
  set g_buffs_spawn_count 0 "how many buffs to spawn on the map if none exist already"
- set g_buffs_replace_powerups 1 "replace powerups on the map with random buffs"
+ set g_buffs_replace_powerups 0 "replace powerups on the map with random buffs"
  set g_buffs_cooldown_activate 5 "cooldown period when buff is first activated"
  set g_buffs_cooldown_respawn 3 "cooldown period when buff is reloading"
  set g_buffs_ammo 1 "ammo buff: infinite ammunition"
@@@ -361,6 -377,8 +361,8 @@@ set g_buffs_luck 1 "luck buff: randoml
  set g_buffs_luck_time 60 "luck buff carry time"
  set g_buffs_luck_chance 0.15 "chance for 'critical' hit (multiplied damage) with luck buff"
  set g_buffs_luck_damagemultiplier 3 "luck damage multiplier"
+ set g_buffs_flight 0 "flight buff: crouch jump to reverse your gravity!"
+ set g_buffs_flight_time 60 "flight buff carry time"
  
  
  // ==============
index a5d3490a459186e0ff8fa71d9e57905dad91354d,d9eed031cd2f15b2fe4d6459bb2d6a19351d6e7a..3bf73bf101364748169fef6c39303a0cda392ba2
@@@ -6,11 -6,14 +6,14 @@@
  .float buff_time = _STAT(BUFF_TIME);
  void buffs_DelayedInit(entity this);
  
- REGISTER_MUTATOR(buffs, cvar("g_buffs"))
+ 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)
  {
        MUTATOR_ONADD
        {
-               InitializeEntity(NULL, buffs_DelayedInit, INITPRIO_FINDTARGET);
+               if(autocvar_g_buffs > 0)
+                       InitializeEntity(NULL, buffs_DelayedInit, INITPRIO_FINDTARGET);
        }
  }
  
@@@ -592,6 -595,10 +595,10 @@@ MUTATOR_HOOKFUNCTION(buffs, PlayerJump
  
        if(player.buffs & BUFF_JUMP.m_itemid)
                M_ARGV(1, float) = autocvar_g_buffs_jump_height;
+       if(player.buffs & BUFF_FLIGHT.m_itemid)
+       if(!IS_JUMP_HELD(player) && PHYS_INPUT_BUTTON_CROUCH(player))
+               player.gravity *= -1;
  }
  
  MUTATOR_HOOKFUNCTION(buffs, MonsterMove)
@@@ -744,6 -751,9 +751,9 @@@ MUTATOR_HOOKFUNCTION(buffs, CustomizeWa
  
  MUTATOR_HOOKFUNCTION(buffs, OnEntityPreSpawn, CBC_ORDER_LAST)
  {
+       if(autocvar_g_buffs < 0)
+               return; // no auto replacing of entities in this mode
        entity ent = M_ARGV(0, entity);
  
        if(autocvar_g_buffs_replace_powerups)
@@@ -843,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)
                BUFF_ONREM(BUFF_INVISIBLE)
                        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 = player.buffs;
                if(player.buffs)
                {
@@@ -1003,12 -1002,14 +1023,14 @@@ REPLICATE(cvar_cl_buffs_autoreplace, bo
  
  MUTATOR_HOOKFUNCTION(buffs, BuildMutatorsString)
  {
-       M_ARGV(0, string) = strcat(M_ARGV(0, string), ":Buffs");
+       if(autocvar_g_buffs > 0) // only report as a mutator if they're enabled
+               M_ARGV(0, string) = strcat(M_ARGV(0, string), ":Buffs");
  }
  
  MUTATOR_HOOKFUNCTION(buffs, BuildMutatorsPrettyString)
  {
-       M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Buffs");
+       if(autocvar_g_buffs > 0)
+               M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Buffs");
  }
  
  void buffs_DelayedInit(entity this)
diff --combined qcsrc/common/t_items.qc
index 82801228e964c4a063fdcd0290b46392f329f00c,9d89679e3be9f88817ea8abd9883774c36eca22a..116fbfcd5a4768f4d4eb4c03f2f681cece9a1241
@@@ -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;
  }
@@@ -950,17 -924,20 +950,20 @@@ float generic_pickupevalfunc(entity pla
  float weapon_pickupevalfunc(entity player, entity item)
  {
        float c;
+       int rating = item.bot_pickupbasevalue;
  
        // See if I have it already
-       if(item.weapons & ~player.weapons)
+       if(player.weapons & item.weapons)
        {
                // If I can pick it up
                if(!item.spawnshieldtime)
                        c = 0;
                else if(player.ammo_cells || player.ammo_shells || player.ammo_plasma || player.ammo_nails || player.ammo_rockets)
                {
+                       if (rating > 0)
+                               rating = BOT_PICKUP_RATING_LOW * 0.5 * (1 + rating / BOT_PICKUP_RATING_HIGH);
                        // Skilled bots will grab more
-                       c = bound(0, skill / 10, 1) * 0.5;
+                       c = 1 + bound(0, skill / 10, 1) * 0.5;
                }
                else
                        c = 0;
        else
                c = 1;
  
+       if (c <= 0)
+               return 0;
        // If custom weapon priorities for bots is enabled rate most wanted weapons higher
-       if( bot_custom_weapon && c )
+       if(bot_custom_weapon)
        {
-               // Find the highest position on any range
-               int position = -1;
-               for (int j = 0; j < WEP_LAST ; ++j){
-                       if(
-                                       bot_weapons_far[j] == item.weapon ||
-                                       bot_weapons_mid[j] == item.weapon ||
-                                       bot_weapons_close[j] == item.weapon
-                         )
+               int best_ratio = 0;
+               int missing = 0;
+               // evaluate weapon usefulness in all ranges
+               for(int list = 0; list < 3; list++)
+               {
+                       int position = -1;
+                       int wep_count = 0;
+                       int wpn = item.weapon;
+                       for (int j = 0; j < WEP_LAST; ++j)
+                       {
+                               int list_wpn = 0;
+                               if (list == 0) list_wpn = bot_weapons_far[j];
+                               else if (list == 1) list_wpn = bot_weapons_mid[j];
+                               else list_wpn = bot_weapons_close[j];
+                               if (weaponsInMap & Weapons_from(list_wpn).m_wepset) // only if available
+                               {
+                                       if (list_wpn > 0)
+                                               wep_count++;
+                                       if (position == -1 && list_wpn == wpn)
+                                               position = wep_count;
+                               }
+                       }
+                       if (position == -1)
+                       {
+                               missing++;
+                               position = wep_count; // if missing assume last
+                       }
+                       if (wep_count)
                        {
-                               position = j;
-                               break;
+                               if (!best_ratio || position / wep_count < best_ratio)
+                                       best_ratio = position / wep_count;
                        }
                }
  
-               // Rate it
-               if (position >= 0 )
-               {
-                       position = WEP_LAST - position;
-                       // item.bot_pickupbasevalue is overwritten here
-                       return (BOT_PICKUP_RATING_LOW + ( (BOT_PICKUP_RATING_HIGH - BOT_PICKUP_RATING_LOW) * (position / WEP_LAST ))) * c;
-               }
+               if (missing < 3 && best_ratio)
+                       c = c - best_ratio * 0.3;
        }
  
-       return item.bot_pickupbasevalue * c;
+       return rating * c;
  }
  
  float commodity_pickupevalfunc(entity player, entity item)
  {
-       float c;
-       float need_shells = false, need_nails = false, need_rockets = false, need_cells = false, need_plasma = false, need_fuel = false;
-       c = 0;
+       bool need_shells = false, need_nails = false, need_rockets = false, need_cells = false, need_plasma = false, need_fuel = false;
+       float c = 0;
  
        // Detect needed ammo
        FOREACH(Weapons, it != WEP_Null, {
                if(!(player.weapons & (it.m_wepset)))
                        continue;
  
-               if(it.items & ITEM_Shells.m_itemid)
-                       need_shells = true;
-               else if(it.items & ITEM_Bullets.m_itemid)
-                       need_nails = true;
-               else if(it.items & ITEM_Rockets.m_itemid)
-                       need_rockets = true;
-               else if(it.items & ITEM_Cells.m_itemid)
-                       need_cells = true;
-               else if(it.items & ITEM_Plasma.m_itemid)
-                       need_plasma = true;
-               else if(it.items & ITEM_JetpackFuel.m_itemid)
-                       need_fuel = true;
+               switch(it.ammo_field)
+               {
+                       case ammo_shells:  need_shells  = true; break;
+                       case ammo_nails:   need_nails   = true; break;
+                       case ammo_rockets: need_rockets = true; break;
+                       case ammo_cells:   need_cells   = true; break;
+                       case ammo_plasma:  need_plasma  = true; break;
+                       case ammo_fuel:    need_fuel    = true; break;
+               }
        });
  
        // TODO: figure out if the player even has the weapon this ammo is for?
@@@ -1674,6 -1667,7 +1693,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 9641fab9ada27951e5acfc66d28c3f553aad7bfb,8e6b3912b3da70f7ecb757bb41a3ce997c24824d..9321d388c52e6b129990538fb038fa21e6b1d7c2
@@@ -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,13 -29,6 +30,6 @@@ void havocbot_ai(entity this
        if(bot_execute_commands(this))
                return;
  
-       while(this.goalcurrent && wasfreed(this.goalcurrent))
-       {
-               navigation_poproute(this);
-               if(!this.goalcurrent)
-                       this.bot_strategytime = 0;
-       }
        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;
 +                              ));
 +                      }
                }
        }
  }
@@@ -371,7 -343,8 +364,8 @@@ void havocbot_bunnyhop(entity this, vec
                                        if(checkdistance)
                                        {
                                                this.aistatus &= ~AI_STATUS_RUNNING;
-                                               if(bunnyhopdistance > autocvar_bot_ai_bunnyhop_stopdistance)
+                                               // increase stop distance in case the goal is on a slope or a lower platform 
+                                               if(bunnyhopdistance > autocvar_bot_ai_bunnyhop_stopdistance + (this.origin.z - gco.z))
                                                        PHYS_INPUT_BUTTON_JUMP(this) = true;
                                        }
                                        else
@@@ -513,6 -486,7 +507,7 @@@ void havocbot_movetogoal(entity this
        if(this.jumppadcount)
        {
                // If got stuck on the jump pad try to reach the farthest visible waypoint
+               // but with some randomness so it can try out different paths
                if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
                {
                        if(fabs(this.velocity.z)<50)
                                        if(trace_fraction < 1)
                                                continue;
  
-                                       if(!newgoal || vlen2(it.origin - this.origin) > vlen2(newgoal.origin - this.origin))
+                                       if(!newgoal || ((random() < 0.8) && vlen2(it.origin - this.origin) > vlen2(newgoal.origin - this.origin)))
                                                newgoal = it;
                                });
  
                                        this.ignoregoaltime = time + autocvar_bot_ai_ignoregoal_timeout;
                                        navigation_clearroute(this);
                                        navigation_routetogoal(this, newgoal, this.origin);
+                                       if(autocvar_bot_debug_goalstack)
+                                               debuggoalstack(this);
                                        this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
                                }
                        }
                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
        if (this.goalcurrent == NULL)
                return;
  
-       if (this.goalcurrent)
-               navigation_poptouchedgoals(this);
+       navigation_poptouchedgoals(this);
  
        // if ran out of goals try to use an alternative goal or get a new strategy asap
        if(this.goalcurrent == NULL)
                        }
  
                        // avoiding dangers and obstacles
-                       vector dst_ahead, dst_down;
-                       makevectors(this.v_angle.y * '0 1 0');
-                       dst_ahead = this.origin + this.view_ofs + (this.velocity * 0.4) + (v_forward * 32 * 3);
-                       dst_down = dst_ahead - '0 0 1500';
+                       vector dst_ahead = this.origin + this.view_ofs + this.velocity * 0.5;
+                       vector dst_down = dst_ahead - '0 0 3000';
  
                        // Look ahead
                        traceline(this.origin + this.view_ofs, dst_ahead, true, NULL);
                        this.aistatus &= ~AI_STATUS_DANGER_AHEAD;
  
                        if(trace_fraction == 1 && this.jumppadcount == 0 && !this.goalcurrent.wphardwired )
-                       if((IS_ONGROUND(this)) || (this.aistatus & AI_STATUS_RUNNING) || PHYS_INPUT_BUTTON_JUMP(this))
+                       if((IS_ONGROUND(this)) || (this.aistatus & AI_STATUS_RUNNING) || (this.aistatus & AI_STATUS_ROAMING) || PHYS_INPUT_BUTTON_JUMP(this))
                        {
                                // Look downwards
                                traceline(dst_ahead , dst_down, true, NULL);
-                       //      te_lightning2(NULL, this.origin, dst_ahead);    // Draw "ahead" look
-                       //      te_lightning2(NULL, dst_ahead, dst_down);               // Draw "downwards" look
+                               //te_lightning2(NULL, this.origin + this.view_ofs, dst_ahead); // Draw "ahead" look
+                               //te_lightning2(NULL, dst_ahead, dst_down); // Draw "downwards" look
                                if(trace_endpos.z < this.origin.z + this.mins.z)
                                {
                                        s = pointcontents(trace_endpos + '0 0 1');
                                                evadelava = normalize(this.velocity) * -1;
                                        else if (s == CONTENT_SKY)
                                                evadeobstacle = normalize(this.velocity) * -1;
-                                       else if (!boxesoverlap(dst_ahead - this.view_ofs + this.mins, dst_ahead - this.view_ofs + this.maxs,
-                                                               this.goalcurrent.absmin, this.goalcurrent.absmax))
+                                       else if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
                                        {
-                                               // if ain't a safe goal with "holes" (like the jumpad on soylent)
-                                               // and there is a trigger_hurt below
-                                               if(tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
+                                               // the traceline check isn't enough but is good as optimization,
+                                               // when not true (most of the time) this tracebox call is avoided
+                                               tracebox(dst_ahead, this.mins, this.maxs, dst_down, true, this);
+                                               if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
                                                {
-                                                       // Remove dangerous dynamic goals from stack
-                                                       LOG_TRACE("bot ", this.netname, " avoided the goal ", this.goalcurrent.classname, " ", etos(this.goalcurrent), " because it led to a dangerous path; goal stack cleared");
-                                                       navigation_clearroute(this);
-                                                       return;
+                                                       if (gco.z > this.origin.z + jumpstepheightvec.z)
+                                                       { 
+                                                               // the goal is probably on an upper platform, assume bot can't get there
+                                                               LOG_TRACE("bot ", this.netname, " avoided the goal ", this.goalcurrent.classname, " ", etos(this.goalcurrent), " because it led to a dangerous path; goal stack cleared");
+                                                               navigation_clearroute(this);
+                                                               this.bot_strategytime = 0;
+                                                       }
+                                                       else
+                                                               evadelava = normalize(this.velocity) * -1;
                                                }
                                        }
                                }
@@@ -1012,7 -980,7 +1011,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 4114c3c58b199cc9a950c811dcf5269b0fdea807,30b700098d4a056d150ba589ce39d57b1376ff99..ba84b14c50d11ea9ebb657e8b5465c25e5d210b2
@@@ -11,7 -11,6 +11,7 @@@
  #include <common/util.qh>
  #include <common/weapons/_all.qh>
  #include <common/state.qh>
 +#include <common/wepent.qh>
  
  void thrown_wep_think(entity this)
  {
        {
                this.SendFlags |= ISF_LOCATION;
                this.oldorigin = this.origin;
+               this.bot_pickup = false;
        }
+       else
+               this.bot_pickup = true;
        this.owner = NULL;
        float timeleft = this.savenextthink - time;
        if(timeleft > 1)
@@@ -32,7 -34,7 +35,7 @@@
  }
  
  // returns amount of ammo used as string, or -1 for failure, or 0 for no ammo count
 -string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo)
 +string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo, .entity weaponentity)
  {
        float thisammo;
        string s;
@@@ -46,9 -48,9 +49,9 @@@
        wep.owner = wep.enemy = own;
        wep.flags |= FL_TOSSED;
        wep.colormap = own.colormap;
 -      wep.glowmod = weaponentity_glowmod(info, own, own.clientcolors);
 +      wep.glowmod = weaponentity_glowmod(info, own, own.clientcolors, own.(weaponentity));
  
 -      W_DropEvent(wr_drop,own,wpn,wep);
 +      W_DropEvent(wr_drop,own,wpn,wep,weaponentity);
  
        if(WepSet_FromWeapon(Weapons_from(wpn)) & WEPSET_SUPERWEAPONS)
        {
                if(doreduce && g_weapon_stay == 2)
                {
                        // if our weapon is loaded, give its load back to the player
 -                      int i = PS(own).m_weapon.m_id;
 -                      if(own.(weapon_load[i]) > 0)
 +                      int i = own.(weaponentity).m_weapon.m_id;
 +                      if(own.(weaponentity).(weapon_load[i]) > 0)
                        {
 -                              own.(ammotype) += own.(weapon_load[i]);
 -                              own.(weapon_load[i]) = -1; // schedule the weapon for reloading
 +                              own.(ammotype) += own.(weaponentity).(weapon_load[i]);
 +                              own.(weaponentity).(weapon_load[i]) = -1; // schedule the weapon for reloading
                        }
  
                        wep.(ammotype) = 0;
                else if(doreduce)
                {
                        // if our weapon is loaded, give its load back to the player
 -                      int i = PS(own).m_weapon.m_id;
 -                      if(own.(weapon_load[i]) > 0)
 +                      int i = own.(weaponentity).m_weapon.m_id;
 +                      if(own.(weaponentity).(weapon_load[i]) > 0)
                        {
 -                              own.(ammotype) += own.(weapon_load[i]);
 -                              own.(weapon_load[i]) = -1; // schedule the weapon for reloading
 +                              own.(ammotype) += own.(weaponentity).(weapon_load[i]);
 +                              own.(weaponentity).(weapon_load[i]) = -1; // schedule the weapon for reloading
                        }
  
                        thisammo = min(own.(ammotype), wep.(ammotype));
  
  bool W_IsWeaponThrowable(entity this, int w)
  {
 -      if (MUTATOR_CALLHOOK(ForbidDropCurrentWeapon, this))
 +      if (MUTATOR_CALLHOOK(ForbidDropCurrentWeapon, this, w))
                return false;
        if (!autocvar_g_pickup_items)
                return false;
  // toss current weapon
  void W_ThrowWeapon(entity this, .entity weaponentity, vector velo, vector delta, float doreduce)
  {
 -      Weapon w = PS(this).m_weapon;
 +      Weapon w = this.(weaponentity).m_weapon;
        if (w == WEP_Null)
                return; // just in case
 -      if(MUTATOR_CALLHOOK(ForbidThrowCurrentWeapon, this))
 +      if(MUTATOR_CALLHOOK(ForbidThrowCurrentWeapon, this, this.(weaponentity)))
                return;
        if(!autocvar_g_weapon_throwable)
                return;
        if(!(this.weapons & set)) return;
        this.weapons &= ~set;
  
 -      W_SwitchWeapon_Force(this, w_getbestweapon(this));
 -      string a = W_ThrowNewWeapon(this, w.m_id, doreduce, this.origin + delta, velo);
 +      W_SwitchWeapon_Force(this, w_getbestweapon(this, weaponentity), weaponentity);
 +      string a = W_ThrowNewWeapon(this, w.m_id, doreduce, this.origin + delta, velo, weaponentity);
  
        if(!a) return;
        Send_Notification(NOTIF_ONE, this, MSG_MULTI, ITEM_WEAPON_DROP, a, w.m_id);
  }
  
 -void SpawnThrownWeapon(entity this, vector org, float w)
 +void SpawnThrownWeapon(entity this, vector org, float w, .entity weaponentity)
  {
 -      if(this.weapons & WepSet_FromWeapon(PS(this).m_weapon))
 -              if(W_IsWeaponThrowable(this, PS(this).m_weapon.m_id))
 -                      W_ThrowNewWeapon(this, PS(this).m_weapon.m_id, false, org, randomvec() * 125 + '0 0 200');
 +      entity wep = this.(weaponentity).m_weapon;
 +
 +      if(this.weapons & WepSet_FromWeapon(wep))
 +              if(W_IsWeaponThrowable(this, wep.m_id))
 +                      W_ThrowNewWeapon(this, wep.m_id, false, org, randomvec() * 125 + '0 0 200', weaponentity);
  }