Merge branch 'master' into terencehill/bot_ai
authorterencehill <piuntn@gmail.com>
Tue, 17 Jul 2018 15:19:59 +0000 (17:19 +0200)
committerterencehill <piuntn@gmail.com>
Tue, 17 Jul 2018 15:19:59 +0000 (17:19 +0200)
21 files changed:
.gitlab-ci.yml
qcsrc/common/gamemodes/gamemode/assault/assault.qc
qcsrc/common/gamemodes/gamemode/assault/assault.qh
qcsrc/common/gamemodes/gamemode/ctf/ctf.qc
qcsrc/common/gamemodes/gamemode/domination/domination.qc
qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qc
qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qc
qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qc
qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc
qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qh
qcsrc/common/items/item/powerup.qh
qcsrc/server/bot/api.qh
qcsrc/server/bot/default/aim.qc
qcsrc/server/bot/default/aim.qh
qcsrc/server/bot/default/bot.qc
qcsrc/server/bot/default/havocbot/havocbot.qc
qcsrc/server/bot/default/havocbot/havocbot.qh
qcsrc/server/bot/default/havocbot/roles.qc
qcsrc/server/bot/default/navigation.qc
qcsrc/server/bot/default/navigation.qh
xonotic-server.cfg

index 153b172f2bc2986096b04662f7ec1e011bfe617f..61ebfcdfbfc9b9fd2de528569fffd06be9f0c6b7 100644 (file)
@@ -29,7 +29,7 @@ test_sv_game:
     - wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
     - wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
     - make
-    - EXPECT=b8f4fa5002af1f9f2d5ac3d1809ed188
+    - EXPECT=f83676ceba1516c8f365f49c3d1743d5
     - HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
       | tee /dev/stderr
       | grep '^:'
index e7134182e497b95facf46c75d680bc5079dd7348..bc048563e83e837df8cf5cc3301e35b9391085e3 100644 (file)
@@ -430,9 +430,9 @@ void havocbot_goalrating_ast_targets(entity this, float ratingscale)
                // Find and rate waypoints around it
                found = false;
                entity best = NULL;
-               float bestvalue = 99999999999;
+               float bestvalue = FLOAT_MAX;
                entity des = it;
-               for(float radius = 0; radius < 1500 && !found; radius += 500)
+               for (float radius = 500; radius <= 1500 && !found; radius += 500)
                {
                        FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
                        {
@@ -493,10 +493,11 @@ void havocbot_role_ast_offense(entity this)
 
        if (navigation_goalrating_timeout(this))
        {
+               // role: offense
                navigation_goalrating_start(this);
-               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
+               havocbot_goalrating_enemyplayers(this, 10000, this.origin, 650);
                havocbot_goalrating_ast_targets(this, 20000);
-               havocbot_goalrating_items(this, 15000, this.origin, 10000);
+               havocbot_goalrating_items(this, 30000, this.origin, 10000);
                navigation_goalrating_end(this);
 
                navigation_goalrating_timeout_set(this);
@@ -527,10 +528,11 @@ void havocbot_role_ast_defense(entity this)
 
        if (navigation_goalrating_timeout(this))
        {
+               // role: defense
                navigation_goalrating_start(this);
-               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 3000);
+               havocbot_goalrating_enemyplayers(this, 10000, this.origin, 3000);
                havocbot_goalrating_ast_targets(this, 20000);
-               havocbot_goalrating_items(this, 15000, this.origin, 10000);
+               havocbot_goalrating_items(this, 30000, this.origin, 10000);
                navigation_goalrating_end(this);
 
                navigation_goalrating_timeout_set(this);
@@ -543,12 +545,10 @@ void havocbot_role_ast_setrole(entity this, float role)
        {
                case HAVOCBOT_AST_ROLE_DEFENSE:
                        this.havocbot_role = havocbot_role_ast_defense;
-                       this.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE;
                        this.havocbot_role_timeout = 0;
                        break;
                case HAVOCBOT_AST_ROLE_OFFENSE:
                        this.havocbot_role = havocbot_role_ast_offense;
-                       this.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE;
                        this.havocbot_role_timeout = 0;
                        break;
        }
index d949f18b34700775cd5ecc5429b78373a862c07b..ff65b66ed23923be629d0f6e16166370ff645080 100644 (file)
@@ -32,7 +32,6 @@ const int HAVOCBOT_AST_ROLE_NONE = 0;
 const int HAVOCBOT_AST_ROLE_DEFENSE = 2;
 const int HAVOCBOT_AST_ROLE_OFFENSE = 4;
 
-.int havocbot_role_flags;
 .float havocbot_attack_time;
 
 void(entity this) havocbot_role_ast_defense;
index 812d5da4ec0321625d09484e9f38daee27533a57..13d89c1be13c3c3f6537ee0f0592ee883f906c05 100644 (file)
@@ -449,7 +449,6 @@ void ctf_Handle_Throw(entity player, entity receiver, int droptype)
        flag.solid = SOLID_TRIGGER;
        flag.ctf_dropper = player;
        flag.ctf_droptime = time;
-       navigation_dynamicgoal_set(flag);
 
        flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
 
@@ -488,6 +487,7 @@ void ctf_Handle_Throw(entity player, entity receiver, int droptype)
                        flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
                        flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
                        ctf_Handle_Drop(flag, player, droptype);
+                       navigation_dynamicgoal_set(flag, player);
                        break;
                }
 
@@ -502,6 +502,7 @@ void ctf_Handle_Throw(entity player, entity receiver, int droptype)
                {
                        flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
                        ctf_Handle_Drop(flag, player, droptype);
+                       navigation_dynamicgoal_set(flag, player);
                        break;
                }
        }
@@ -1515,17 +1516,28 @@ void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
                head = head.ctf_worldflagnext;
        }
        if (head)
+       {
+               if (head.ctf_status == FLAG_CARRY)
+               {
+                       // adjust rating of our flag carrier depending on his health
+                       head = head.tag_entity;
+                       float f = bound(0, (head.health + head.armorvalue) / 100, 2) - 1;
+                       ratingscale += ratingscale * f * 0.1;
+               }
                navigation_routerating(this, head, ratingscale, 10000);
+       }
 }
 
 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
 {
+       // disabled because we always spawn waypoints for flags with waypoint_spawnforitem_force
+       /*
        if (!bot_waypoints_for_items)
        {
                havocbot_goalrating_ctf_enemyflag(this, ratingscale);
                return;
        }
-
+       */
        entity head;
 
        head = havocbot_ctf_find_enemy_flag(this);
@@ -1572,28 +1584,10 @@ void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector
        }
 }
 
-void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
-{
-       IL_EACH(g_items, it.bot_pickup,
-       {
-               // gather health and armor only
-               if (it.solid)
-               if (GetResourceAmount(it, RESOURCE_HEALTH) || GetResourceAmount(it, RESOURCE_ARMOR))
-               if (vdist(it.origin - org, <, sradius))
-               {
-                       // get the value of the item
-                       float t = it.bot_pickupevalfunc(this, it) * 0.0001;
-                       if (t > 0)
-                               navigation_routerating(this, it, t * ratingscale, 500);
-               }
-       });
-}
-
 void havocbot_ctf_reset_role(entity this)
 {
        float cdefense, cmiddle, coffense;
        entity mf, ef;
-       float c;
 
        if(IS_DEAD(this))
                return;
@@ -1622,15 +1616,30 @@ void havocbot_ctf_reset_role(entity this)
                return;
        }
 
-       // if there is only me on the team switch to offense
-       c = 0;
-       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
+       // if there is no one else on the team switch to offense
+       int count = 0;
+       // don't check if this bot is a player since it isn't true when the bot is added to the server
+       FOREACH_CLIENT(it != this && IS_PLAYER(it) && SAME_TEAM(it, this), { ++count; });
 
-       if(c==1)
+       if (count == 0)
        {
                havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
                return;
        }
+       else if (time < CS(this).jointime + 1)
+       {
+               // if bots spawn all at once set good default roles
+               if (count == 1)
+               {
+                       havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
+                       return;
+               }
+               else if (count == 2)
+               {
+                       havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+                       return;
+               }
+       }
 
        // Evaluate best position to take
        // Count mates on middle position
@@ -1648,6 +1657,25 @@ void havocbot_ctf_reset_role(entity this)
                havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
        else
                havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+
+       // if bots spawn all at once assign them a more appropriated role after a while
+       if (time < CS(this).jointime + 1 && count > 2)
+               this.havocbot_role_timeout = time + 10 + random() * 10;
+}
+
+bool havocbot_ctf_is_basewaypoint(entity item)
+{
+       if (item.classname != "waypoint")
+               return false;
+
+       entity head = ctf_worldflaglist;
+       while (head)
+       {
+               if (item == head.bot_basewaypoint)
+                       return true;
+               head = head.ctf_worldflagnext;
+       }
+       return false;
 }
 
 void havocbot_role_ctf_carrier(entity this)
@@ -1668,30 +1696,30 @@ void havocbot_role_ctf_carrier(entity this)
        {
                navigation_goalrating_start(this);
 
+               // role: carrier
+               entity mf = havocbot_ctf_find_flag(this);
+               vector base_org = mf.dropped_origin;
+               float base_rating = (mf.ctf_status == FLAG_BASE) ? 10000 : (vdist(this.origin - base_org, >, 100) ? 2000 : 1000);
                if(ctf_oneflag)
-                       havocbot_goalrating_ctf_enemybase(this, 50000);
+                       havocbot_goalrating_ctf_enemybase(this, base_rating);
                else
-                       havocbot_goalrating_ctf_ourbase(this, 50000);
+                       havocbot_goalrating_ctf_ourbase(this, base_rating);
+
+               // start collecting items very close to the bot but only inside of own base radius
+               if (vdist(this.origin - base_org, <, havocbot_middlepoint_radius))
+                       havocbot_goalrating_items(this, 15000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
 
-               if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
-                       havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
+               havocbot_goalrating_items(this, 10000, base_org, havocbot_middlepoint_radius * 0.5);
 
                navigation_goalrating_end(this);
 
                navigation_goalrating_timeout_set(this);
 
-               entity head = ctf_worldflaglist;
-               while (head)
-               {
-                       if (this.goalentity == head.bot_basewaypoint)
-                       {
-                               this.goalentity_lock_timeout = time + 5;
-                               break;
-                       }
-                       head = head.ctf_worldflagnext;
-               }
+               entity goal = this.goalentity;
+               if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
+                       this.goalentity_lock_timeout = time + ((this.bot_aimtarg) ? 2 : 3);
 
-               if (this.goalentity)
+               if (goal)
                        this.havocbot_cantfindflag = time + 10;
                else if (time > this.havocbot_cantfindflag)
                {
@@ -1727,11 +1755,15 @@ void havocbot_role_ctf_escort(entity this)
                this.havocbot_role_timeout = 0;
                return;
        }
+       if (ef.ctf_status == FLAG_DROPPED)
+       {
+               navigation_goalrating_timeout_expire(this, 1);
+               return;
+       }
 
        // If the flag carrier reached the base switch to defense
        mf = havocbot_ctf_find_flag(this);
-       if(mf.ctf_status!=FLAG_BASE)
-       if(vdist(ef.origin - mf.dropped_origin, <, 300))
+       if (mf.ctf_status != FLAG_BASE && vdist(ef.origin - mf.dropped_origin, <, 900))
        {
                havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
                return;
@@ -1756,9 +1788,10 @@ void havocbot_role_ctf_escort(entity this)
        {
                navigation_goalrating_start(this);
 
-               havocbot_goalrating_ctf_enemyflag(this, 30000);
-               havocbot_goalrating_ctf_ourstolenflag(this, 40000);
-               havocbot_goalrating_items(this, 10000, this.origin, 10000);
+               // role: escort
+               havocbot_goalrating_ctf_enemyflag(this, 10000);
+               havocbot_goalrating_ctf_ourstolenflag(this, 6000);
+               havocbot_goalrating_items(this, 21000, this.origin, 10000);
 
                navigation_goalrating_end(this);
 
@@ -1818,13 +1851,6 @@ void havocbot_role_ctf_offense(entity this)
                }
        }
 
-       // About to fail, switch to middlefield
-       if(GetResourceAmount(this, RESOURCE_HEALTH) < 50)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
-               return;
-       }
-
        // Set the role timeout if necessary
        if (!this.havocbot_role_timeout)
                this.havocbot_role_timeout = time + 120;
@@ -1839,10 +1865,10 @@ void havocbot_role_ctf_offense(entity this)
        {
                navigation_goalrating_start(this);
 
-               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
-               havocbot_goalrating_ctf_enemybase(this, 20000);
-               havocbot_goalrating_items(this, 5000, this.origin, 1000);
-               havocbot_goalrating_items(this, 1000, this.origin, 10000);
+               // role: offense
+               havocbot_goalrating_ctf_ourstolenflag(this, 10000);
+               havocbot_goalrating_ctf_enemybase(this, 10000);
+               havocbot_goalrating_items(this, 22000, this.origin, 10000);
 
                navigation_goalrating_end(this);
 
@@ -1888,15 +1914,20 @@ void havocbot_role_ctf_retriever(entity this)
 
        if (navigation_goalrating_timeout(this))
        {
-               float rt_radius;
-               rt_radius = 10000;
+               const float RT_RADIUS = 10000;
 
                navigation_goalrating_start(this);
 
-               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
-               havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
-               havocbot_goalrating_ctf_enemybase(this, 30000);
-               havocbot_goalrating_items(this, 500, this.origin, rt_radius);
+               // role: retriever
+               havocbot_goalrating_ctf_ourstolenflag(this, 10000);
+               havocbot_goalrating_ctf_droppedflags(this, 12000, this.origin, RT_RADIUS);
+               havocbot_goalrating_ctf_enemybase(this, 8000);
+               entity ef = havocbot_ctf_find_enemy_flag(this);
+               vector enemy_base_org = ef.dropped_origin;
+               // start collecting items very close to the bot but only inside of enemy base radius
+               if (vdist(this.origin - enemy_base_org, <, havocbot_middlepoint_radius))
+                       havocbot_goalrating_items(this, 27000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
+               havocbot_goalrating_items(this, 18000, this.origin, havocbot_middlepoint_radius);
 
                navigation_goalrating_end(this);
 
@@ -1945,15 +1976,20 @@ void havocbot_role_ctf_middle(entity this)
 
                navigation_goalrating_start(this);
 
-               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
-               havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
-               havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
-               havocbot_goalrating_items(this, 2500, this.origin, 10000);
-               havocbot_goalrating_ctf_enemybase(this, 2500);
+               // role: middle
+               havocbot_goalrating_ctf_ourstolenflag(this, 8000);
+               havocbot_goalrating_ctf_droppedflags(this, 9000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 25000, org, havocbot_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(this, 25000, org, havocbot_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(this, 18000, this.origin, 10000);
+               havocbot_goalrating_ctf_enemybase(this, 3000);
 
                navigation_goalrating_end(this);
 
+               entity goal = this.goalentity;
+               if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
+                       this.goalentity_lock_timeout = time + 2;
+
                navigation_goalrating_timeout_set(this);
        }
 }
@@ -2008,17 +2044,18 @@ void havocbot_role_ctf_defense(entity this)
                        }
                });
 
+               // role: defense
                if(closestplayer)
                if(DIFF_TEAM(closestplayer, this))
                if(vdist(org - this.origin, >, 1000))
                if(checkpvs(this.origin,closestplayer)||random()<0.5)
-                       havocbot_goalrating_ctf_ourbase(this, 30000);
+                       havocbot_goalrating_ctf_ourbase(this, 10000);
 
-               havocbot_goalrating_ctf_ourstolenflag(this, 20000);
-               havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
-               havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
-               havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
-               havocbot_goalrating_items(this, 5000, this.origin, 10000);
+               havocbot_goalrating_ctf_ourstolenflag(this, 5000);
+               havocbot_goalrating_ctf_droppedflags(this, 6000, org, havocbot_middlepoint_radius);
+               havocbot_goalrating_enemyplayers(this, 25000, org, havocbot_middlepoint_radius);
+               havocbot_goalrating_items(this, 25000, org, havocbot_middlepoint_radius);
+               havocbot_goalrating_items(this, 18000, this.origin, 10000);
 
                navigation_goalrating_end(this);
 
index 0cea02f2f2aef7325b0c7dc64a5bf13c1573cea2..b8f08a5a1e09b35f28aa9d3e4ae6b852cd59d196 100644 (file)
@@ -401,9 +401,9 @@ void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector or
                if(it.cnt > -1) // this is just being fought
                        navigation_routerating(this, it, ratingscale, 5000);
                else if(it.goalentity.cnt == 0) // unclaimed
-                       navigation_routerating(this, it, ratingscale * 0.5, 5000);
+                       navigation_routerating(this, it, ratingscale, 5000);
                else if(it.goalentity.team != this.team) // other team's point
-                       navigation_routerating(this, it, ratingscale * 0.2, 5000);
+                       navigation_routerating(this, it, ratingscale, 5000);
        });
 }
 
@@ -416,8 +416,8 @@ void havocbot_role_dom(entity this)
        {
                navigation_goalrating_start(this);
                havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
-               havocbot_goalrating_items(this, 8000, this.origin, 8000);
-               //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
+               havocbot_goalrating_items(this, 20000, this.origin, 8000);
+               //havocbot_goalrating_enemyplayers(this, 1500, this.origin, 2000);
                havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
                navigation_goalrating_end(this);
 
index 15726ada31d03b5442b12e15e175d34533af9768..6919518b8340ff1e65fa1f16d615eaed9eb7582b 100644 (file)
@@ -229,9 +229,10 @@ float freezetag_isEliminated(entity e)
 void(entity this) havocbot_role_ft_freeing;
 void(entity this) havocbot_role_ft_offense;
 
-void havocbot_goalrating_freeplayers(entity this, float ratingscale, vector org, float sradius)
+void havocbot_goalrating_ft_freeplayers(entity this, float ratingscale, vector org, float sradius)
 {
-       float t;
+       entity best_pl = NULL;
+       float best_dist2 = FLOAT_MAX;
        FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
                if (STAT(FROZEN, it) == 1)
                {
@@ -239,14 +240,24 @@ void havocbot_goalrating_freeplayers(entity this, float ratingscale, vector org,
                                continue;
                        navigation_routerating(this, it, ratingscale, 2000);
                }
-               else if(vdist(it.origin - org, >, 400)) // avoid gathering all teammates in one place
+               else if (best_dist2
+                       && GetResourceAmount(it, RESOURCE_HEALTH) < GetResourceAmount(this, RESOURCE_HEALTH) + 30
+                       && vlen2(it.origin - org) < best_dist2)
                {
                        // If teamate is not frozen still seek them out as fight better
                        // in a group.
-                       t = 0.2 * 150 / (GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR));
-                       navigation_routerating(this, it, t * ratingscale, 2000);
+                       best_dist2 = vlen2(it.origin - org);
+                       if (best_dist2 < 700 ** 2)
+                       {
+                               best_pl = NULL;
+                               best_dist2 = 0; // already close to a teammate
+                       }
+                       else
+                               best_pl = it;
                }
        });
+       if (best_pl)
+               navigation_routerating(this, best_pl, ratingscale / 2, 2000);
 }
 
 void havocbot_role_ft_offense(entity this)
@@ -259,10 +270,10 @@ void havocbot_role_ft_offense(entity this)
 
        // Count how many players on team are unfrozen.
        int unfrozen = 0;
-       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !(STAT(FROZEN, it) != 1), { unfrozen++; });
+       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !STAT(FROZEN, it), { unfrozen++; });
 
        // If only one left on team or if role has timed out then start trying to free players.
-       if (((unfrozen == 0) && (!STAT(FROZEN, this))) || (time > this.havocbot_role_timeout))
+       if ((unfrozen == 0 && !STAT(FROZEN, this)) || time > this.havocbot_role_timeout)
        {
                LOG_TRACE("changing role to freeing");
                this.havocbot_role = havocbot_role_ft_freeing;
@@ -273,9 +284,9 @@ void havocbot_role_ft_offense(entity this)
        if (navigation_goalrating_timeout(this))
        {
                navigation_goalrating_start(this);
-               havocbot_goalrating_items(this, 10000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
-               havocbot_goalrating_freeplayers(this, 9000, this.origin, 10000);
+               havocbot_goalrating_items(this, 12000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
+               havocbot_goalrating_ft_freeplayers(this, 9000, this.origin, 10000);
                havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
                navigation_goalrating_end(this);
 
@@ -302,9 +313,9 @@ void havocbot_role_ft_freeing(entity this)
        if (navigation_goalrating_timeout(this))
        {
                navigation_goalrating_start(this);
-               havocbot_goalrating_items(this, 8000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
-               havocbot_goalrating_freeplayers(this, 20000, this.origin, 10000);
+               havocbot_goalrating_items(this, 10000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 5000, this.origin, 10000);
+               havocbot_goalrating_ft_freeplayers(this, 20000, this.origin, 10000);
                havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
                navigation_goalrating_end(this);
 
@@ -557,6 +568,10 @@ MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
                        bot.havocbot_role = havocbot_role_ft_offense;
        }
 
+       // if bots spawn all at once assign them a more appropriated role after a while
+       if (time < CS(bot).jointime + 1)
+               bot.havocbot_role_timeout = time + 10 + random() * 10;
+
        return true;
 }
 
index c8bfbf25ae8513550434de0d0f21b0144afeb79e..7c0356b38d739d790cf150a4c93c71ae959bedeb 100644 (file)
@@ -64,7 +64,7 @@ void ka_RespawnBall(entity this) // runs whenever the ball needs to be relocated
        settouch(this, ka_TouchEvent);
        setthink(this, ka_RespawnBall);
        this.nextthink = time + autocvar_g_keepawayball_respawntime;
-       navigation_dynamicgoal_set(this);
+       navigation_dynamicgoal_set(this, NULL);
 
        Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1);
        Send_Effect(EFFECT_ELECTRO_COMBO, this.origin, '0 0 0', 1);
@@ -164,10 +164,11 @@ void ka_DropEvent(entity plyr) // runs any time that a player is supposed to los
        ball.effects &= ~EF_NODRAW;
        setorigin(ball, plyr.origin + '0 0 10');
        ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
-       entity e = ball.owner; ball.owner = NULL;
+       entity e = ball.owner;
+       ball.owner = NULL;
        e.ballcarried = NULL;
        GameRules_scoring_vip(e, false);
-       navigation_dynamicgoal_set(ball);
+       navigation_dynamicgoal_set(ball, e);
 
        // reset the player effects
        plyr.glow_trail = false;
@@ -212,20 +213,15 @@ void ka_Reset(entity this)
 
 void havocbot_goalrating_ball(entity this, float ratingscale, vector org)
 {
-       float t;
        entity ball_owner;
        ball_owner = ka_ball.owner;
 
        if (ball_owner == this)
                return;
 
-       // If ball is carried by player then hunt them down.
        if (ball_owner)
-       {
-               t = (GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR)) / (GetResourceAmount(ball_owner, RESOURCE_HEALTH) + GetResourceAmount(ball_owner, RESOURCE_ARMOR));
-               navigation_routerating(this, ball_owner, t * ratingscale, 2000);
-       }
-       else // Ball has been dropped so collect.
+               navigation_routerating(this, ball_owner, ratingscale, 2000);
+       else
                navigation_routerating(this, ka_ball, ratingscale, 2000);
 }
 
@@ -238,7 +234,7 @@ void havocbot_role_ka_carrier(entity this)
        {
                navigation_goalrating_start(this);
                havocbot_goalrating_items(this, 10000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
                havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
                navigation_goalrating_end(this);
 
@@ -261,8 +257,8 @@ void havocbot_role_ka_collector(entity this)
        {
                navigation_goalrating_start(this);
                havocbot_goalrating_items(this, 10000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 1000, this.origin, 10000);
-               havocbot_goalrating_ball(this, 20000, this.origin);
+               havocbot_goalrating_enemyplayers(this, 500, this.origin, 10000);
+               havocbot_goalrating_ball(this, 8000, this.origin);
                navigation_goalrating_end(this);
 
                navigation_goalrating_timeout_set(this);
index 5e0d3fcbbfaa0e475a8b6d0f3f87dc99ae18ac32..5af4c45b7ddc9b7752f5aee5eb8a0c194efe08e5 100644 (file)
@@ -299,7 +299,7 @@ void kh_Key_Detach(entity key) // runs every time a key is dropped or lost. Runs
        key.takedamage = DAMAGE_YES;
        // let key.team stay
        key.modelindex = kh_key_dropped;
-       navigation_dynamicgoal_set(key);
+       navigation_dynamicgoal_set(key, key.owner);
        key.kh_previous_owner = key.owner;
        key.kh_previous_owner_playerid = key.owner.playerid;
 }
@@ -1077,9 +1077,9 @@ void havocbot_role_kh_carrier(entity this)
                navigation_goalrating_start(this);
 
                if(kh_Key_AllOwnedByWhichTeam() == this.team)
-                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // bring home
+                       havocbot_goalrating_kh(this, 10, 0.1, 0.05); // bring home
                else
-                       havocbot_goalrating_kh(this, 4, 4, 1); // play defensively
+                       havocbot_goalrating_kh(this, 4, 4, 0.5); // play defensively
 
                navigation_goalrating_end(this);
 
@@ -1117,11 +1117,11 @@ void havocbot_role_kh_defense(entity this)
 
                key_owner_team = kh_Key_AllOwnedByWhichTeam();
                if(key_owner_team == this.team)
-                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend key carriers
+                       havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend key carriers
                else if(key_owner_team == -1)
-                       havocbot_goalrating_kh(this, 4, 1, 0.1); // play defensively
+                       havocbot_goalrating_kh(this, 4, 1, 0.05); // play defensively
                else
-                       havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
+                       havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK ANYWAY
 
                navigation_goalrating_end(this);
 
@@ -1160,11 +1160,11 @@ void havocbot_role_kh_offense(entity this)
 
                key_owner_team = kh_Key_AllOwnedByWhichTeam();
                if(key_owner_team == this.team)
-                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
+                       havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend anyway
                else if(key_owner_team == -1)
-                       havocbot_goalrating_kh(this, 0.1, 1, 4); // play offensively
+                       havocbot_goalrating_kh(this, 0.1, 1, 2); // play offensively
                else
-                       havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK! EMERGENCY!
+                       havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK! EMERGENCY!
 
                navigation_goalrating_end(this);
 
@@ -1209,11 +1209,11 @@ void havocbot_role_kh_freelancer(entity this)
 
                int key_owner_team = kh_Key_AllOwnedByWhichTeam();
                if(key_owner_team == this.team)
-                       havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
+                       havocbot_goalrating_kh(this, 10, 0.1, 0.05); // defend anyway
                else if(key_owner_team == -1)
-                       havocbot_goalrating_kh(this, 1, 10, 4); // prefer dropped keys
+                       havocbot_goalrating_kh(this, 1, 10, 2); // prefer dropped keys
                else
-                       havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
+                       havocbot_goalrating_kh(this, 0.1, 0.1, 5); // ATTACK ANYWAY
 
                navigation_goalrating_end(this);
 
index c20a4fac74a160b62e9d876a35092fd6afc5a2b4..6d719eb977c512997131382e30a0698e0fcd14c0 100644 (file)
@@ -1264,71 +1264,26 @@ void Onslaught_RoundStart()
 
 // NOTE: LEGACY CODE, needs to be re-written!
 
-void havocbot_goalrating_ons_offenseitems(entity this, float ratingscale, vector org, float sradius)
-{
-       bool needarmor = false, needweapons = false;
-
-       // Needs armor/health?
-       if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
-               needarmor = true;
-
-       // Needs weapons?
-       int c = 0;
-       FOREACH(Weapons, it != WEP_Null, {
-               if(STAT(WEAPONS, this) & (it.m_wepset))
-               if(++c >= 4)
-                       break;
-       });
-
-       if(c<4)
-               needweapons = true;
-
-       if(!needweapons && !needarmor)
-               return;
-
-       LOG_DEBUG(this.netname, " needs weapons ", ftos(needweapons));
-       LOG_DEBUG(this.netname, " needs armor ", ftos(needarmor));
-
-       // See what is around
-       IL_EACH(g_items, it.bot_pickup,
-       {
-               // gather health and armor only
-               if (it.solid)
-               if ( ((GetResourceAmount(it, RESOURCE_HEALTH) || GetResourceAmount(it, RESOURCE_ARMOR)) && needarmor) || (STAT(WEAPONS, it) && needweapons ) )
-               if (vdist(it.origin - org, <, sradius))
-               {
-                       int t = it.bot_pickupevalfunc(this, it);
-                       if (t > 0)
-                               navigation_routerating(this, it, t * ratingscale, 500);
-               }
-       });
-}
-
 void havocbot_role_ons_setrole(entity this, int role)
 {
-       LOG_DEBUG(this.netname," switched to ");
        switch(role)
        {
                case HAVOCBOT_ONS_ROLE_DEFENSE:
-                       LOG_DEBUG("defense");
+                       LOG_DEBUG(this.netname, " switched to defense");
                        this.havocbot_role = havocbot_role_ons_defense;
-                       this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE;
                        this.havocbot_role_timeout = 0;
                        break;
                case HAVOCBOT_ONS_ROLE_ASSISTANT:
-                       LOG_DEBUG("assistant");
+                       LOG_DEBUG(this.netname, " switched to assistant");
                        this.havocbot_role = havocbot_role_ons_assistant;
-                       this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT;
                        this.havocbot_role_timeout = 0;
                        break;
                case HAVOCBOT_ONS_ROLE_OFFENSE:
-                       LOG_DEBUG("offense");
+                       LOG_DEBUG(this.netname, " switched to offense");
                        this.havocbot_role = havocbot_role_ons_offense;
-                       this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE;
                        this.havocbot_role_timeout = 0;
                        break;
        }
-       LOG_DEBUG("");
 }
 
 void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale)
@@ -1353,9 +1308,9 @@ void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale
 
                // Count team mates interested in this control point
                // (easier and cleaner than keeping counters per cp and teams)
-               FOREACH_CLIENT(IS_PLAYER(it), {
+               FOREACH_CLIENT(it != this && IS_PLAYER(it), {
                        if(SAME_TEAM(it, this))
-                       if(it.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE)
+                       if(it.havocbot_role == havocbot_role_ons_offense)
                        if(it.havocbot_ons_target == cp2)
                                ++c;
                });
@@ -1366,7 +1321,7 @@ void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale
        }
 
        // We'll consider only the best case
-       bestvalue = 99999999999;
+       bestvalue = FLOAT_MAX;
        cp = NULL;
        for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext)
        {
@@ -1392,23 +1347,21 @@ void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale
                // Rate waypoints near it
                found = false;
                best = NULL;
-               bestvalue = 99999999999;
-               for(radius=0; radius<1000 && !found; radius+=500)
+               bestvalue = FLOAT_MAX;
+               for (radius = 500; radius <= 1000 && !found; radius += 500)
                {
-                       for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
+                       IL_EACH(g_waypoints, vdist(cp.origin - it.origin, <, radius),
                        {
-                               if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
-                               if(wp.classname=="waypoint")
-                               if(checkpvs(wp.origin,cp))
+                               if (!(it.wpflags & WAYPOINTFLAG_GENERATED) && checkpvs(it.origin, cp))
                                {
                                        found = true;
-                                       if(wp.cnt<bestvalue)
+                                       if (it.cnt < bestvalue)
                                        {
-                                               best = wp;
-                                               bestvalue = wp.cnt;
+                                               best = it;
+                                               bestvalue = it.cnt;
                                        }
                                }
-                       }
+                       });
                }
 
                if(best)
@@ -1431,22 +1384,7 @@ void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale
        {
                // Should be touched
                LOG_DEBUG(this.netname, " found a touchable controlpoint at ", vtos(cp.origin));
-               found = false;
-
-               // Look for auto generated waypoint
-               if (!bot_waypoints_for_items)
-               for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
-               {
-                       if(wp.classname=="waypoint")
-                       {
-                               navigation_routerating(this, wp, ratingscale, 10000);
-                               found = true;
-                       }
-               }
-
-               // Nothing found, rate the controlpoint itself
-               if (!found)
-                       navigation_routerating(this, cp, ratingscale, 10000);
+               navigation_routerating(this, cp, ratingscale * 2, 10000);
        }
 }
 
@@ -1454,7 +1392,7 @@ bool havocbot_goalrating_ons_generator_attack(entity this, float ratingscale)
 {
        entity g, wp, bestwp;
        bool found;
-       int best;
+       int bestvalue;
 
        for(g = ons_worldgeneratorlist; g; g = g.ons_worldgeneratornext)
        {
@@ -1465,21 +1403,20 @@ bool havocbot_goalrating_ons_generator_attack(entity this, float ratingscale)
                // Rate waypoints near it
                found = false;
                bestwp = NULL;
-               best = 99999999999;
+               bestvalue = FLOAT_MAX;
 
-               for(wp=findradius(g.origin,400); wp; wp=wp.chain)
+               IL_EACH(g_waypoints, vdist(g.origin - it.origin, <, 400),
                {
-                       if(wp.classname=="waypoint")
-                       if(checkpvs(wp.origin,g))
+                       if (checkpvs(it.origin, g))
                        {
                                found = true;
-                               if(wp.cnt<best)
+                               if (it.cnt < bestvalue)
                                {
-                                       bestwp = wp;
-                                       best = wp.cnt;
+                                       bestwp = it;
+                                       bestvalue = it.cnt;
                                }
                        }
-               }
+               });
 
                if(bestwp)
                {
@@ -1532,9 +1469,9 @@ void havocbot_role_ons_offense(entity this)
        {
                navigation_goalrating_start(this);
                havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
-               if(!havocbot_goalrating_ons_generator_attack(this, 20000))
-                       havocbot_goalrating_ons_controlpoints_attack(this, 20000);
-               havocbot_goalrating_ons_offenseitems(this, 10000, this.origin, 10000);
+               if(!havocbot_goalrating_ons_generator_attack(this, 10000))
+                       havocbot_goalrating_ons_controlpoints_attack(this, 10000);
+               havocbot_goalrating_items(this, 25000, this.origin, 10000);
                navigation_goalrating_end(this);
 
                navigation_goalrating_timeout_set(this);
index 5c7fd469735b1e38cad2abdf8e11165d9ffb3d66..7a9b192a5a7b041e313c807dd464b04d3e0c3e98 100644 (file)
@@ -80,7 +80,6 @@ const int HAVOCBOT_ONS_ROLE_OFFENSE   = 8;
 
 .entity havocbot_ons_target;
 
-.int havocbot_role_flags;
 .float havocbot_attack_time;
 
 void havocbot_role_ons_defense(entity this);
index 1c10afa488a581065888af5fa7a7324e82ca2eb6..9de1235361787678286474044f004ddbc8e4c31c 100644 (file)
@@ -10,7 +10,7 @@ CLASS(Powerup, Pickup)
 #ifdef SVQC
     ATTRIB(Powerup, m_mins, vector, '-16 -16 0');
     ATTRIB(Powerup, m_maxs, vector, '16 16 80');
-    ATTRIB(Powerup, m_botvalue, int, 20000);
+    ATTRIB(Powerup, m_botvalue, int, 11000);
     ATTRIB(Powerup, m_itemflags, int, FL_POWERUP);
     ATTRIB(Powerup, m_respawntime, float(), GET(g_pickup_respawntime_powerup));
     ATTRIB(Powerup, m_respawntimejitter, float(), GET(g_pickup_respawntimejitter_powerup));
index 0ecd7b87725b41f7ca894570f7343836a845d3da..0cddd3b27a01df165978105a753016033b7b5c1c 100644 (file)
@@ -90,7 +90,7 @@ float havocbot_symmetry_origin_order;
 .entity bot_basewaypoint;
 .bool navigation_dynamicgoal;
 void navigation_dynamicgoal_init(entity this, bool initially_static);
-void navigation_dynamicgoal_set(entity this);
+void navigation_dynamicgoal_set(entity this, entity dropper);
 void navigation_dynamicgoal_unset(entity this);
 entity navigation_findnearestwaypoint(entity ent, float walkfromwp);
 void navigation_goalrating_end(entity this);
index 3a9befde403f9ee2a8fb6c2f99076af8ca433d9f..768aa6daee2bb2719c759eb5f9f7dc4354f5f264 100644 (file)
@@ -154,11 +154,6 @@ bool bot_shouldattack(entity this, entity targ)
 
 void bot_lagfunc(entity this, float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4)
 {
-       if(this.flags & FL_INWATER)
-       {
-               this.bot_aimtarg = NULL;
-               return;
-       }
        this.bot_aimtarg = e1;
        this.bot_aimlatency = CS(this).ping; // FIXME?  Shouldn't this be in the lag item?
        //this.bot_aimorigin = v1;
@@ -180,6 +175,8 @@ void bot_aimdir(entity this, vector v, float maxfiredeviation)
        float dist, delta_t, blend;
        vector desiredang, diffang;
 
+       this.bot_aimdir_executed = true;
+
        //dprint("aim ", this.netname, ": old:", vtos(this.v_angle));
        // make sure v_angle is sane first
        this.v_angle_y = this.v_angle.y - floor(this.v_angle.y / 360) * 360;
@@ -245,6 +242,7 @@ void bot_aimdir(entity this, vector v, float maxfiredeviation)
                + this.bot_4th_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_4th
                + this.bot_5th_order_aimfilter * autocvar_bot_ai_aimskill_order_mix_5th
        );
+       desiredang.x = bound(-90, desiredang.x, 90);
 
        // calculate turn angles
        diffang = desiredang - this.bot_mouseaim;
index b8b35f1c203a08968e3b82e7029bbfd17109e6dd..1eb71bc7ff58fee1cdaa2eecdf81003495eb5d91 100644 (file)
@@ -59,6 +59,7 @@ vector shotdir;
 .vector lag5_vec3;
 .vector lag5_vec4;
 
+.bool bot_aimdir_executed;
 .float bot_badaimtime;
 .float bot_aimthinktime;
 .float bot_prevaimtime;
index 2b427f4fbc81580d91cd4037ec960a002db40d68..f03bdfc74db470c44ba0927aef9383dd079d7bf1 100644 (file)
@@ -123,6 +123,9 @@ void bot_think(entity this)
        // if dead, just wait until we can respawn
        if (IS_DEAD(this))
        {
+               if (bot_waypoint_queue_owner == this)
+                       bot_waypoint_queue_owner = NULL;
+               this.aistatus = 0;
                CS(this).movement = '0 0 0';
                if (this.deadflag == DEAD_DEAD)
                {
index 357b37da00337b99e929787cafda0741674ce1e3..f5ba20c8a41b1acbe04a1368748eb8cbede98d30 100644 (file)
@@ -18,6 +18,7 @@
 #include <common/items/_mod.qh>
 #include <common/wepent.qh>
 
+#include <common/mapobjects/func/ladder.qh>
 #include <common/mapobjects/teleporters.qh>
 #include <common/mapobjects/trigger/jumppads.qh>
 
@@ -104,6 +105,9 @@ void havocbot_ai(entity this)
        }
        havocbot_aim(this);
        lag_update(this);
+
+       this.bot_aimdir_executed = false;
+
        if (this.bot_aimtarg)
        {
                this.aistatus |= AI_STATUS_ATTACKING;
@@ -140,48 +144,17 @@ void havocbot_ai(entity this)
        {
                this.aistatus |= AI_STATUS_ROAMING;
                this.aistatus &= ~AI_STATUS_ATTACKING;
-
-               vector now, next;
-               float aimdistance,skillblend,distanceblend,blend;
-
-               vector v = get_closer_dest(this.goalcurrent, this.origin);
-               if(this.goalcurrent.wpisbox)
-               {
-                       // avoid a glitch when bot is teleported but teleport waypoint isn't removed yet
-                       if(this.goalstack02 && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT
-                       && this.lastteleporttime > 0 && time - this.lastteleporttime < 0.15)
-                               v = (this.goalstack02.absmin + this.goalstack02.absmax) * 0.5;
-                       // aim to teleport origin if bot is inside teleport waypoint but hasn't touched the real teleport yet
-                       else if(boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin, this.origin))
-                               v = this.goalcurrent.origin;
-               }
-               next = now = v - (this.origin + this.view_ofs);
-               aimdistance = vlen(now);
-
-               //dprint(this.goalstack01.classname,etos(this.goalstack01),"\n");
-               if(
-                       this.goalstack01 != this && this.goalstack01 && !wasfreed(this.goalstack01) && ((this.aistatus & AI_STATUS_RUNNING) == 0) &&
-                       !(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
-               )
-                       next = ((this.goalstack01.absmin + this.goalstack01.absmax) * 0.5) - (this.origin + this.view_ofs);
-
-               skillblend=bound(0,(skill+this.bot_moveskill-2.5)*0.5,1); //lower skill player can't preturn
-               distanceblend=bound(0,aimdistance/autocvar_bot_ai_keyboard_distance,1);
-               blend = skillblend * (1-distanceblend);
-               //v = (now * (distanceblend) + next * (1-distanceblend)) * (skillblend) + now * (1-skillblend);
-               //v = now * (distanceblend) * (skillblend) + next * (1-distanceblend) * (skillblend) + now * (1-skillblend);
-               //v = now * ((1-skillblend) + (distanceblend) * (skillblend)) + next * (1-distanceblend) * (skillblend);
-               v = now + blend * (next - now);
-               //dprint(etos(this), " ");
-               //dprint(vtos(now), ":", vtos(next), "=", vtos(v), " (blend ", ftos(blend), ")\n");
-               //v = now * (distanceblend) + next * (1-distanceblend);
-               if (this.waterlevel < WATERLEVEL_SWIMMING)
-                       v.z = 0;
-               //dprint("walk at:", vtos(v), "\n");
-               //te_lightning2(NULL, this.origin, this.goalcurrent.origin);
-               bot_aimdir(this, v, -1);
        }
+
        havocbot_movetogoal(this);
+       if (!this.bot_aimdir_executed && this.goalcurrent)
+       {
+               // Heading
+               vector dir = get_closer_dest(this.goalcurrent, this.origin);
+               dir -= this.origin + this.view_ofs;
+               dir.z = 0;
+               bot_aimdir(this, dir, -1);
+       }
 
        // if the bot is not attacking, consider reloading weapons
        if (!(this.aistatus & AI_STATUS_ATTACKING))
@@ -488,6 +461,7 @@ void havocbot_movetogoal(entity this)
        vector flatdir;
        vector evadeobstacle;
        vector evadelava;
+       float dodge_enemy_factor = 1;
        float maxspeed;
        //float dist;
        vector dodge;
@@ -567,8 +541,8 @@ void havocbot_movetogoal(entity this)
                if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
                {
                        this.aistatus |= AI_STATUS_OUT_JUMPPAD;
-                       navigation_poptouchedgoals(this);
-                       return;
+                       if(navigation_poptouchedgoals(this))
+                               return;
                }
                else if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
                {
@@ -695,7 +669,8 @@ void havocbot_movetogoal(entity this)
 
                        return;
                }
-               else if(GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR) > ROCKETJUMP_DAMAGE())
+               else if(!this.jumppadcount && !this.goalcurrent.wphardwired
+                       && GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR) > ROCKETJUMP_DAMAGE())
                {
                        if(this.velocity.z < 0)
                        {
@@ -800,19 +775,28 @@ void havocbot_movetogoal(entity this)
        if (this.goalcurrent == this.goalentity && this.goalentity_lock_timeout > time)
                locked_goal = true;
 
-       navigation_shortenpath(this);
+       if (navigation_shortenpath(this))
+       {
+               if (vdist(this.origin - this.goalcurrent_prev.origin, <, 50)
+                       && navigation_goalrating_timeout_can_be_anticipated(this))
+                       navigation_goalrating_timeout_force(this);
+       }
 
-       if (IS_MOVABLE(this.goalcurrent))
+       bool goalcurrent_can_be_removed = false;
+       if (IS_PLAYER(this.goalcurrent) || IS_MONSTER(this.goalcurrent))
        {
-               if (IS_DEAD(this.goalcurrent))
+               bool freeze_state_changed = (boolean(STAT(FROZEN, this.goalentity)) != this.goalentity_shouldbefrozen);
+               if (IS_DEAD(this.goalcurrent) || (this.goalentity == this.goalcurrent && freeze_state_changed))
                {
+                       goalcurrent_can_be_removed = true;
+                       // don't remove if not visible
                        if (checkpvs(this.origin + this.view_ofs, this.goalcurrent))
                        {
                                navigation_goalrating_timeout_force(this);
                                return;
                        }
                }
-               else if (this.bot_tracewalk_time < time)
+               else if (!(STAT(FROZEN, this.goalentity)) && this.bot_tracewalk_time < time)
                {
                        set_tracewalk_dest(this.goalcurrent, this.origin, true);
                        if (!(trace_ent == this || tracewalk(this, this.origin, this.mins, this.maxs,
@@ -824,6 +808,7 @@ void havocbot_movetogoal(entity this)
                        this.bot_tracewalk_time = max(time, this.bot_tracewalk_time) + 0.25;
                }
        }
+
        if(!locked_goal)
        {
                // optimize path finding by anticipating goalrating when bot is near a waypoint;
@@ -833,7 +818,7 @@ void havocbot_movetogoal(entity this)
                {
                        if (this.goalcurrent)
                        {
-                               if (IS_MOVABLE(this.goalcurrent) && IS_DEAD(this.goalcurrent))
+                               if (goalcurrent_can_be_removed)
                                {
                                        // remove even if not visible
                                        navigation_goalrating_timeout_force(this);
@@ -873,29 +858,52 @@ void havocbot_movetogoal(entity this)
 
        bool bunnyhop_forbidden = false;
        vector destorg = get_closer_dest(this.goalcurrent, this.origin);
-
-       // in case bot ends up inside the teleport waypoint without touching
-       // the teleport itself, head to the teleport origin
-       if(this.goalcurrent.wpisbox && boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin + eZ * this.mins.z, this.origin + eZ * this.maxs.z))
+       if (this.jumppadcount && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
        {
-               bunnyhop_forbidden = true;
+               // if bot used the jumppad, push towards jumppad origin until jumppad waypoint gets removed
                destorg = this.goalcurrent.origin;
-               if(destorg.z > this.origin.z)
-                       PHYS_INPUT_BUTTON_JUMP(this) = true;
+       }
+       else if (this.goalcurrent.wpisbox)
+       {
+               // if bot is inside the teleport waypoint, head to teleport origin until teleport gets used
+               // do it even if bot is on a ledge above a teleport/jumppad so it doesn't get stuck
+               if (boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin + eZ * this.mins.z, this.origin + eZ * this.maxs.z)
+                       || (this.absmin.z > destorg.z && destorg.x == this.origin.x && destorg.y == this.origin.y))
+               {
+                       bunnyhop_forbidden = true;
+                       destorg = this.goalcurrent.origin;
+                       if(destorg.z > this.origin.z)
+                               PHYS_INPUT_BUTTON_JUMP(this) = true;
+               }
        }
 
        diff = destorg - this.origin;
 
-       // 1. stop if too close to target player (even if frozen)
-       // 2. stop if the locked goal has been reached
-       if ((IS_PLAYER(this.goalcurrent) && vdist(diff, <, 80))
-               || (this.goalcurrent == this.goalentity && time < this.goalentity_lock_timeout && vdist(diff, <, 10)))
+       if (this.goalcurrent == this.goalentity && time < this.goalentity_lock_timeout && vdist(diff, <, 10))
        {
+               // stop if the locked goal has been reached
                destorg = this.origin;
-               diff = '0 0 0';
+               diff = dir = '0 0 0';
        }
-
-       dir = normalize(diff);
+       else if (IS_PLAYER(this.goalcurrent) || IS_MONSTER(this.goalcurrent))
+       {
+               if (vdist(diff, <, 80))
+               {
+                       // stop if too close to target player (even if frozen)
+                       destorg = this.origin;
+                       diff = dir = '0 0 0';
+               }
+               else
+               {
+                       // move destorg out of target players, otherwise bot will consider them
+                       // an obstacle that needs to be jumped (especially if frozen)
+                       dir = normalize(diff);
+                       destorg -= dir * PL_MAX_CONST.x * M_SQRT2;
+                       diff = destorg - this.origin;
+               }
+       }
+       else
+               dir = normalize(diff);
        flatdir = (diff.z == 0) ? dir : normalize(vec2(diff));
 
        //if (this.bot_dodgevector_time < time)
@@ -907,9 +915,9 @@ void havocbot_movetogoal(entity this)
 
                this.aistatus &= ~AI_STATUS_DANGER_AHEAD;
                makevectors(this.v_angle.y * '0 1 0');
-               if (this.waterlevel)
+               if (this.waterlevel > WATERLEVEL_WETFEET)
                {
-                       if(this.waterlevel>WATERLEVEL_SWIMMING)
+                       if (this.waterlevel > WATERLEVEL_SWIMMING)
                        {
                                if(!this.goalcurrent)
                                        this.aistatus |= AI_STATUS_OUT_WATER;
@@ -920,7 +928,7 @@ void havocbot_movetogoal(entity this)
                        {
                                dir = flatdir;
                                if(this.velocity.z >= 0 && !(this.watertype == CONTENT_WATER && destorg.z < this.origin.z) &&
-                                       ( !(this.waterlevel == WATERLEVEL_WETFEET && this.watertype == CONTENT_WATER) || this.aistatus & AI_STATUS_OUT_WATER))
+                                       (this.aistatus & AI_STATUS_OUT_WATER))
                                        PHYS_INPUT_BUTTON_JUMP(this) = true;
                                else
                                        PHYS_INPUT_BUTTON_JUMP(this) = false;
@@ -936,16 +944,20 @@ void havocbot_movetogoal(entity this)
                        // jump if going toward an obstacle that doesn't look like stairs we
                        // can walk up directly
                        vector deviation = '0 0 0';
-                       if (this.velocity)
+                       float current_speed = vlen(vec2(this.velocity));
+                       if (current_speed < maxspeed * 0.2)
+                               current_speed = maxspeed * 0.2;
+                       else
                        {
                                deviation = vectoangles(diff) - vectoangles(this.velocity);
                                while (deviation.y < -180) deviation.y += 360;
                                while (deviation.y > 180) deviation.y -= 360;
                        }
+                       float turning = false;
                        vector flat_diff = vec2(diff);
-                       offset = max(32, vlen(vec2(this.velocity)) * cos(deviation.y * DEG2RAD) * 0.2) * flatdir;
+                       offset = max(32, current_speed * cos(deviation.y * DEG2RAD) * 0.3) * flatdir;
                        vector actual_destorg = this.origin + offset;
-                       if (!this.goalstack01 || this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
+                       if (!this.goalstack01 || this.goalcurrent.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_LADDER))
                        {
                                if (vlen2(flat_diff) < vlen2(offset))
                                {
@@ -962,24 +974,53 @@ void havocbot_movetogoal(entity this)
                        {
                                vector next_goal_org = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
                                vector next_dir = normalize(vec2(next_goal_org - destorg));
-                               float next_dist = vlen(vec2(this.origin + offset - destorg));
-                               actual_destorg = vec2(destorg) + next_dist * next_dir;
+                               float dist = vlen(vec2(this.origin + offset - destorg));
+                               // if current and next goal are close to each other make sure
+                               // actual_destorg isn't set beyond next_goal_org
+                               if (dist ** 2 > vlen2(vec2(next_goal_org - destorg)))
+                                       actual_destorg = next_goal_org;
+                               else
+                                       actual_destorg = vec2(destorg) + dist * next_dir;
                                actual_destorg.z = this.origin.z;
+                               turning = true;
                        }
 
-                       tracebox(this.origin, this.mins, this.maxs, actual_destorg, false, this);
-                       if (trace_fraction < 1)
-                       if (trace_plane_normal.z < 0.7)
+                       LABEL(jump_check);
+                       dir = flatdir = normalize(actual_destorg - this.origin);
+
+                       if (turning || fabs(deviation.y) < 50) // don't even try to jump if deviation is too high
                        {
-                               s = trace_fraction;
-                               tracebox(this.origin + stepheightvec, this.mins, this.maxs, actual_destorg + stepheightvec, false, this);
-                               if (trace_fraction < s + 0.01)
-                               if (trace_plane_normal.z < 0.7)
+                               tracebox(this.origin, this.mins, this.maxs, actual_destorg, false, this);
+                               if (trace_fraction < 1 && trace_plane_normal.z < 0.7)
                                {
                                        s = trace_fraction;
-                                       tracebox(this.origin + jumpstepheightvec, this.mins, this.maxs, actual_destorg + jumpstepheightvec, false, this);
-                                       if (trace_fraction > s)
-                                               PHYS_INPUT_BUTTON_JUMP(this) = true;
+                                       tracebox(this.origin + stepheightvec, this.mins, this.maxs, actual_destorg + stepheightvec, false, this);
+                                       if (trace_fraction < s + 0.01 && trace_plane_normal.z < 0.7)
+                                       {
+                                               // found an obstacle
+                                               if (turning && fabs(deviation.y) > 5)
+                                               {
+                                                       // check if the obstacle is still there without turning
+                                                       actual_destorg = destorg;
+                                                       turning = false;
+                                                       this.bot_tracewalk_time = time + 0.25;
+                                                       goto jump_check;
+                                               }
+                                               s = trace_fraction;
+                                               // don't artificially reduce max jump height in real-time
+                                               // (jumpstepheightvec is reduced a bit to make the jumps easy in tracewalk)
+                                               vector jump_height = (IS_ONGROUND(this)) ? stepheightvec + jumpheight_vec : jumpstepheightvec;
+                                               tracebox(this.origin + jump_height, this.mins, this.maxs, actual_destorg + jump_height, false, this);
+                                               if (trace_fraction > s)
+                                                       PHYS_INPUT_BUTTON_JUMP(this) = true;
+                                               else
+                                               {
+                                                       jump_height = stepheightvec + jumpheight_vec / 2;
+                                                       tracebox(this.origin + jump_height, this.mins, this.maxs, actual_destorg + jump_height, false, this);
+                                                       if (trace_fraction > s)
+                                                               PHYS_INPUT_BUTTON_JUMP(this) = true;
+                                               }
+                                       }
                                }
                        }
 
@@ -1066,6 +1107,18 @@ void havocbot_movetogoal(entity this)
                                if(IS_PLAYER(this.goalcurrent))
                                        unreachable = true;
                        }
+
+                       // slow down if bot is in the air and goal is under it
+                       if (!this.goalcurrent.wphardwired
+                               && vdist(flat_diff, <, 250) && this.origin.z - destorg.z > 120
+                               && (!IS_ONGROUND(this) || vdist(vec2(this.velocity), >, maxspeed * 0.3)))
+                       {
+                               // tracebox wouldn't work when bot is still on the ledge
+                               traceline(this.origin, this.origin - '0 0 200', true, this);
+                               if (this.origin.z - trace_endpos.z > 120)
+                                       evadeobstacle = normalize(this.velocity) * -1;
+                       }
+
                        if(unreachable)
                        {
                                navigation_clearroute(this);
@@ -1076,31 +1129,64 @@ void havocbot_movetogoal(entity this)
                }
 
                dodge = havocbot_dodge(this);
-               dodge = dodge * bound(0,0.5+(skill+this.bot_dodgeskill)*0.1,1);
+               if (dodge)
+                       dodge *= bound(0, 0.5 + (skill + this.bot_dodgeskill) * 0.1, 1);
+               dodge += evadeobstacle + evadelava;
                evadelava = evadelava * bound(1,3-(skill+this.bot_dodgeskill),3); //Noobs fear lava a lot and take more distance from it
-               traceline(this.origin, ( ( this.enemy.absmin + this.enemy.absmax ) * 0.5 ), true, NULL);
-               if(IS_PLAYER(trace_ent))
-                       dir = dir * bound(0,(skill+this.bot_dodgeskill)/7,1);
-
-               dir = normalize(dir + dodge + evadeobstacle + evadelava);
+               if (this.enemy)
+               {
+                       traceline(this.origin, (this.enemy.absmin + this.enemy.absmax) * 0.5, true, NULL);
+                       if (IS_PLAYER(trace_ent))
+                               dodge_enemy_factor = bound(0, (skill + this.bot_dodgeskill) / 7, 1);
+               }
        //      this.bot_dodgevector = dir;
        //      this.bot_dodgevector_jumpbutton = PHYS_INPUT_BUTTON_JUMP(this);
        }
 
+       float ladder_zdir = 0;
        if(time < this.ladder_time)
        {
                if(this.goalcurrent.origin.z + this.goalcurrent.mins.z > this.origin.z + this.mins.z)
                {
                        if(this.origin.z + this.mins.z  < this.ladder_entity.origin.z + this.ladder_entity.maxs.z)
-                               dir.z = 1;
+                               ladder_zdir = 1;
                }
                else
                {
                        if(this.origin.z + this.mins.z  > this.ladder_entity.origin.z + this.ladder_entity.mins.z)
-                               dir.z = -1;
+                               ladder_zdir = -1;
+               }
+               if (ladder_zdir)
+               {
+                       if (vdist(flatdir, <, 15))
+                               dir = ladder_zdir * '0 0 1';
+                       else
+                       {
+                               dir.z = ladder_zdir * 1.3;
+                               dir = normalize(dir);
+                       }
                }
        }
 
+       if (this.goalcurrent.wpisbox
+               && boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin, this.origin))
+       {
+               // bot is inside teleport waypoint but hasn't touched the real teleport yet
+               // head to teleport origin
+               dir = (this.goalcurrent.origin - this.origin);
+               dir.z = 0;
+               dir = normalize(dir);
+       }
+
+       if (!this.bot_aimdir_executed)
+               bot_aimdir(this, dir, -1);
+
+       if (!ladder_zdir)
+       {
+               dir *= dodge_enemy_factor;
+               dir = normalize(dir + dodge);
+       }
+
        //dir = this.bot_dodgevector;
        //if (this.bot_dodgevector_jumpbutton)
        //      PHYS_INPUT_BUTTON_JUMP(this) = true;
@@ -1454,15 +1540,19 @@ float havocbot_moveto(entity this, vector pos)
                if(autocvar_bot_debug_goalstack)
                        debuggoalstack(this);
 
-               // Heading
-               vector dir = get_closer_dest(this.goalcurrent, this.origin);
-               dir = dir - (this.origin + this.view_ofs);
-               dir.z = 0;
-               bot_aimdir(this, dir, -1);
 
                // Go!
                havocbot_movetogoal(this);
 
+               if (!this.bot_aimdir_executed && this.goalcurrent)
+               {
+                       // Heading
+                       vector dir = get_closer_dest(this.goalcurrent, this.origin);
+                       dir -= this.origin + this.view_ofs;
+                       dir.z = 0;
+                       bot_aimdir(this, dir, -1);
+               }
+
                if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_REACHED)
                {
                        // Step 5: Waypoint reached
@@ -1536,6 +1626,7 @@ void havocbot_setupbot(entity this)
        this.cmd_moveto = havocbot_moveto;
        this.cmd_resetgoal = havocbot_resetgoal;
 
+       // NOTE: bot is not player yet
        havocbot_chooserole(this);
 }
 
index 2f987f674ec8719503137e05452abe5e7701fd1e..b3c0c3ea79191abab45599fe91bc4435631a4866 100644 (file)
@@ -61,5 +61,3 @@ void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrat
  */
 
 .entity draggedby;
-.float ladder_time;
-.entity ladder_entity;
index 9ca2b1208e91a2e828b13ba4ce1646111ac1fc22..1d66df09668a783b4b763a72174e944d2e8e4dcc 100644 (file)
@@ -73,7 +73,7 @@ bool havocbot_goalrating_item_pickable_check_players(entity this, vector org, en
        float enemy_distance = FLOAT_MAX;
        float dist;
 
-       FOREACH_CLIENT(IS_PLAYER(it) && it != this && !IS_DEAD(it),
+       FOREACH_CLIENT(IS_PLAYER(it) && it != this && !(IS_DEAD(it) || STAT(FROZEN, it)),
        {
                if (it.team == this.team)
                {
@@ -113,7 +113,7 @@ void havocbot_goalrating_items(entity this, float ratingscale, vector org, float
 {
        float rating;
        vector o;
-       ratingscale = ratingscale * 0.0001; // items are rated around 10000 already
+       ratingscale = ratingscale * 0.0001;
 
        IL_EACH(g_items, it.bot_pickup,
        {
@@ -121,8 +121,6 @@ void havocbot_goalrating_items(entity this, float ratingscale, vector org, float
                // NOTE: this code assumes each bot rates items in a different frame
                if(it.bot_ratingscale_time == time && ratingscale < it.bot_ratingscale)
                        continue;
-               it.bot_ratingscale_time = time;
-               it.bot_ratingscale = ratingscale;
 
                if(!it.solid)
                {
@@ -175,6 +173,8 @@ void havocbot_goalrating_items(entity this, float ratingscale, vector org, float
                if(!havocbot_goalrating_item_pickable_check_players(this, org, it, o))
                        continue;
 
+               it.bot_ratingscale_time = time;
+               it.bot_ratingscale = ratingscale;
                rating = it.bot_pickupevalfunc(this, it);
                if(rating > 0)
                        navigation_routerating(this, it, rating * ratingscale, 2000);
@@ -191,7 +191,7 @@ void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org
        if(this.waterlevel>WATERLEVEL_WETFEET)
                return;
 
-       ratingscale = ratingscale * 0.00005; // enemies are rated around 20000 already
+       ratingscale = ratingscale * 0.0001;
 
        float t;
        FOREACH_CLIENT(IS_PLAYER(it) && bot_shouldattack(this, it), {
@@ -235,7 +235,7 @@ void havocbot_role_generic(entity this)
        {
                navigation_goalrating_start(this);
                havocbot_goalrating_items(this, 10000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
                havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
                navigation_goalrating_end(this);
 
index 555cfd0ef0a1f4da4beb81ebc73577ced6dd0360..250b7cb09c639e45680e699514958d769b97a686 100644 (file)
@@ -13,6 +13,7 @@
 
 #include <common/constants.qh>
 #include <common/net_linked.qh>
+#include <common/mapobjects/func/ladder.qh>
 #include <common/mapobjects/trigger/jumppads.qh>
 
 .float speed;
@@ -49,12 +50,15 @@ bool navigation_goalrating_timeout(entity this)
 #define MAX_CHASE_DISTANCE 700
 bool navigation_goalrating_timeout_can_be_anticipated(entity this)
 {
-       if(time > this.bot_strategytime - (IS_MOVABLE(this.goalentity) ? 3 : 2))
+       vector gco = (this.goalentity.absmin + this.goalentity.absmax) * 0.5;
+       if (vdist(gco - this.origin, >, autocvar_sv_maxspeed * 1.5)
+               && time > this.bot_strategytime - (IS_MOVABLE(this.goalentity) ? 3 : 2))
+       {
                return true;
+       }
 
        if (this.goalentity.bot_pickup && time > this.bot_strategytime - 5)
        {
-               vector gco = (this.goalentity.absmin + this.goalentity.absmax) * 0.5;
                if(!havocbot_goalrating_item_pickable_check_players(this, this.origin, this.goalentity, gco))
                {
                        this.ignoregoal = this.goalentity;
@@ -75,9 +79,11 @@ void navigation_dynamicgoal_init(entity this, bool initially_static)
                this.nearestwaypointtimeout = time;
 }
 
-void navigation_dynamicgoal_set(entity this)
+void navigation_dynamicgoal_set(entity this, entity dropper)
 {
        this.nearestwaypointtimeout = time;
+       if (dropper && dropper.nearestwaypointtimeout && dropper.nearestwaypointtimeout < time + 2)
+               this.nearestwaypoint = dropper.nearestwaypoint;
        if (this.nearestwaypoint)
                this.nearestwaypointtimeout += 2;
 }
@@ -123,8 +129,18 @@ void set_tracewalk_dest(entity ent, vector org, bool fix_player_dest)
                // z coord is set to ent's min height
                tracewalk_dest.x = bound(wm1.x, org.x, wm2.x);
                tracewalk_dest.y = bound(wm1.y, org.y, wm2.y);
-               tracewalk_dest.z = wm1.z;
-               tracewalk_dest_height = wm2.z - wm1.z; // destination height
+               if ((IS_PLAYER(ent) || IS_MONSTER(ent))
+                       && org.x == tracewalk_dest.x && org.y == tracewalk_dest.y && org.z > tracewalk_dest.z)
+               {
+                       tracewalk_dest.z = wm2.z - PL_MIN_CONST.z;
+                       tracewalk_dest_height = 0;
+                       fix_player_dest = false;
+               }
+               else
+               {
+                       tracewalk_dest.z = wm1.z;
+                       tracewalk_dest_height = wm2.z - wm1.z;
+               }
        }
        else
        {
@@ -265,8 +281,7 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float e
        int nav_action;
 
        // Analyze starting point
-       traceline(start, start, MOVE_NORMAL, e);
-       if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA))
+       if (IN_LAVA(start))
                ignorehazards = true;
 
        tracebox(start, m1, m2, start, MOVE_NOMONSTERS, e);
@@ -732,6 +747,7 @@ void navigation_clearroute(entity this)
        this.goalcurrent_distance_z = FLOAT_MAX;
        this.goalcurrent_distance_time = 0;
        this.goalentity_lock_timeout = 0;
+       this.goalentity_shouldbefrozen = false;
        this.goalentity = NULL;
        this.goalcurrent = NULL;
        this.goalstack01 = NULL;
@@ -900,7 +916,7 @@ entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfrom
        vector pm2 = ent.origin + ent.maxs;
 
        // do two scans, because box test is cheaper
-       IL_EACH(g_waypoints, it != ent && it != except,
+       IL_EACH(g_waypoints, it != ent && it != except && !(it.wpflags & WAYPOINTFLAG_TELEPORT),
        {
                if(boxesoverlap(pm1, pm2, it.absmin, it.absmax))
                {
@@ -1202,11 +1218,7 @@ void navigation_markroutes_inverted(entity fixed_source_waypoint)
 // updates the best goal according to a weighted calculation of travel cost and item value of a new proposed item
 void navigation_routerating(entity this, entity e, float f, float rangebias)
 {
-       if (!e)
-               return;
-
-       if(e.blacklisted)
-               return;
+       if (!e || e.blacklisted) { return; }
 
        rangebias = waypoint_getlinearcost(rangebias);
        f = waypoint_getlinearcost(f);
@@ -1214,8 +1226,11 @@ void navigation_routerating(entity this, entity e, float f, float rangebias)
        if (IS_PLAYER(e))
        {
                bool rate_wps = false;
-               if((e.flags & FL_INWATER) || (e.flags & FL_PARTIALGROUND))
+               if (e.watertype < CONTENT_WATER || (e.waterlevel > WATERLEVEL_WETFEET && !STAT(FROZEN, e))
+                       || (e.flags & FL_PARTIALGROUND))
+               {
                        rate_wps = true;
+               }
 
                if(!IS_ONGROUND(e))
                {
@@ -1388,7 +1403,6 @@ void navigation_routerating(entity this, entity e, float f, float rangebias)
                nwp = e.nearestwaypoint;
        }
 
-       LOG_DEBUG("-- checking ", e.classname, " (with cost ", ftos(nwp.wpcost), ")");
        if (nwp && nwp.wpcost < 10000000)
        {
                //te_wizspike(nwp.wpnearestpoint);
@@ -1398,12 +1412,12 @@ void navigation_routerating(entity this, entity e, float f, float rangebias)
                else
                        nwptoitem_cost = waypoint_gettravelcost(nwp.wpnearestpoint, goal_org, nwp, e);
                float cost = nwp.wpcost + nwptoitem_cost;
-               LOG_DEBUG(e.classname, " ", ftos(f), "/(1+", ftos(cost), "/", ftos(rangebias), ") = ");
+               LOG_DEBUG("checking ^5", e.classname, "^7 with base rating ^xf04", ftos(f), "^7 and rangebias ^xf40", ftos(rangebias));
                f = f * rangebias / (rangebias + cost);
-               LOG_DEBUG("considering ", e.classname, " (with rating ", ftos(f), ")");
+               LOG_DEBUG("         ^5", e.classname, "^7 with cost ^6", ftos(cost), "^7 and final rating ^2", ftos(f));
                if (navigation_bestrating < f)
                {
-                       LOG_DEBUG("ground path: added goal ", e.classname, " (with rating ", ftos(f), ")");
+                       LOG_DEBUG(" ground path: ^3added goal ^5", e.classname);
                        navigation_bestrating = f;
                        navigation_bestgoal = e;
                }
@@ -1510,12 +1524,12 @@ bool navigation_routetogoal(entity this, entity e, vector startposition)
 }
 
 // shorten path by removing intermediate goals
-void navigation_shortenpath(entity this)
+bool navigation_shortenpath(entity this)
 {
        if (!this.goalstack01 || wasfreed(this.goalstack01))
-               return;
+               return false;
        if (this.bot_tracewalk_time > time)
-               return;
+               return false;
        this.bot_tracewalk_time = max(time, this.bot_tracewalk_time) + 0.25;
 
        bool cut_allowed = false;
@@ -1554,8 +1568,9 @@ void navigation_shortenpath(entity this)
                                        navigation_poproute(this);
                                }
                                while (this.goalcurrent != next);
+                               return true;
                        }
-                       return;
+                       return false;
                }
        }
 
@@ -1576,8 +1591,10 @@ void navigation_shortenpath(entity this)
                {
                        LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue");
                        navigation_poproute(this);
+                       return true;
                }
        }
+       return false;
 }
 
 // removes any currently touching waypoints from the goal stack
@@ -1601,6 +1618,16 @@ int navigation_poptouchedgoals(entity this)
                                this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
                                this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED;
                        }
+                       if(this.jumppadcount)
+                       {
+                               // remove jumppad waypoint after a random delay to prevent bots getting
+                               // stuck on certain jumppads that require an extra initial horizontal speed
+                               float max_delay = 0.1;
+                               if (vdist(vec2(this.velocity), >, 2 * autocvar_sv_maxspeed))
+                                       max_delay = 0.05;
+                               if (time - this.lastteleporttime < random() * max_delay)
+                                       return removed_goals;
+                       }
                        navigation_poproute(this);
                        this.lastteleporttime = 0;
                        ++removed_goals;
@@ -1675,8 +1702,16 @@ int navigation_poptouchedgoals(entity this)
                        gc_min = this.goalcurrent.origin - '1 1 1' * 12;
                        gc_max = this.goalcurrent.origin + '1 1 1' * 12;
                }
-               if(!boxesoverlap(this.absmin, this.absmax, gc_min, gc_max))
-                       break;
+               if (time < this.ladder_time)
+               {
+                       if (!boxesoverlap(this.absmin, this.absmax - eZ * STAT(PL_MAX, this).z, gc_min, gc_max))
+                               break;
+               }
+               else
+               {
+                       if (!boxesoverlap(this.absmin, this.absmax, gc_min, gc_max))
+                               break;
+               }
 
                // Detect personal waypoints
                if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING)
@@ -1771,6 +1806,7 @@ void navigation_goalrating_end(entity this)
                        this.aistatus |= AI_STATUS_STUCK;
                }
        }
+       this.goalentity_shouldbefrozen = boolean(STAT(FROZEN, this.goalentity));
 }
 
 void botframe_updatedangerousobjects(float maxupdate)
@@ -1849,6 +1885,17 @@ void navigation_unstuck(entity this)
                                bot_waypoint_queue_bestgoal = bot_waypoint_queue_goal;
                        }
                }
+
+               // move to a random waypoint while bot is searching for a walkable path;
+               // this is usually sufficient to unstuck bots from bad spots or when other
+               // bots of the same team block all their ways
+               if (!bot_waypoint_queue_bestgoal && (!this.goalentity || random() < 0.1))
+               {
+                       navigation_clearroute(this);
+                       navigation_routetogoal(this, bot_waypoint_queue_goal, this.origin);
+                       navigation_goalrating_timeout_expire(this, 1 + random() * 2);
+               }
+
                bot_waypoint_queue_goal = bot_waypoint_queue_goal.bot_waypoint_queue_nextgoal;
 
                if (!bot_waypoint_queue_goal)
@@ -1856,6 +1903,7 @@ void navigation_unstuck(entity this)
                        if (bot_waypoint_queue_bestgoal)
                        {
                                LOG_DEBUG(this.netname, " stuck, reachable waypoint found, heading to it");
+                               navigation_clearroute(this);
                                navigation_routetogoal(this, bot_waypoint_queue_bestgoal, this.origin);
                                navigation_goalrating_timeout_set(this);
                                this.aistatus &= ~AI_STATUS_STUCK;
index f3103cc4fcd39d875708a0612e83c59addd03095..b8b067c3b993cae47c20e7b5219a2d36aa913b9f 100644 (file)
@@ -30,6 +30,7 @@ entity navigation_bestgoal;
 .float goalcurrent_distance_time;
 
 .float goalentity_lock_timeout;
+.bool goalentity_shouldbefrozen;
 
 .entity nearestwaypoint;
 .float nearestwaypointtimeout;
@@ -82,7 +83,7 @@ float bot_waypoint_queue_bestgoalrating;
 .entity bot_basewaypoint;
 .bool navigation_dynamicgoal;
 void navigation_dynamicgoal_init(entity this, bool initially_static);
-void navigation_dynamicgoal_set(entity this);
+void navigation_dynamicgoal_set(entity this, entity dropper);
 void navigation_dynamicgoal_unset(entity this);
 
 .int nav_submerged_state;
@@ -114,7 +115,7 @@ void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost2, vecto
 void navigation_markroutes(entity this, entity fixed_source_waypoint);
 void navigation_markroutes_inverted(entity fixed_source_waypoint);
 void navigation_routerating(entity this, entity e, float f, float rangebias);
-void navigation_shortenpath(entity this);
+bool navigation_shortenpath(entity this);
 int navigation_poptouchedgoals(entity this);
 void navigation_goalrating_start(entity this);
 void navigation_goalrating_end(entity this);
index 21a94e340f1f643d06b4b86c9fbe3c504679a8cb..4898cb6afcae4c7e3aac4726d479fab8c3645536 100644 (file)
@@ -440,6 +440,7 @@ set g_jetpack 0 "Jetpack mutator"
 set g_hitplots 0 "when set to 1, hitplots are stored by the server to provide a means of proving that a triggerbot was used"
 set g_hitplots_individuals "" "the individuals, by IP, that should have their hitplots recorded"
 
+// set it to 1 to "fix bot moveto command and routing... now all bots can get to their seats" (Nexuiz repo, commit 2c9873e6)
 set bot_navigation_ignoreplayers 0 // FIXME remove this once the issue is solved
 set bot_sound_monopoly 0 "when enabled, only bots can make any noise"