Bot waypoints: teach bots to jump gaps by implementing jump waypoints. Spawn it with...
authorterencehill <piuntn@gmail.com>
Fri, 28 Jun 2019 15:18:53 +0000 (17:18 +0200)
committerterencehill <piuntn@gmail.com>
Sat, 29 Jun 2019 12:56:09 +0000 (14:56 +0200)
qcsrc/server/bot/api.qh
qcsrc/server/bot/default/bot.qc
qcsrc/server/bot/default/havocbot/havocbot.qc
qcsrc/server/bot/default/navigation.qc
qcsrc/server/bot/default/navigation.qh
qcsrc/server/bot/default/waypoints.qc
qcsrc/server/bot/default/waypoints.qh
qcsrc/server/bot/null/bot_null.qc
qcsrc/server/command/cmd.qc

index 9321931..f0d1f45 100644 (file)
@@ -121,7 +121,7 @@ void waypoint_spawnforitem(entity e);
 void waypoint_spawnforitem_force(entity e, vector org);
 void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent);
 void waypoint_spawnforteleporter_wz(entity e, entity tracetest_ent);
-void waypoint_spawn_fromeditor(entity pl, bool at_crosshair);
+void waypoint_spawn_fromeditor(entity pl, bool at_crosshair, bool is_jump_wp);
 entity waypoint_spawn(vector m1, vector m2, float f);
 void waypoint_unreachable(entity pl);
 
index 90338d2..fc6e1d1 100644 (file)
@@ -580,6 +580,7 @@ void bot_calculate_stepheightvec()
        stepheightvec = autocvar_sv_stepheight * '0 0 1';
        jumpheight_vec = (autocvar_sv_jumpvelocity ** 2) / (2 * autocvar_sv_gravity) * '0 0 1';
        jumpstepheightvec = stepheightvec + jumpheight_vec * 0.85; // reduce it a bit to make the jumps easy
+       jumpheight_time = autocvar_sv_jumpvelocity / autocvar_sv_gravity;
 }
 
 bool bot_fixcount()
index 6c056c2..1c4f3bf 100644 (file)
@@ -336,6 +336,7 @@ void havocbot_bunnyhop(entity this, vector dir)
                                        if (!(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL))
                                        if(fabs(gco.z - this.origin.z) < this.maxs.z - this.mins.z)
                                        if(this.goalstack01 && !wasfreed(this.goalstack01))
+                                       if (!(this.goalstack01.wpflags & WAYPOINTFLAG_JUMP))
                                        {
                                                vector gno = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
                                                deviation = vectoangles(gno - this.origin) - vectoangles(gco - this.origin);
@@ -410,6 +411,8 @@ void havocbot_bunnyhop(entity this, vector dir)
 // return true when bot isn't getting closer to the current goal
 bool havocbot_checkgoaldistance(entity this, vector gco)
 {
+       if (this.bot_stop_moving_timeout > time)
+               return false;
        float curr_dist_z = max(20, fabs(this.origin.z - gco.z));
        float curr_dist_2d = max(20, vlen(vec2(this.origin - gco)));
        float distance_time = this.goalcurrent_distance_time;
@@ -679,6 +682,7 @@ void havocbot_movetogoal(entity this)
                        return;
                }
                else if(!this.jumppadcount && !this.goalcurrent.wphardwired
+                       && !(this.goalcurrent_prev && this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP)
                        && GetResource(this, RES_HEALTH) + GetResource(this, RES_ARMOR) > ROCKETJUMP_DAMAGE())
                {
                        if(this.velocity.z < 0)
@@ -993,7 +997,26 @@ void havocbot_movetogoal(entity this)
                        vector flat_diff = vec2(diff);
                        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 | WAYPOINTFLAG_LADDER))
+                       if (this.goalcurrent_prev && this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP)
+                       {
+                               if (time > this.bot_stop_moving_timeout
+                                       && fabs(deviation.y) > 20 && current_speed > maxspeed * 0.4
+                                       && vdist(vec2(this.origin - this.goalcurrent_prev.origin), <, 50))
+                               {
+                                       this.bot_stop_moving_timeout = time + 0.1;
+                               }
+                               if (current_speed > autocvar_sv_maxspeed * 0.9
+                                       && vlen2(flat_diff) < vlen2(vec2(this.goalcurrent_prev.origin - destorg))
+                                       && vdist(vec2(this.origin - this.goalcurrent_prev.origin), >, 50)
+                                       && vdist(vec2(this.origin - this.goalcurrent_prev.origin), <, 150)
+                               )
+                               {
+                                       PHYS_INPUT_BUTTON_JUMP(this) = true;
+                                       // avoid changing route while bot is jumping a gap
+                                       navigation_goalrating_timeout_extend_if_needed(this, 1.5);
+                               }
+                       }
+                       else if (!this.goalstack01 || this.goalcurrent.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_LADDER))
                        {
                                if (vlen2(flat_diff) < vlen2(offset))
                                {
@@ -1029,7 +1052,7 @@ void havocbot_movetogoal(entity this)
                                turning = true;
                        }
 
-                       LABEL(jump_check);
+                       LABEL(jumpobstacle_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
@@ -1048,7 +1071,7 @@ void havocbot_movetogoal(entity this)
                                                        actual_destorg = destorg;
                                                        turning = false;
                                                        this.bot_tracewalk_time = time + 0.25;
-                                                       goto jump_check;
+                                                       goto jumpobstacle_check;
                                                }
                                                s = trace_fraction;
                                                // don't artificially reduce max jump height in real-time
@@ -1112,7 +1135,8 @@ void havocbot_movetogoal(entity this)
 
                        bool unreachable = false;
                        s = CONTENT_SOLID;
-                       if(trace_fraction == 1 && this.jumppadcount == 0 && !this.goalcurrent.wphardwired )
+                       if (trace_fraction == 1 && !this.jumppadcount && !this.goalcurrent.wphardwired
+                               && !(this.goalcurrent_prev && this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP) )
                        if((IS_ONGROUND(this)) || (this.aistatus & AI_STATUS_RUNNING) || (this.aistatus & AI_STATUS_ROAMING) || PHYS_INPUT_BUTTON_JUMP(this))
                        {
                                // Look downwards
index d63e158..508d421 100644 (file)
@@ -47,6 +47,11 @@ bool navigation_goalrating_timeout(entity this)
        return this.bot_strategytime < time;
 }
 
+void navigation_goalrating_timeout_extend_if_needed(entity this, float seconds)
+{
+       this.bot_strategytime = max(this.bot_strategytime, time + seconds);
+}
+
 #define MAX_CHASE_DISTANCE 700
 bool navigation_goalrating_timeout_can_be_anticipated(entity this)
 {
@@ -917,7 +922,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 && !(it.wpflags & WAYPOINTFLAG_TELEPORT),
+       IL_EACH(g_waypoints, it != ent && it != except && !(it.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_JUMP)),
        {
                if(boxesoverlap(pm1, pm2, it.absmin, it.absmax))
                {
@@ -1577,7 +1582,7 @@ bool navigation_shortenpath(entity this)
 
        next = this.goalstack01;
        // if for some reason the bot is closer to the next goal, pop the current one
-       if (!IS_MOVABLE(next) // already checked in the previous case
+       if (!IS_MOVABLE(next) && !this.goalcurrent.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_JUMP)
                && vlen2(this.goalcurrent.origin - next.origin) > vlen2(next.origin - this.origin)
                && checkpvs(this.origin + this.view_ofs, next))
        {
@@ -1771,7 +1776,7 @@ entity navigation_get_really_close_waypoint(entity this)
        if(vdist(wp.origin - this.origin, >, 50))
        {
                wp = NULL;
-               IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_TELEPORT),
+               IL_EACH(g_waypoints, !(it.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_JUMP)),
                {
                        if(vdist(it.origin - this.origin, <, 50))
                        {
index d002ae2..73df404 100644 (file)
@@ -10,6 +10,7 @@ float navigation_testtracewalk;
 vector jumpstepheightvec;
 vector stepheightvec;
 vector jumpheight_vec;
+float jumpheight_time;
 
 entity navigation_bestgoal;
 
index f3277b8..4a73af1 100644 (file)
@@ -285,16 +285,25 @@ void waypoint_setupmodel(entity wp)
                wp.model = "";
 }
 
+entity waypoint_get(vector m1, vector m2)
+{
+       if (m1 == m2)
+       {
+               m1 -= '8 8 8';
+               m2 += '8 8 8';
+       }
+       IL_EACH(g_waypoints, boxesoverlap(m1, m2, it.absmin, it.absmax), { return it; });
+
+       return NULL;
+}
+
 entity waypoint_spawn(vector m1, vector m2, float f)
 {
        if(!(f & (WAYPOINTFLAG_PERSONAL | WAYPOINTFLAG_GENERATED)) && m1 == m2)
        {
-               vector em1 = m1 - '8 8 8';
-               vector em2 = m2 + '8 8 8';
-               IL_EACH(g_waypoints, boxesoverlap(em1, em2, it.absmin, it.absmax),
-               {
-                       return it;
-               });
+               entity wp_found = waypoint_get(m1, m2);
+               if (wp_found)
+                       return wp_found;
        }
        // spawn only one destination waypoint for teleports teleporting player to the exact same spot
        // otherwise links loaded from file would be applied only to the first destination
@@ -355,7 +364,11 @@ void waypoint_addlink_for_custom_jumppad(entity wp_from, entity wp_to)
        IL_EACH(g_jumppads, boxesoverlap(wp_from.absmin, wp_from.absmax, it.absmin, it.absmax),
        {
                jp = it;
+               break;
        });
+       if (!jp)
+               return;
+
        float cost = trigger_push_get_push_time(jp, wp_to.origin);
        wp_from.wp00 = wp_to;
        wp_from.wp00mincost = cost;
@@ -366,15 +379,16 @@ void waypoint_addlink_for_custom_jumppad(entity wp_from, entity wp_to)
 bool start_wp_is_spawned;
 vector start_wp_origin;
 
-void waypoint_clear_start_wp(entity pl)
+void waypoint_clear_start_wp(entity pl, bool warn)
 {
        start_wp_is_spawned = false;
        start_wp_origin = '0 0 0';
        pl.wp_locked = NULL;
-       LOG_INFO("^xf80Start waypoint has been cleared\n");
+       if (warn)
+               LOG_INFO("^xf80Start waypoint has been cleared.\n");
 }
 
-void waypoint_spawn_fromeditor(entity pl, bool at_crosshair)
+void waypoint_spawn_fromeditor(entity pl, bool at_crosshair, bool is_jump_wp)
 {
        entity e = NULL, jp = NULL;
        vector org = pl.origin;
@@ -387,9 +401,12 @@ void waypoint_spawn_fromeditor(entity pl, bool at_crosshair)
                        jp = it;
                        break;
                });
-               if (jp && start_wp_is_spawned)
+       }
+       if (jp || is_jump_wp)
+       {
+               if (start_wp_is_spawned)
                        start_wp_is_spawned = false;
-               LOG_INFO("^xf80Spawning start waypoint\n");
+               LOG_INFO("^xf80Spawning start waypoint...\n");
        }
        int ctf_flags = havocbot_symmetry_origin_order;
        bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
@@ -400,7 +417,7 @@ void waypoint_spawn_fromeditor(entity pl, bool at_crosshair)
                ctf_flags = 2;
        int wp_num = ctf_flags;
 
-       if(!PHYS_INPUT_BUTTON_CROUCH(pl) && !at_crosshair)
+       if(!PHYS_INPUT_BUTTON_CROUCH(pl) && !at_crosshair && !is_jump_wp)
        {
                // snap waypoint to item's origin if close enough
                IL_EACH(g_items, true,
@@ -418,12 +435,13 @@ void waypoint_spawn_fromeditor(entity pl, bool at_crosshair)
        vector start_org = '0 0 0';
        if (start_wp_is_spawned)
        {
-               LOG_INFO("^xf80Spawning destination waypoint\n");
+               LOG_INFO("^xf80Spawning destination waypoint...\n");
                start_org = start_wp_origin;
        }
 
        // save org as it can be modified spawning symmetrycal waypoints
-       vector org_save = org;
+       vector initial_origin = '0 0 0';
+       bool initial_origin_is_set = false;
 
        LABEL(add_wp);
 
@@ -440,16 +458,34 @@ void waypoint_spawn_fromeditor(entity pl, bool at_crosshair)
                if (!pl.wp_locked)
                        pl.wp_locked = e;
        }
+       else if (is_jump_wp)
+       {
+               entity wp_found = waypoint_get(org, org);
+               if (wp_found && !(wp_found.wpflags & WAYPOINTFLAG_JUMP))
+               {
+                       LOG_INFO("Error: can't spawn a jump waypoint over an existent waypoint of a different type\n");
+                       return;
+               }
+               e = waypoint_spawn(org, org, WAYPOINTFLAG_JUMP | WAYPOINTFLAG_NORELINK);
+               if (!pl.wp_locked)
+                       pl.wp_locked = e;
+       }
        else
                e = waypoint_spawn(org, org, 0);
        if(!e)
        {
                LOG_INFOF("Couldn't spawn waypoint at %v\n", org);
                if (start_wp_is_spawned)
-                       waypoint_clear_start_wp(pl);
+                       waypoint_clear_start_wp(pl, true);
                return;
        }
 
+       if (!initial_origin_is_set)
+       {
+               initial_origin = e.origin;
+               initial_origin_is_set = true;
+       }
+
        entity start_wp = NULL;
        if (start_wp_is_spawned)
        {
@@ -462,13 +498,14 @@ void waypoint_spawn_fromeditor(entity pl, bool at_crosshair)
                {
                        // should not happen
                        LOG_INFOF("Couldn't find start waypoint at %v\n", start_org);
-                       waypoint_clear_start_wp(pl);
+                       waypoint_clear_start_wp(pl, true);
                        return;
                }
-               waypoint_addlink_for_custom_jumppad(start_wp, e);
+               waypoint_addlink(start_wp, e);
        }
 
-       waypoint_schedulerelink(e);
+       if (!(jp || is_jump_wp))
+               waypoint_schedulerelink(e);
        bprint(strcat("Waypoint spawned at ", vtos(e.origin), "\n"));
 
        if (start_wp_is_spawned)
@@ -498,20 +535,19 @@ void waypoint_spawn_fromeditor(entity pl, bool at_crosshair)
                        goto add_wp;
                }
        }
-
-       if (jp)
+       if (jp || is_jump_wp)
        {
                if (!start_wp_is_spawned)
                {
                        // we've just created a custom jumppad waypoint
                        // the next one created by the user will be the destination waypoint
                        start_wp_is_spawned = true;
-                       start_wp_origin = org_save;
+                       start_wp_origin = initial_origin;
                }
        }
        else if (start_wp_is_spawned)
        {
-               waypoint_clear_start_wp(pl);
+               waypoint_clear_start_wp(pl, false);
        }
 }
 
@@ -543,7 +579,7 @@ void waypoint_remove_fromeditor(entity pl)
        if (e.wpflags & WAYPOINTFLAG_GENERATED)
        {
                if (start_wp_is_spawned)
-                       waypoint_clear_start_wp(pl);
+                       waypoint_clear_start_wp(pl, true);
                return;
        }
 
@@ -580,12 +616,12 @@ void waypoint_remove_fromeditor(entity pl)
        }
 
        if (start_wp_is_spawned)
-               waypoint_clear_start_wp(pl);
+               waypoint_clear_start_wp(pl, true);
 }
 
 void waypoint_removelink(entity from, entity to)
 {
-       if (from == to || (from.wpflags & WAYPOINTFLAG_NORELINK))
+       if (from == to || (from.wpflags & WAYPOINTFLAG_NORELINK && !(from.wpflags & WAYPOINTFLAG_JUMP)))
                return;
 
        entity fromwp31_prev = from.wp31;
@@ -713,7 +749,11 @@ float waypoint_gettravelcost(vector from, vector to, entity from_ent, entity to_
        float height = from.z - to.z;
        if(height > jumpheight_vec.z && autocvar_sv_gravity > 0)
        {
-               float height_cost = sqrt(height / (autocvar_sv_gravity / 2));
+               float height_cost;
+               if (boolean(from_ent.wpflags & WAYPOINTFLAG_JUMP))
+                       height_cost = jumpheight_time + sqrt((height + jumpheight_vec.z) / (autocvar_sv_gravity / 2));
+               else
+                       height_cost = sqrt(height / (autocvar_sv_gravity / 2));
                c = waypoint_getlinearcost(vlen(vec2(to - from))); // xy distance cost
                if(height_cost > c)
                        c = height_cost;
@@ -751,7 +791,7 @@ void waypoint_addlink_customcost(entity from, entity to, float c)
 {
        if (from == to || waypoint_islinked(from, to))
                return;
-       if (c == -1 && (from.wpflags & WAYPOINTFLAG_NORELINK))
+       if (c == -1 && (from.wpflags & WAYPOINTFLAG_NORELINK) && !(from.wpflags & WAYPOINTFLAG_JUMP))
                return;
 
        if(c == -1)
@@ -794,7 +834,10 @@ void waypoint_addlink_customcost(entity from, entity to, float c)
 
 void waypoint_addlink(entity from, entity to)
 {
-       waypoint_addlink_customcost(from, to, -1);
+       if ((from.wpflags & WAYPOINTFLAG_NORELINK) && !(from.wpflags & (WAYPOINTFLAG_JUMP)))
+               waypoint_addlink_for_custom_jumppad(from, to);
+       else
+               waypoint_addlink_customcost(from, to, -1);
 }
 
 // relink this spawnfunc_waypoint
@@ -848,7 +891,7 @@ void waypoint_think(entity this)
 
                        //traceline(this.origin, it.origin, false, NULL);
                        //if (trace_fraction == 1)
-                       if (this.wpisbox)
+                       if (this.wpisbox || this.wpflags & WAYPOINTFLAG_JUMP)
                                relink_walkculled += 0.5;
                        else
                        {
@@ -858,7 +901,7 @@ void waypoint_think(entity this)
                                        relink_walkculled += 0.5;
                        }
 
-                       if (it.wpisbox)
+                       if (it.wpisbox || it.wpflags & WAYPOINTFLAG_JUMP)
                                relink_walkculled += 0.5;
                        else
                        {
@@ -1062,10 +1105,7 @@ bool waypoint_load_links()
                }
 
                ++c;
-               if ((wp_from.wpflags & WAYPOINTFLAG_NORELINK) && !(wp_from.wpflags & WAYPOINTFLAG_GENERATED))
-                       waypoint_addlink_for_custom_jumppad(wp_from, wp_to);
-               else
-                       waypoint_addlink(wp_from, wp_to);
+               waypoint_addlink(wp_from, wp_to);
        }
 
        fclose(file);
index 3b741f8..9c8b507 100644 (file)
@@ -70,7 +70,7 @@ bool waypoint_load_links();
 #define waypoint_remove_links_hardwired() waypoint_load_or_remove_links_hardwired(true)
 void waypoint_load_or_remove_links_hardwired(bool removal_mode);
 
-void waypoint_spawn_fromeditor(entity pl, bool at_crosshair);
+void waypoint_spawn_fromeditor(entity pl, bool at_crosshair, bool is_jump_wp);
 entity waypoint_spawn(vector m1, vector m2, float f);
 entity waypoint_spawnpersonal(entity this, vector position);
 
index 8671cd0..e0c9fd8 100644 (file)
@@ -39,6 +39,6 @@ void waypoint_spawnforitem(entity e) { }
 void waypoint_spawnforitem_force(entity e, vector org) { }
 void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent) { }
 void waypoint_spawnforteleporter_wz(entity e, entity tracetest_ent) { }
-void waypoint_spawn_fromeditor(entity pl, bool at_crosshair) { }
+void waypoint_spawn_fromeditor(entity pl, bool at_crosshair, bool is_jump_wp) { }
 entity waypoint_spawn(vector m1, vector m2, float f) { return NULL; }
 #endif
index 9ce1504..c662fab 100644 (file)
@@ -187,7 +187,7 @@ void ClientCommand_wpeditor(entity caller, int request, int argc)
                                        if (!IS_PLAYER(caller))
                                                sprint(caller, "ERROR: this command works only if you are player\n");
                                        else
-                                               waypoint_spawn_fromeditor(caller, (argv(2) == "crosshair"));
+                                               waypoint_spawn_fromeditor(caller, (argv(2) == "crosshair"), (argv(2) == "jump"));
                                        return;
                                }
                                else if (argv(1) == "remove")