From 44e71cdf3fb00920d80900ddccc7f36ae6ca84ac Mon Sep 17 00:00:00 2001 From: terencehill Date: Fri, 28 Jun 2019 17:18:53 +0200 Subject: [PATCH] Bot waypoints: teach bots to jump gaps by implementing jump waypoints. Spawn it with "wpeditor spawn jump" (60-80 qu before the edge depending on jump length) then spawn a normal waypoint as destination waypoint on the other side of the gap --- qcsrc/server/bot/api.qh | 2 +- qcsrc/server/bot/default/bot.qc | 1 + qcsrc/server/bot/default/havocbot/havocbot.qc | 32 +++++- qcsrc/server/bot/default/navigation.qc | 11 +- qcsrc/server/bot/default/navigation.qh | 1 + qcsrc/server/bot/default/waypoints.qc | 108 ++++++++++++------ qcsrc/server/bot/default/waypoints.qh | 2 +- qcsrc/server/bot/null/bot_null.qc | 2 +- qcsrc/server/command/cmd.qc | 2 +- 9 files changed, 116 insertions(+), 45 deletions(-) diff --git a/qcsrc/server/bot/api.qh b/qcsrc/server/bot/api.qh index 93219319a4..f0d1f4541b 100644 --- a/qcsrc/server/bot/api.qh +++ b/qcsrc/server/bot/api.qh @@ -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); diff --git a/qcsrc/server/bot/default/bot.qc b/qcsrc/server/bot/default/bot.qc index 90338d2021..fc6e1d1712 100644 --- a/qcsrc/server/bot/default/bot.qc +++ b/qcsrc/server/bot/default/bot.qc @@ -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() diff --git a/qcsrc/server/bot/default/havocbot/havocbot.qc b/qcsrc/server/bot/default/havocbot/havocbot.qc index 6c056c27e7..1c4f3bf997 100644 --- a/qcsrc/server/bot/default/havocbot/havocbot.qc +++ b/qcsrc/server/bot/default/havocbot/havocbot.qc @@ -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 diff --git a/qcsrc/server/bot/default/navigation.qc b/qcsrc/server/bot/default/navigation.qc index d63e158781..508d421f8e 100644 --- a/qcsrc/server/bot/default/navigation.qc +++ b/qcsrc/server/bot/default/navigation.qc @@ -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)) { diff --git a/qcsrc/server/bot/default/navigation.qh b/qcsrc/server/bot/default/navigation.qh index d002ae29db..73df404519 100644 --- a/qcsrc/server/bot/default/navigation.qh +++ b/qcsrc/server/bot/default/navigation.qh @@ -10,6 +10,7 @@ float navigation_testtracewalk; vector jumpstepheightvec; vector stepheightvec; vector jumpheight_vec; +float jumpheight_time; entity navigation_bestgoal; diff --git a/qcsrc/server/bot/default/waypoints.qc b/qcsrc/server/bot/default/waypoints.qc index f3277b8f38..4a73af1c7d 100644 --- a/qcsrc/server/bot/default/waypoints.qc +++ b/qcsrc/server/bot/default/waypoints.qc @@ -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); diff --git a/qcsrc/server/bot/default/waypoints.qh b/qcsrc/server/bot/default/waypoints.qh index 3b741f8d10..9c8b507a56 100644 --- a/qcsrc/server/bot/default/waypoints.qh +++ b/qcsrc/server/bot/default/waypoints.qh @@ -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); diff --git a/qcsrc/server/bot/null/bot_null.qc b/qcsrc/server/bot/null/bot_null.qc index 8671cd0900..e0c9fd8129 100644 --- a/qcsrc/server/bot/null/bot_null.qc +++ b/qcsrc/server/bot/null/bot_null.qc @@ -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 diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index 9ce1504d8e..c662fab5a7 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -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") -- 2.39.2