From: terencehill Date: Mon, 5 Jun 2017 12:54:08 +0000 (+0200) Subject: Merge branch 'master' into terencehill/bot_waypoints X-Git-Tag: xonotic-v0.8.5~2378^2~144 X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=c879eb771ea22f97a56cc1c1aaf5487c40cf9e3d;hp=f1b56e53cbd550d750a87da25139179d8c4a4716 Merge branch 'master' into terencehill/bot_waypoints --- diff --git a/qcsrc/common/triggers/func/ladder.qc b/qcsrc/common/triggers/func/ladder.qc index f4a7ffb02c..42fe0b9d30 100644 --- a/qcsrc/common/triggers/func/ladder.qc +++ b/qcsrc/common/triggers/func/ladder.qc @@ -42,9 +42,34 @@ void func_ladder_link(entity this) void func_ladder_init(entity this) { settouch(this, func_ladder_touch); - trigger_init(this); func_ladder_link(this); + + if(min(this.absmax.x - this.absmin.x, this.absmax.y - this.absmin.y) > 100) + return; + + vector top_min = (this.absmin + this.absmax) / 2; + top_min.z = this.absmax.z; + vector top_max = top_min; + top_max.z += PL_MAX_CONST.z - PL_MIN_CONST.z; + tracebox(top_max + jumpstepheightvec, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, NULL); + if(trace_startsolid) + { + tracebox(top_max + stepheightvec, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, NULL); + if(trace_startsolid) + { + tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, NULL); + if(trace_startsolid) + return; + } + } + if(trace_endpos.z < this.absmax.z) + return; + + this.bot_pickup = true; // allow bots to make use of this ladder + float cost = waypoint_getlinearcost(trace_endpos.z - this.absmin.z); + top_min = trace_endpos; + waypoint_spawnforteleporter_boxes(this, WAYPOINTFLAG_LADDER, this.absmin, this.absmax, top_min, top_min, cost); } spawnfunc(func_ladder) diff --git a/qcsrc/common/triggers/trigger/jumppads.qc b/qcsrc/common/triggers/trigger/jumppads.qc index df96507458..c6c60ba7d3 100644 --- a/qcsrc/common/triggers/trigger/jumppads.qc +++ b/qcsrc/common/triggers/trigger/jumppads.qc @@ -272,6 +272,42 @@ void trigger_push_touch(entity this, entity toucher) #ifdef SVQC void trigger_push_link(entity this); void trigger_push_updatelink(entity this); +bool trigger_push_testorigin(entity e, entity targ, entity jp, vector org) +{ + setorigin(e, org); + tracetoss(e, e); + if(trace_startsolid) + return false; + + if(!jp.height) + { + // since tracetoss starting from jumppad's origin often fails when target + // is very close to real destination, start it directly from target's + // origin instead + e.velocity_z = 0; + setorigin(e, targ.origin + stepheightvec); + tracetoss(e, e); + if(trace_startsolid) + { + setorigin(e, targ.origin + stepheightvec / 2); + tracetoss(e, e); + if(trace_startsolid) + { + setorigin(e, targ.origin); + tracetoss(e, e); + if(trace_startsolid) + return false; + } + } + } + + if(e.move_movetype == MOVETYPE_NONE) + { + tracebox(trace_endpos, e.mins, e.maxs, trace_endpos - eZ * 1500, true, jp); + return true; + } + return false; +} #endif void trigger_push_findtarget(entity this) { @@ -282,17 +318,76 @@ void trigger_push_findtarget(entity this) if (this.target) { int n = 0; +#ifdef SVQC + vector vel = '0 0 0'; +#endif for(entity t = NULL; (t = find(t, targetname, this.target)); ) { ++n; #ifdef SVQC entity e = spawn(); - setorigin(e, org); setsize(e, PL_MIN_CONST, PL_MAX_CONST); e.velocity = trigger_push_calculatevelocity(org, t, this.height); - tracetoss(e, e); - if(e.move_movetype == MOVETYPE_NONE) - waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity)); + vel = e.velocity; + vector best_target = '0 0 0'; + vector best_org = '0 0 0'; + vector best_vel = '0 0 0'; + bool valid_best_target = false; + if (trigger_push_testorigin(e, t, this, org)) + { + best_target = trace_endpos; + best_org = org; + best_vel = e.velocity; + valid_best_target = true; + } + + vector new_org; + vector dist = t.origin - org; + if (dist.x || dist.y) // if not perfectly vertical + { + // test trajectory with different starting points, sometimes the trajectory + // starting from the jumppad origin can't reach the real destination + // and destination waypoint ends up near the jumppad itself + vector flatdir = normalize(dist - eZ * dist.z); + vector ofs = flatdir * 0.5 * min(fabs(this.absmax.x - this.absmin.x), fabs(this.absmax.y - this.absmin.y)); + new_org = org + ofs; + e.velocity = trigger_push_calculatevelocity(new_org, t, this.height); + vel = e.velocity; + if(vlen(vec2(e.velocity)) < autocvar_sv_maxspeed) + e.velocity = autocvar_sv_maxspeed * flatdir; + if (trigger_push_testorigin(e, t, this, new_org) && (!valid_best_target || trace_endpos.z > best_target.z + 50)) + { + best_target = trace_endpos; + best_org = new_org; + best_vel = vel; + valid_best_target = true; + } + new_org = org - ofs; + e.velocity = trigger_push_calculatevelocity(new_org, t, this.height); + vel = e.velocity; + if(vlen(vec2(e.velocity)) < autocvar_sv_maxspeed) + e.velocity = autocvar_sv_maxspeed * flatdir; + if (trigger_push_testorigin(e, t, this, new_org) && (!valid_best_target || trace_endpos.z > best_target.z + 50)) + { + best_target = trace_endpos; + best_org = new_org; + best_vel = vel; + valid_best_target = true; + } + } + + if (valid_best_target) + { + if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, best_target + PL_MIN_CONST, best_target + PL_MAX_CONST))) + { + float velxy = vlen(vec2(best_vel)); + float cost = vlen(vec2(t.origin - best_org)) / velxy; + if(velxy < autocvar_sv_maxspeed) + velxy = autocvar_sv_maxspeed; + cost += vlen(vec2(best_target - t.origin)) / velxy; + waypoint_spawnforteleporter(this, best_target, cost); + } + } delete(e); #endif } @@ -324,7 +419,8 @@ void trigger_push_findtarget(entity this) setsize(e, PL_MIN_CONST, PL_MAX_CONST); e.velocity = this.movedir; tracetoss(e, e); - waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity)); + if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, trace_endpos + PL_MIN_CONST, trace_endpos + PL_MAX_CONST))) + waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity)); delete(e); } diff --git a/qcsrc/server/bot/api.qh b/qcsrc/server/bot/api.qh index d488213576..695763fe56 100644 --- a/qcsrc/server/bot/api.qh +++ b/qcsrc/server/bot/api.qh @@ -10,6 +10,7 @@ const int WAYPOINTFLAG_PERSONAL = BIT(19); const int WAYPOINTFLAG_PROTECTED = BIT(18); // Useless WP detection never kills these. const int WAYPOINTFLAG_USEFUL = BIT(17); // Useless WP detection temporary flag. const int WAYPOINTFLAG_DEAD_END = BIT(16); // Useless WP detection temporary flag. +const int WAYPOINTFLAG_LADDER = BIT(15); entity kh_worldkeylist; .entity kh_worldkeynext; @@ -85,9 +86,10 @@ 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); -bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode); +bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode); -void waypoint_remove(entity e); +void waypoint_remove_fromeditor(entity pl); +void waypoint_remove(entity wp); void waypoint_saveall(); void waypoint_schedulerelinkall(); void waypoint_schedulerelink(entity wp); @@ -95,7 +97,9 @@ void waypoint_spawnforitem(entity e); void waypoint_spawnforitem_force(entity e, vector org); void waypoint_spawnforteleporter(entity e, vector destination, float timetaken); void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken); +void waypoint_spawn_fromeditor(entity pl); entity waypoint_spawn(vector m1, vector m2, float f); +void waypoint_unreachable(entity pl); .entity goalcurrent; void navigation_clearroute(entity this); diff --git a/qcsrc/server/bot/default/bot.qc b/qcsrc/server/bot/default/bot.qc index 7ce4b1ab37..3b9dd7c60c 100644 --- a/qcsrc/server/bot/default/bot.qc +++ b/qcsrc/server/bot/default/bot.qc @@ -571,9 +571,8 @@ void autoskill(float factor) void bot_calculate_stepheightvec() { stepheightvec = autocvar_sv_stepheight * '0 0 1'; - jumpstepheightvec = stepheightvec + - ((autocvar_sv_jumpvelocity * autocvar_sv_jumpvelocity) / (2 * autocvar_sv_gravity)) * '0 0 0.85'; - // 0.75 factor is for safety to make the jumps easy + 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 } float bot_fixcount() @@ -679,6 +678,19 @@ void bot_serverframe() if (time < 2) return; + if(autocvar_skill != skill) + { + float wpcost_update = false; + if(skill >= autocvar_bot_ai_bunnyhop_skilloffset && autocvar_skill < autocvar_bot_ai_bunnyhop_skilloffset) + wpcost_update = true; + if(skill < autocvar_bot_ai_bunnyhop_skilloffset && autocvar_skill >= autocvar_bot_ai_bunnyhop_skilloffset) + wpcost_update = true; + + skill = autocvar_skill; + if (wpcost_update) + waypoint_updatecost_foralllinks(); + } + bot_calculate_stepheightvec(); bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL); @@ -728,7 +740,7 @@ void bot_serverframe() botframe_spawnedwaypoints = true; waypoint_loadall(); if(!waypoint_load_links()) - waypoint_schedulerelinkall(); + waypoint_schedulerelinkall(); // link all the autogenerated waypoints (teleporters) } if (bot_list) diff --git a/qcsrc/server/bot/default/havocbot/havocbot.qc b/qcsrc/server/bot/default/havocbot/havocbot.qc index 42f51af8c7..c533071b02 100644 --- a/qcsrc/server/bot/default/havocbot/havocbot.qc +++ b/qcsrc/server/bot/default/havocbot/havocbot.qc @@ -413,9 +413,26 @@ void havocbot_bunnyhop(entity this, vector dir) #endif } -.entity goalcurrent_prev; -.float goalcurrent_distance; -.float goalcurrent_distance_time; +// return true when bot isn't getting closer to the current goal +bool havocbot_checkgoaldistance(entity this, vector gco) +{ + float curr_dist = vlen(this.origin - gco); + if(curr_dist > this.goalcurrent_distance) + { + if(!this.goalcurrent_distance_time) + this.goalcurrent_distance_time = time; + else if (time - this.goalcurrent_distance_time > 0.5) + return true; + } + else + { + // reduce it a little bit so it works even with very small approaches to the goal + this.goalcurrent_distance = max(20, curr_dist - 15); + this.goalcurrent_distance_time = 0; + } + return false; +} + void havocbot_movetogoal(entity this) { vector destorg; @@ -507,17 +524,25 @@ void havocbot_movetogoal(entity this) // Handling of jump pads if(this.jumppadcount) { - // If got stuck on the jump pad try to reach the farthest visible waypoint - // but with some randomness so it can try out different paths - if(this.aistatus & AI_STATUS_OUT_JUMPPAD) + if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT) + { + this.aistatus |= AI_STATUS_OUT_JUMPPAD; + navigation_poptouchedgoals(this); + return; + } + else if(this.aistatus & AI_STATUS_OUT_JUMPPAD) { - if(fabs(this.velocity.z)<50) + // If got stuck on the jump pad try to reach the farthest visible waypoint + // but with some randomness so it can try out different paths + if(!this.goalcurrent) { entity newgoal = NULL; - if (vdist(this.origin - this.goalcurrent.origin, <, 150)) - this.aistatus &= ~AI_STATUS_OUT_JUMPPAD; - else IL_EACH(g_waypoints, vdist(it.origin - this.origin, <=, 1000), + IL_EACH(g_waypoints, vdist(it.origin - this.origin, <=, 1000), { + if(it.wpflags & WAYPOINTFLAG_TELEPORT) + if(it.origin.z < this.origin.z - 100 && vdist(vec2(it.origin - this.origin), <, 100)) + continue; + traceline(this.origin + this.view_ofs, ((it.absmin + it.absmax) * 0.5), true, this); if(trace_fraction < 1) @@ -539,11 +564,22 @@ void havocbot_movetogoal(entity this) } } else - return; + { + gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5; + if (this.origin.z > gco.z && vdist(vec2(this.velocity), <, autocvar_sv_maxspeed)) + this.aistatus &= ~AI_STATUS_OUT_JUMPPAD; + else if(havocbot_checkgoaldistance(this, gco)) + { + navigation_clearroute(this); + this.bot_strategytime = 0; + } + else + return; + } } else { - if(time - this.lastteleporttime > 0.3 && this.velocity.z > 0) + if(time - this.lastteleporttime > 0.2 && this.velocity.z > 0) { vector velxy = this.velocity; velxy_z = 0; if(vdist(velxy, <, autocvar_sv_maxspeed * 0.2)) @@ -722,6 +758,12 @@ void havocbot_movetogoal(entity this) destorg.x = bound(m1_x, destorg.x, m2_x); destorg.y = bound(m1_y, destorg.y, m2_y); destorg.z = bound(m1_z, destorg.z, m2_z); + + // in case bot ends up inside the teleport waypoint without touching + // the teleport itself, head to the teleport origin + if(destorg == this.origin) + destorg = this.goalcurrent.origin; + diff = destorg - this.origin; //dist = vlen(diff); dir = normalize(diff); @@ -782,33 +824,12 @@ void havocbot_movetogoal(entity this) } // if bot for some reason doesn't get close to the current goal find another one - if(!IS_PLAYER(this.goalcurrent) && !(this.goalcurrent.bot_pickup_respawning && this.goalcurrent_distance < 50)) + if(!this.jumppadcount && !IS_PLAYER(this.goalcurrent) && !(this.goalcurrent.bot_pickup_respawning && this.goalcurrent_distance < 50)) + if(havocbot_checkgoaldistance(this, gco)) { - float curr_dist = vlen(this.origin - this.goalcurrent.origin); - if(this.goalcurrent != this.goalcurrent_prev) - { - this.goalcurrent_prev = this.goalcurrent; - this.goalcurrent_distance = curr_dist; - this.goalcurrent_distance_time = 0; - } - else if(curr_dist > this.goalcurrent_distance) - { - if(!this.goalcurrent_distance_time) - this.goalcurrent_distance_time = time; - else if (time - this.goalcurrent_distance_time > 0.5) - { - this.goalcurrent_prev = NULL; - navigation_clearroute(this); - this.bot_strategytime = 0; - return; - } - } - else - { - // reduce it a little bit so it works even with very small approaches to the goal - this.goalcurrent_distance = max(20, curr_dist - 15); - this.goalcurrent_distance_time = 0; - } + navigation_clearroute(this); + this.bot_strategytime = 0; + return; } // Check for water/slime/lava and dangerous edges diff --git a/qcsrc/server/bot/default/navigation.qc b/qcsrc/server/bot/default/navigation.qc index fc97931541..c93e826152 100644 --- a/qcsrc/server/bot/default/navigation.qc +++ b/qcsrc/server/bot/default/navigation.qc @@ -37,35 +37,43 @@ void navigation_dynamicgoal_unset(entity this) this.nearestwaypointtimeout = -1; } +bool navigation_checkladders(entity e, vector org, vector move, vector m1, vector m2, vector end, vector end2, int movemode) +{ + IL_EACH(g_ladders, it.classname == "func_ladder", + { + if(it.bot_pickup) + if(boxesoverlap(move + m1 + '-1 -1 -1', move + m2 + '1 1 1', it.absmin, it.absmax)) + if(boxesoverlap(end, end2, it.absmin + (m1 - eZ * m1.z - '1 1 0'), it.absmax + (m2 - eZ * m2.z + '1 1 0'))) + { + vector top = org; + top.z = it.absmax.z + (PL_MAX_CONST.z - PL_MIN_CONST.z); + tracebox(org, m1, m2, top, movemode, e); + if(trace_fraction == 1) + return true; + } + }); + return false; +} + // rough simulation of walking from one point to another to test if a path // can be traveled, used for waypoint linking and havocbot - -bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode) +// if end_height is > 0 destination is any point in the vertical segment [end, end + end_height * eZ] +bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode) { - vector org; - vector move; - vector dir; - float dist; - float totaldist; - float stepdist; - float ignorehazards; - float swimming; - entity tw_ladder = NULL; - if(autocvar_bot_debug_tracewalk) { debugresetnodes(); debugnode(e, start); } - move = end - start; - move.z = 0; - org = start; - dist = totaldist = vlen(move); - dir = normalize(move); - stepdist = 32; - ignorehazards = false; - swimming = false; + vector org = start; + vector dir = end - start; + dir.z = 0; + float dist = vlen(dir); + dir = normalize(dir); + float stepdist = 32; + bool ignorehazards = false; + bool swimming = false; // Analyze starting point traceline(start, start, MOVE_NORMAL, e); @@ -91,11 +99,13 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m return false; } + vector end2 = end; + if(end_height) + end2.z += end_height; // Movement loop - move = end - org; for (;;) { - if (boxesoverlap(end, end, org + m1 + '-1 -1 -1', org + m2 + '1 1 1')) + if (boxesoverlap(end, end2, org + m1 + '-1 -1 -1', org + m2 + '1 1 1')) { // Succeeded if(autocvar_bot_debug_tracewalk) @@ -127,8 +137,11 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m } if (trace_dpstartcontents & DPCONTENTS_LIQUIDSMASK) { - move = normalize(end - org); - tracebox(org, m1, m2, org + move * stepdist, movemode, e); + vector water_end = end; + water_end.z = bound(end.z, org.z, end2.z); + vector water_dir = normalize(water_end - org); + vector move = org + water_dir * stepdist; + tracebox(org, m1, m2, move, movemode, e); if(autocvar_bot_debug_tracewalk) debugnode(e, trace_endpos); @@ -136,8 +149,8 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m if (trace_fraction < 1) { swimming = true; - org = trace_endpos + normalize(org - trace_endpos) * stepdist; - for (; org.z < end.z + e.maxs.z; org.z += stepdist) + org = trace_endpos - water_dir * (stepdist / 2); + for (; org.z < end2.z + e.maxs.z; org.z += stepdist) { if(autocvar_bot_debug_tracewalk) debugnode(e, org); @@ -154,6 +167,15 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m return false; //print("tracewalk: ", vtos(start), " failed under water\n"); } + + if(navigation_checkladders(e, org, move, m1, m2, end, end2, movemode)) + { + if(autocvar_bot_debug_tracewalk) + debugnodestatus(trace_endpos, DEBUG_NODE_SUCCESS); + + //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n"); + return true; + } continue; } @@ -162,7 +184,7 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m } else { - move = dir * stepdist + org; + vector move = org + dir * stepdist; tracebox(org, m1, m2, move, movemode, e); if(autocvar_bot_debug_tracewalk) @@ -178,17 +200,21 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m tracebox(org + jumpstepheightvec, m1, m2, move + jumpstepheightvec, movemode, e); if (trace_fraction < 1 || trace_startsolid) { + if(navigation_checkladders(e, trace_endpos - dir * (stepdist / 2), + move + jumpheight_vec, m1, m2, end, end2, movemode)) + { + if(autocvar_bot_debug_tracewalk) + debugnodestatus(trace_endpos, DEBUG_NODE_SUCCESS); + + //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n"); + return true; + } + if(autocvar_bot_debug_tracewalk) debugnodestatus(trace_endpos, DEBUG_NODE_WARNING); - IL_EACH(g_ladders, it.classname == "func_ladder", - { it.solid = SOLID_BSP; }); - traceline( org, move, movemode, e); - IL_EACH(g_ladders, it.classname == "func_ladder", - { it.solid = SOLID_TRIGGER; }); - if ( trace_ent.classname == "door_rotating" || trace_ent.classname == "door") { vector nextmove; @@ -200,24 +226,6 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m move = nextmove; } } - else if (trace_ent.classname == "func_ladder") - { - tw_ladder = trace_ent; - vector ladder_bottom = trace_endpos - dir * m2.x; - vector ladder_top = ladder_bottom; - ladder_top.z = trace_ent.absmax.z + (-m1.z + 1); - tracebox(ladder_bottom, m1, m2, ladder_top, movemode, e); - if (trace_fraction < 1 || trace_startsolid) - { - if(autocvar_bot_debug_tracewalk) - debugnodestatus(trace_endpos, DEBUG_NODE_FAIL); - - return false; // failed - } - org = ladder_top + dir * m2.x; - move = org + dir * stepdist; - continue; - } else { if(autocvar_bot_debug_tracewalk) @@ -257,16 +265,6 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m org = trace_endpos; } - - if(tw_ladder && org.z < tw_ladder.absmax.z) - { - // stop tracewalk if destination height is lower than the top of the ladder - // otherwise bot can't easily figure out climbing direction - if(autocvar_bot_debug_tracewalk) - debugnodestatus(org, DEBUG_NODE_FAIL); - - return false; - } } //print("tracewalk: ", vtos(start), " did not arrive at ", vtos(end), " but at ", vtos(org), "\n"); @@ -285,6 +283,9 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m // completely empty the goal stack, used when deciding where to go void navigation_clearroute(entity this) { + this.goalcurrent_prev = this.goalcurrent; + this.goalcurrent_distance = 10000000; + this.goalcurrent_distance_time = 0; //print("bot ", etos(this), " clear\n"); this.goalentity = NULL; this.goalcurrent = NULL; @@ -329,6 +330,9 @@ void navigation_clearroute(entity this) // steps to the goal, and then recalculate the path. void navigation_pushroute(entity this, entity e) { + this.goalcurrent_prev = this.goalcurrent; + this.goalcurrent_distance = 10000000; + this.goalcurrent_distance_time = 0; //print("bot ", etos(this), " push ", etos(e), "\n"); if(this.goalstack31 == this.goalentity) this.goalentity = NULL; @@ -371,6 +375,9 @@ void navigation_pushroute(entity this, entity e) // (used when a spawnfunc_waypoint is reached) void navigation_poproute(entity this) { + this.goalcurrent_prev = this.goalcurrent; + this.goalcurrent_distance = 10000000; + this.goalcurrent_distance_time = 0; //print("bot ", etos(this), " pop\n"); if(this.goalcurrent == this.goalentity) this.goalentity = NULL; @@ -419,12 +426,12 @@ float navigation_waypoint_will_link(vector v, vector org, entity ent, float walk { if (walkfromwp) { - if (tracewalk(ent, v, PL_MIN_CONST, PL_MAX_CONST, org, bot_navigation_movemode)) + if (tracewalk(ent, v, PL_MIN_CONST, PL_MAX_CONST, org, 0, bot_navigation_movemode)) return true; } else { - if (tracewalk(ent, org, PL_MIN_CONST, PL_MAX_CONST, v, bot_navigation_movemode)) + if (tracewalk(ent, org, PL_MIN_CONST, PL_MAX_CONST, v, 0, bot_navigation_movemode)) return true; } } @@ -456,9 +463,35 @@ entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfrom entity best = NULL; vector v; + if(!autocvar_g_waypointeditor && !ent.navigation_dynamicgoal) + { + waypoint_clearlinks(ent); // initialize wpXXmincost fields + IL_EACH(g_waypoints, it != ent, + { + if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK)) + continue; + + if(it.wpisbox) + { + vector wm1 = it.absmin; + vector wm2 = it.absmax; + v.x = bound(wm1_x, org.x, wm2_x); + v.y = bound(wm1_y, org.y, wm2_y); + v.z = bound(wm1_z, org.z, wm2_z); + } + else + v = it.origin; + if(navigation_waypoint_will_link(v, org, ent, walkfromwp, 1050)) + navigation_item_addlink(it, ent); + }); + } + // box check failed, try walk IL_EACH(g_waypoints, it != ent, { + if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK)) + continue; + if(it.wpisbox) { vector wm1 = it.origin + it.mins; @@ -493,8 +526,9 @@ entity navigation_findnearestwaypoint(entity ent, float walkfromwp) float navigation_markroutes_nearestwaypoints(entity this, float maxdist) { vector v, m1, m2; -// navigation_testtracewalk = true; + //navigation_testtracewalk = true; int c = 0; + float v_height; IL_EACH(g_waypoints, !it.wpconsidered, { if (it.wpisbox) @@ -504,19 +538,23 @@ float navigation_markroutes_nearestwaypoints(entity this, float maxdist) v = this.origin; v.x = bound(m1_x, v.x, m2_x); v.y = bound(m1_y, v.y, m2_y); - v.z = bound(m1_z, v.z, m2_z); + v.z = m1.z; + v_height = m2.z - m1.z; } else + { v = it.origin; + v_height = 0; + } vector diff = v - this.origin; diff.z = max(0, diff.z); if(vdist(diff, <, maxdist)) { it.wpconsidered = true; - if (tracewalk(this, this.origin, this.mins, this.maxs, v, bot_navigation_movemode)) + if (tracewalk(this, this.origin, this.mins, this.maxs, v, v_height, bot_navigation_movemode)) { it.wpnearestpoint = v; - it.wpcost = vlen(v - this.origin) + it.dmg; + it.wpcost = waypoint_gettravelcost(this.origin, v) + it.dmg; it.wpfire = 1; it.enemy = NULL; c = c + 1; @@ -528,7 +566,7 @@ float navigation_markroutes_nearestwaypoints(entity this, float maxdist) } // updates a path link if a spawnfunc_waypoint link is better than the current one -void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost2, vector p) +void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost, vector p) { vector m1; vector m2; @@ -543,10 +581,13 @@ void navigation_markroutes_checkwaypoint(entity w, entity wp, float cost2, vecto } else v = wp.origin; - cost2 = cost2 + vlen(v - p); - if (wp.wpcost > cost2) + if (w.wpflags & WAYPOINTFLAG_TELEPORT) + cost += w.wp00mincost; // assuming teleport has exactly one destination + else + cost += waypoint_gettravelcost(p, v); + if (wp.wpcost > cost) { - wp.wpcost = cost2; + wp.wpcost = cost; wp.enemy = w; wp.wpfire = 1; wp.wpnearestpoint = v; @@ -681,16 +722,9 @@ void navigation_markroutes_inverted(entity fixed_source_waypoint) cost = it.wpcost; // cost to walk from it to home p = it.wpnearestpoint; entity wp = it; - IL_EACH(g_waypoints, true, + IL_EACH(g_waypoints, it != wp, { - if(wp != it.wp00) if(wp != it.wp01) if(wp != it.wp02) if(wp != it.wp03) - if(wp != it.wp04) if(wp != it.wp05) if(wp != it.wp06) if(wp != it.wp07) - if(wp != it.wp08) if(wp != it.wp09) if(wp != it.wp10) if(wp != it.wp11) - if(wp != it.wp12) if(wp != it.wp13) if(wp != it.wp14) if(wp != it.wp15) - if(wp != it.wp16) if(wp != it.wp17) if(wp != it.wp18) if(wp != it.wp19) - if(wp != it.wp20) if(wp != it.wp21) if(wp != it.wp22) if(wp != it.wp23) - if(wp != it.wp24) if(wp != it.wp25) if(wp != it.wp26) if(wp != it.wp27) - if(wp != it.wp28) if(wp != it.wp29) if(wp != it.wp30) if(wp != it.wp31) + if(!waypoint_islinked(it, wp)) continue; cost2 = cost + it.dmg; navigation_markroutes_checkwaypoint(wp, it, cost2, p); @@ -708,6 +742,9 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) if(e.blacklisted) return; + rangebias = waypoint_getlinearcost(rangebias); + f = waypoint_getlinearcost(f); + if (IS_PLAYER(e)) { bool rate_wps = false; @@ -749,7 +786,7 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) } } - vector o = (e.absmin + e.absmax) * 0.5; + vector goal_org = (e.absmin + e.absmax) * 0.5; //print("routerating ", etos(e), " = ", ftos(f), " - ", ftos(rangebias), "\n"); @@ -757,7 +794,7 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) if(g_jetpack) if(this.items & IT_JETPACK) if(autocvar_bot_ai_navigation_jetpack) - if(vdist(this.origin - o, >, autocvar_bot_ai_navigation_jetpack_mindistance)) + if(vdist(this.origin - goal_org, >, autocvar_bot_ai_navigation_jetpack_mindistance)) { vector pointa, pointb; @@ -768,7 +805,7 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) pointa = trace_endpos - '0 0 1'; // Point B - traceline(o, o + '0 0 65535', MOVE_NORMAL, e); + traceline(goal_org, goal_org + '0 0 65535', MOVE_NORMAL, e); pointb = trace_endpos - '0 0 1'; // Can I see these two points from the sky? @@ -861,7 +898,26 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) { nwp = navigation_findnearestwaypoint(e, true); if(nwp) + { e.nearestwaypoint = nwp; + + vector m1 = nwp.absmin, m2 = nwp.absmax; + m1.x = nwp.origin.x; m1.y = nwp.origin.y; + m2.x = nwp.origin.x; m2.y = nwp.origin.y; + vector ve = (e.absmin - e.absmax) * 0.5; + ve.x = bound(m1.x, ve.x, m2.x); + ve.y = bound(m1.y, ve.y, m2.y); + ve.z = bound(m1.z, ve.z, m2.z); + + m1 = e.absmin; m2 = e.absmax; + m1.x = e.origin.x; m1.y = e.origin.y; + m2.x = e.origin.x; m2.y = e.origin.y; + vector vnwp = nwp.origin; + vnwp.x = bound(m1.x, vnwp.x, m2.x); + vnwp.y = bound(m1.y, vnwp.y, m2.y); + vnwp.z = bound(m1.z, vnwp.z, m2.z); + e.nearestwaypoint_dist = vlen(ve - vnwp); + } else { LOG_DEBUG("FAILED to find a nearest waypoint to '", e.classname, "' #", etos(e)); @@ -889,8 +945,9 @@ void navigation_routerating(entity this, entity e, float f, float rangebias) if (nwp.wpcost < 10000000) { //te_wizspike(nwp.wpnearestpoint); - LOG_DEBUG(e.classname, " ", ftos(f), "/(1+", ftos((nwp.wpcost + vlen(e.origin - nwp.wpnearestpoint))), "/", ftos(rangebias), ") = "); - f = f * rangebias / (rangebias + (nwp.wpcost + vlen(o - nwp.wpnearestpoint))); + float cost = nwp.wpcost + waypoint_gettravelcost(nwp.wpnearestpoint, goal_org); + LOG_DEBUG(e.classname, " ", ftos(f), "/(1+", ftos(cost), "/", ftos(rangebias), ") = "); + f = f * rangebias / (rangebias + cost); LOG_DEBUG("considering ", e.classname, " (with rating ", ftos(f), ")"); if (navigation_bestrating < f) { @@ -908,6 +965,13 @@ bool navigation_routetogoal(entity this, entity e, vector startposition) if (!e) return false; + if(e.wpflags & WAYPOINTFLAG_TELEPORT) + { + // force teleport destination as route destination + e.wp00.enemy = e; + e = e.wp00; + } + this.goalentity = e; // put the entity on the goal stack @@ -925,7 +989,10 @@ bool navigation_routetogoal(entity this, entity e, vector startposition) return true; // if it can reach the goal there is nothing more to do - if (tracewalk(this, startposition, STAT(PL_MIN, this), STAT(PL_MAX, this), (e.absmin + e.absmax) * 0.5, bot_navigation_movemode)) + vector dest = (e.absmin + e.absmax) * 0.5; + dest.z = e.absmin.z; + float dest_height = e.absmax.z - e.absmin.z; + if (tracewalk(this, startposition, STAT(PL_MIN, this), STAT(PL_MAX, this), dest, dest_height, bot_navigation_movemode)) return true; entity nearest_wp = NULL; @@ -944,8 +1011,21 @@ bool navigation_routetogoal(entity this, entity e, vector startposition) if(nearest_wp && nearest_wp.enemy) { // often path can be optimized by not adding the nearest waypoint - if(tracewalk(this, nearest_wp.enemy.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), (this.goalentity.absmin + this.goalentity.absmax) * 0.5, bot_navigation_movemode)) + if (this.goalentity.nearestwaypoint_dist < 8) e = nearest_wp.enemy; + else + { + if (this.goalentity.navigation_dynamicgoal) + { + vector dest = (this.goalentity.absmin + this.goalentity.absmax) * 0.5; + dest.z = this.goalentity.absmin.z; + float dest_height = this.goalentity.absmax.z - this.goalentity.absmin.z; + if(tracewalk(this, nearest_wp.enemy.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), dest, dest_height, bot_navigation_movemode)) + e = nearest_wp.enemy; + } + else if(navigation_item_islinked(nearest_wp.enemy, this.goalentity)) + e = nearest_wp.enemy; + } } for (;;) @@ -965,17 +1045,12 @@ bool navigation_routetogoal(entity this, entity e, vector startposition) // (this is how bots detect if they reached a goal) void navigation_poptouchedgoals(entity this) { - vector org, m1, m2; - org = this.origin; - m1 = org + this.mins; - m2 = org + this.maxs; - if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT) { // make sure jumppad is really hit, don't rely on distance based checks // as they may report a touch even if it didn't really happen - if(this.lastteleporttime>0) - if(time - this.lastteleporttime < ((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15)) + if(this.lastteleporttime > 0 + && time - this.lastteleporttime < ((this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15)) { if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_GOING) if(this.goalcurrent.wpflags & WAYPOINTFLAG_PERSONAL && this.goalcurrent.owner==this) @@ -984,30 +1059,35 @@ void navigation_poptouchedgoals(entity this) this.aistatus |= AI_STATUS_WAYPOINT_PERSONAL_REACHED; } navigation_poproute(this); - return; } + else + return; } // If for some reason the bot is closer to the next goal, pop the current one if(this.goalstack01 && !wasfreed(this.goalstack01)) if(vlen2(this.goalcurrent.origin - this.origin) > vlen2(this.goalstack01.origin - this.origin)) if(checkpvs(this.origin + this.view_ofs, this.goalstack01)) - if(tracewalk(this, this.origin, this.mins, this.maxs, (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5, bot_navigation_movemode)) { - LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue"); - navigation_poproute(this); - // TODO this may also be a nice idea to do "early" (e.g. by - // manipulating the vlen() comparisons) to shorten paths in - // general - this would make bots walk more "on rails" than - // "zigzagging" which they currently do with sufficiently - // random-like waypoints, and thus can make a nice bot - // personality property + vector dest = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5; + dest.z = this.goalstack01.absmin.z; + float dest_height = this.goalstack01.absmax.z - this.goalstack01.absmin.z; + if(tracewalk(this, this.origin, this.mins, this.maxs, dest, dest_height, bot_navigation_movemode)) + { + LOG_DEBUG("path optimized for ", this.netname, ", removed a goal from the queue"); + navigation_poproute(this); + // TODO this may also be a nice idea to do "early" (e.g. by + // manipulating the vlen() comparisons) to shorten paths in + // general - this would make bots walk more "on rails" than + // "zigzagging" which they currently do with sufficiently + // random-like waypoints, and thus can make a nice bot + // personality property + } } // Loose goal touching check when running if(this.aistatus & AI_STATUS_RUNNING) if(this.goalcurrent.classname=="waypoint") - if(!(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)) if(vlen(this.velocity - eZ * this.velocity.z) >= autocvar_sv_maxspeed) // if -really- running { if(vdist(this.origin - this.goalcurrent.origin, <, 150)) @@ -1037,10 +1117,7 @@ void navigation_poptouchedgoals(entity this) gc_min = this.goalcurrent.origin - '1 1 1' * 12; gc_max = this.goalcurrent.origin + '1 1 1' * 12; } - if(!boxesoverlap(m1, m2, gc_min, gc_max)) - break; - - if((this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)) + if(!boxesoverlap(this.absmin, this.absmax, gc_min, gc_max)) break; // Detect personal waypoints @@ -1096,8 +1173,8 @@ void botframe_updatedangerousobjects(float maxupdate) IL_EACH(g_waypoints, true, { danger = 0; - m1 = it.mins; - m2 = it.maxs; + m1 = it.absmin; + m2 = it.absmax; IL_EACH(g_bot_dodge, it.bot_dodge, { v = it.origin; @@ -1105,7 +1182,7 @@ void botframe_updatedangerousobjects(float maxupdate) v.y = bound(m1_y, v.y, m2_y); v.z = bound(m1_z, v.z, m2_z); o = (it.absmin + it.absmax) * 0.5; - d = it.bot_dodgerating - vlen(o - v); + d = waypoint_getlinearcost(it.bot_dodgerating) - waypoint_gettravelcost(o, v); if (d > 0) { traceline(o, v, true, NULL); @@ -1143,7 +1220,10 @@ void navigation_unstuck(entity this) // evaluate the next goal on the queue float d = vlen2(this.origin - bot_waypoint_queue_goal.origin); LOG_DEBUG(this.netname, " evaluating ", bot_waypoint_queue_goal.classname, " with distance ", ftos(d)); - if(tracewalk(bot_waypoint_queue_goal, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), bot_waypoint_queue_goal.origin, bot_navigation_movemode)) + vector dest = (bot_waypoint_queue_goal.absmin + bot_waypoint_queue_goal.absmax) * 0.5; + dest.z = bot_waypoint_queue_goal.absmin.z; + float dest_height = bot_waypoint_queue_goal.absmax.z - bot_waypoint_queue_goal.absmin.z; + if(tracewalk(bot_waypoint_queue_goal, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this), dest, dest_height, bot_navigation_movemode)) { if( d > bot_waypoint_queue_bestgoalrating) { diff --git a/qcsrc/server/bot/default/navigation.qh b/qcsrc/server/bot/default/navigation.qh index 8f1f03ad19..e94c32a82f 100644 --- a/qcsrc/server/bot/default/navigation.qh +++ b/qcsrc/server/bot/default/navigation.qh @@ -9,6 +9,7 @@ float navigation_testtracewalk; vector jumpstepheightvec; vector stepheightvec; +vector jumpheight_vec; entity navigation_bestgoal; @@ -22,12 +23,35 @@ entity navigation_bestgoal; .entity goalstack20, goalstack21, goalstack22, goalstack23; .entity goalstack24, goalstack25, goalstack26, goalstack27; .entity goalstack28, goalstack29, goalstack30, goalstack31; + +.entity goalcurrent_prev; +.float goalcurrent_distance; +.float goalcurrent_distance_time; + .entity nearestwaypoint; +.float nearestwaypoint_dist; +.float nearestwaypointtimeout; + +/* +// item it is linked from waypoint it.wpXX (INCOMING link) +// links are sorted by their cost (wpXXmincost) +.entity wp00, wp01, wp02, wp03, wp04, wp05, wp06, wp07, wp08, wp09, wp10, wp11, wp12, wp13, wp14, wp15; +.entity wp16, wp17, wp18, wp19, wp20, wp21, wp22, wp23, wp24, wp25, wp26, wp27, wp28, wp29, wp30, wp31; + +.float wp00mincost, wp01mincost, wp02mincost, wp03mincost, wp04mincost, wp05mincost, wp06mincost, wp07mincost; +.float wp08mincost, wp09mincost, wp10mincost, wp11mincost, wp12mincost, wp13mincost, wp14mincost, wp15mincost; +.float wp16mincost, wp17mincost, wp18mincost, wp19mincost, wp20mincost, wp21mincost, wp22mincost, wp23mincost; +.float wp24mincost, wp25mincost, wp26mincost, wp27mincost, wp28mincost, wp29mincost, wp30mincost, wp31mincost; +*/ + +#define navigation_item_islinked(from_wp, to_item) waypoint_islinked(to_item, from_wp) +#define navigation_item_addlink(from_wp, to_item) \ + waypoint_addlink_customcost(to_item, from_wp, waypoint_getlinkcost(from_wp, to_item)) + .entity wp_goal_prev0; .entity wp_goal_prev1; -.float nearestwaypointtimeout; .float lastteleporttime; .float blacklisted; @@ -63,7 +87,7 @@ void debugnodestatus(vector position, float status); void debuggoalstack(entity this); -float tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode); +float tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode); float navigation_markroutes_nearestwaypoints(entity this, float maxdist); float navigation_routetogoal(entity this, entity e, vector startposition); diff --git a/qcsrc/server/bot/default/waypoints.qc b/qcsrc/server/bot/default/waypoints.qc index e4c227c8fa..f801052931 100644 --- a/qcsrc/server/bot/default/waypoints.qc +++ b/qcsrc/server/bot/default/waypoints.qc @@ -11,10 +11,149 @@ #include #include +#include #include #include +.entity spawnpointmodel; +void waypoint_unreachable(entity pl) +{ + IL_EACH(g_waypoints, true, + { + it.colormod = '0.5 0.5 0.5'; + it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE); + }); + + entity e2 = navigation_findnearestwaypoint(pl, false); + if(!e2) + { + LOG_INFOF("Can't find any waypoint nearby\n"); + return; + } + + navigation_markroutes(pl, e2); + + int j = 0; + int m = 0; + IL_EACH(g_waypoints, it.wpcost >= 10000000, + { + LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin), "\n"); + it.colormod_z = 8; + it.effects |= EF_NODEPTHTEST | EF_BLUE; + j++; + m++; + }); + if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)\n", j); + navigation_markroutes_inverted(e2); + + j = 0; + IL_EACH(g_waypoints, it.wpcost >= 10000000, + { + LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin), "\n"); + it.colormod_x = 8; + if (!(it.effects & EF_NODEPTHTEST)) // not already reported before + m++; + it.effects |= EF_NODEPTHTEST | EF_RED; + j++; + }); + if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)\n", j); + if (m) LOG_INFOF("%d waypoints have been marked total\n", m); + + j = 0; + IL_EACH(g_spawnpoints, true, + { + if (navigation_findnearestwaypoint(it, false)) + { + if(it.spawnpointmodel) + { + delete(it.spawnpointmodel); + it.spawnpointmodel = NULL; + } + } + else + { + if(!it.spawnpointmodel) + { + tracebox(it.origin, PL_MIN_CONST, PL_MAX_CONST, it.origin - '0 0 512', MOVE_NOMONSTERS, NULL); + entity e = new(spawnpointmodel); + vector org = trace_endpos + eZ; + setorigin(e, org); + e.solid = SOLID_TRIGGER; + it.spawnpointmodel = e; + } + LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin), "\n"); + it.spawnpointmodel.effects |= EF_NODEPTHTEST; + _setmodel(it.spawnpointmodel, pl.model); + it.spawnpointmodel.frame = pl.frame; + it.spawnpointmodel.skin = pl.skin; + it.spawnpointmodel.colormap = pl.colormap; + it.spawnpointmodel.colormod = pl.colormod; + it.spawnpointmodel.glowmod = pl.glowmod; + setsize(it.spawnpointmodel, PL_MIN_CONST, PL_MAX_CONST); + j++; + } + }); + if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)\n", j); + + j = 0; + IL_EACH(g_items, true, + { + it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE); + it.colormod = '0.5 0.5 0.5'; + }); + IL_EACH(g_items, true, + { + if (navigation_findnearestwaypoint(it, false)) + continue; + LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n"); + it.effects |= EF_NODEPTHTEST | EF_RED; + it.colormod_x = 8; + j++; + }); + if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)\n", j); + + j = 0; + IL_EACH(g_items, true, + { + if (navigation_findnearestwaypoint(it, true)) + continue; + LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n"); + it.effects |= EF_NODEPTHTEST | EF_BLUE; + it.colormod_z = 8; + j++; + }); + if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", j); +} + +vector waypoint_getSymmetricalOrigin(vector org, int ctf_flags) +{ + vector new_org = org; + if (fabs(autocvar_g_waypointeditor_symmetrical) == 1) + { + vector map_center = havocbot_middlepoint; + if (autocvar_g_waypointeditor_symmetrical == -1) + map_center = autocvar_g_waypointeditor_symmetrical_origin; + + new_org = Rotate(org - map_center, 360 * DEG2RAD / ctf_flags) + map_center; + } + else if (fabs(autocvar_g_waypointeditor_symmetrical) == 2) + { + float m = havocbot_symmetryaxis_equation.x; + float q = havocbot_symmetryaxis_equation.y; + if (autocvar_g_waypointeditor_symmetrical == -2) + { + m = autocvar_g_waypointeditor_symmetrical_axis.x; + q = autocvar_g_waypointeditor_symmetrical_axis.y; + } + + new_org.x = (1 / (1 + m*m)) * ((1 - m*m) * org.x + 2 * m * org.y - 2 * m * q); + new_org.y = (1 / (1 + m*m)) * (2 * m * org.x + (m*m - 1) * org.y + 2 * q); + } + new_org.z = org.z; + return new_org; +} + void waypoint_setupmodel(entity wp) { if (autocvar_g_waypointeditor) @@ -43,7 +182,14 @@ entity waypoint_spawn(vector m1, vector m2, float f) { if(!(f & WAYPOINTFLAG_PERSONAL)) { - IL_EACH(g_waypoints, boxesoverlap(m1, m2, it.absmin, it.absmax), + vector em1 = m1; + vector em2 = m2; + if (m1 == m2) + { + em1 = m1 - '8 8 8'; + em2 = m2 + '8 8 8'; + } + IL_EACH(g_waypoints, boxesoverlap(em1, em2, it.absmin, it.absmax), { return it; }); @@ -90,6 +236,115 @@ entity waypoint_spawn(vector m1, vector m2, float f) return w; } +void waypoint_spawn_fromeditor(entity pl) +{ + entity e; + vector org = pl.origin; + int ctf_flags = havocbot_symmetryaxis_equation.z; + bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2) + || (autocvar_g_waypointeditor_symmetrical < 0)); + int order = ctf_flags; + if(autocvar_g_waypointeditor_symmetrical_order >= 2) + { + order = autocvar_g_waypointeditor_symmetrical_order; + ctf_flags = order; + } + + if(!PHYS_INPUT_BUTTON_CROUCH(pl)) + { + // snap waypoint to item's origin if close enough + IL_EACH(g_items, true, + { + vector item_org = (it.absmin + it.absmax) * 0.5; + item_org.z = it.absmin.z - PL_MIN_CONST.z; + if(vlen(item_org - org) < 30) + { + org = item_org; + break; + } + }); + } + + LABEL(add_wp); + e = waypoint_spawn(org, org, 0); + waypoint_schedulerelink(e); + bprint(strcat("Waypoint spawned at ", vtos(org), "\n")); + if(sym) + { + org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags); + if (vdist(org - pl.origin, >, 32)) + { + if(order > 2) + order--; + else + sym = false; + goto add_wp; + } + } +} + +void waypoint_remove(entity wp) +{ + // tell all waypoints linked to wp that they need to relink + IL_EACH(g_waypoints, it != wp, + { + if (waypoint_islinked(it, wp)) + waypoint_removelink(it, wp); + }); + delete(wp); +} + +void waypoint_remove_fromeditor(entity pl) +{ + entity e = navigation_findnearestwaypoint(pl, false); + + int ctf_flags = havocbot_symmetryaxis_equation.z; + bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2) + || (autocvar_g_waypointeditor_symmetrical < 0)); + int order = ctf_flags; + if(autocvar_g_waypointeditor_symmetrical_order >= 2) + { + order = autocvar_g_waypointeditor_symmetrical_order; + ctf_flags = order; + } + + LABEL(remove_wp); + if (!e) return; + if (e.wpflags & WAYPOINTFLAG_GENERATED) return; + + if (e.wphardwired) + { + LOG_INFO("^1Warning: ^7Removal of hardwired waypoints is not allowed in the editor. Please remove links from/to this waypoint (", vtos(e.origin), ") by hand from maps/", mapname, ".waypoints.hardwired\n"); + return; + } + + entity wp_sym = NULL; + if (sym) + { + vector org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags); + FOREACH_ENTITY_CLASS("waypoint", !(it.wpflags & WAYPOINTFLAG_GENERATED), { + if(vdist(org - it.origin, <, 3)) + { + wp_sym = it; + break; + } + }); + } + + bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n")); + waypoint_remove(e); + + if (sym && wp_sym) + { + e = wp_sym; + if(order > 2) + order--; + else + sym = false; + goto remove_wp; + } +} + void waypoint_removelink(entity from, entity to) { if (from == to || (from.wpflags & WAYPOINTFLAG_NORELINK)) @@ -143,41 +398,99 @@ bool waypoint_islinked(entity from, entity to) return false; } -// add a new link to the spawnfunc_waypoint, replacing the furthest link it already has -void waypoint_addlink(entity from, entity to) +void waypoint_updatecost_foralllinks() { - float c; + IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_TELEPORT), + { + if(it.wp00) it.wp00mincost = waypoint_getlinkcost(it, it.wp00); + if(it.wp01) it.wp01mincost = waypoint_getlinkcost(it, it.wp01); + if(it.wp02) it.wp02mincost = waypoint_getlinkcost(it, it.wp02); + if(it.wp03) it.wp03mincost = waypoint_getlinkcost(it, it.wp03); + if(it.wp04) it.wp04mincost = waypoint_getlinkcost(it, it.wp04); + if(it.wp05) it.wp05mincost = waypoint_getlinkcost(it, it.wp05); + if(it.wp06) it.wp06mincost = waypoint_getlinkcost(it, it.wp06); + if(it.wp07) it.wp07mincost = waypoint_getlinkcost(it, it.wp07); + if(it.wp08) it.wp08mincost = waypoint_getlinkcost(it, it.wp08); + if(it.wp09) it.wp09mincost = waypoint_getlinkcost(it, it.wp09); + if(it.wp10) it.wp10mincost = waypoint_getlinkcost(it, it.wp10); + if(it.wp11) it.wp11mincost = waypoint_getlinkcost(it, it.wp11); + if(it.wp12) it.wp12mincost = waypoint_getlinkcost(it, it.wp12); + if(it.wp13) it.wp13mincost = waypoint_getlinkcost(it, it.wp13); + if(it.wp14) it.wp14mincost = waypoint_getlinkcost(it, it.wp14); + if(it.wp15) it.wp15mincost = waypoint_getlinkcost(it, it.wp15); + if(it.wp16) it.wp16mincost = waypoint_getlinkcost(it, it.wp16); + if(it.wp17) it.wp17mincost = waypoint_getlinkcost(it, it.wp17); + if(it.wp18) it.wp18mincost = waypoint_getlinkcost(it, it.wp18); + if(it.wp19) it.wp19mincost = waypoint_getlinkcost(it, it.wp19); + if(it.wp20) it.wp20mincost = waypoint_getlinkcost(it, it.wp20); + if(it.wp21) it.wp21mincost = waypoint_getlinkcost(it, it.wp21); + if(it.wp22) it.wp22mincost = waypoint_getlinkcost(it, it.wp22); + if(it.wp23) it.wp23mincost = waypoint_getlinkcost(it, it.wp23); + if(it.wp24) it.wp24mincost = waypoint_getlinkcost(it, it.wp24); + if(it.wp25) it.wp25mincost = waypoint_getlinkcost(it, it.wp25); + if(it.wp26) it.wp26mincost = waypoint_getlinkcost(it, it.wp26); + if(it.wp27) it.wp27mincost = waypoint_getlinkcost(it, it.wp27); + if(it.wp28) it.wp28mincost = waypoint_getlinkcost(it, it.wp28); + if(it.wp29) it.wp29mincost = waypoint_getlinkcost(it, it.wp29); + if(it.wp30) it.wp30mincost = waypoint_getlinkcost(it, it.wp30); + if(it.wp31) it.wp31mincost = waypoint_getlinkcost(it, it.wp31); + }); +} - if (from == to) - return; - if (from.wpflags & WAYPOINTFLAG_NORELINK) - return; +float waypoint_getlinearcost(float dist) +{ + if(skill >= autocvar_bot_ai_bunnyhop_skilloffset) + return dist / (autocvar_sv_maxspeed * 1.25); + return dist / autocvar_sv_maxspeed; +} - if (waypoint_islinked(from, to)) - return; +float waypoint_gettravelcost(vector from, vector to) +{ + float c = waypoint_getlinearcost(vlen(to - from)); - if (to.wpisbox || from.wpisbox) + float height = from.z - to.z; + if(height > jumpheight_vec.z && autocvar_sv_gravity > 0) { - // if either is a box we have to find the nearest points on them to - // calculate the distance properly - vector v1, v2, m1, m2; - v1 = from.origin; - m1 = to.absmin; - m2 = to.absmax; + float 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; + } + return c; +} + +float waypoint_getlinkcost(entity from, entity to) +{ + vector v1 = from.origin; + vector v2 = to.origin; + if (from.wpisbox) + { + vector m1 = to.absmin, m2 = to.absmax; v1_x = bound(m1_x, v1_x, m2_x); v1_y = bound(m1_y, v1_y, m2_y); v1_z = bound(m1_z, v1_z, m2_z); - v2 = to.origin; - m1 = from.absmin; - m2 = from.absmax; + } + if (to.wpisbox) + { + vector m1 = from.absmin, m2 = from.absmax; v2_x = bound(m1_x, v2_x, m2_x); v2_y = bound(m1_y, v2_y, m2_y); v2_z = bound(m1_z, v2_z, m2_z); - v2 = to.origin; - c = vlen(v2 - v1); } - else - c = vlen(to.origin - from.origin); + return waypoint_gettravelcost(v1, v2); +} + +// add a new link to the spawnfunc_waypoint, replacing the furthest link it already has +// if c == -1 automatically determine cost of the link +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)) + return; + + if(c == -1) + c = waypoint_getlinkcost(from, to); if (from.wp31mincost < c) return; if (from.wp30mincost < c) {from.wp31 = to;from.wp31mincost = c;return;} from.wp31 = from.wp30;from.wp31mincost = from.wp30mincost; @@ -214,6 +527,11 @@ void waypoint_addlink(entity from, entity to) from.wp00 = to;from.wp00mincost = c;return; } +void waypoint_addlink(entity from, entity to) +{ + waypoint_addlink_customcost(from, to, -1); +} + // relink this spawnfunc_waypoint // (precompile a list of all reachable waypoints from this spawnfunc_waypoint) // (SLOW!) @@ -260,6 +578,8 @@ void waypoint_think(entity this) ++relink_lengthculled; continue; } + float sv_deviation = 0; + float ev_deviation = 0; navigation_testtracewalk = 0; if (!this.wpisbox) { @@ -267,6 +587,7 @@ void waypoint_think(entity this) if (!trace_startsolid) { //dprint("sv deviation", vtos(trace_endpos - sv), "\n"); + sv_deviation = trace_endpos.z + 1 - sv.z; sv = trace_endpos + '0 0 1'; } } @@ -276,19 +597,37 @@ void waypoint_think(entity this) if (!trace_startsolid) { //dprint("ev deviation", vtos(trace_endpos - ev), "\n"); + ev_deviation = trace_endpos.z + 1 - ev.z; ev = trace_endpos + '0 0 1'; } } //traceline(this.origin, it.origin, false, NULL); //if (trace_fraction == 1) - if (!this.wpisbox && tracewalk(this, sv, PL_MIN_CONST, PL_MAX_CONST, ev, MOVE_NOMONSTERS)) - waypoint_addlink(this, it); - else + if (this.wpisbox) relink_walkculled += 0.5; - if (!it.wpisbox && tracewalk(it, ev, PL_MIN_CONST, PL_MAX_CONST, sv, MOVE_NOMONSTERS)) - waypoint_addlink(it, this); else + { + vector dest = ev; + dest.z = em1.z + ev_deviation; + float dest_height = em2.z - em1.z; + if(tracewalk(this, sv, PL_MIN_CONST, PL_MAX_CONST, dest, dest_height, MOVE_NOMONSTERS)) + waypoint_addlink(this, it); + else + relink_walkculled += 0.5; + } + + if (it.wpisbox) relink_walkculled += 0.5; + else + { + vector dest = sv; + dest.z = sm1.z + sv_deviation; + float dest_height = sm2.z - sm1.z; + if(tracewalk(it, ev, PL_MIN_CONST, PL_MAX_CONST, dest, dest_height, MOVE_NOMONSTERS)) + waypoint_addlink(it, this); + else + relink_walkculled += 0.5; + } } }); navigation_testtracewalk = 0; @@ -342,25 +681,6 @@ spawnfunc(waypoint) //waypoint_schedulerelink(this); } -void waypoint_remove(entity wp) -{ - // tell all waypoints linked to wp that they need to relink - IL_EACH(g_waypoints, it != wp, - { - if (waypoint_islinked(it, wp)) - waypoint_removelink(it, wp); - }); - delete(wp); -} - -void waypoint_removeall() -{ - IL_EACH(g_waypoints, true, - { - delete(it); - }); -} - // tell all waypoints to relink // actually this is useful only to update relink_* stats void waypoint_schedulerelinkall() @@ -374,7 +694,7 @@ void waypoint_schedulerelinkall() } // Load waypoint links from file -float waypoint_load_links() +bool waypoint_load_links() { string filename, s; float file, tokens, c = 0, found; @@ -762,11 +1082,11 @@ void waypoint_spawnforitem(entity e) waypoint_spawnforitem_force(e, e.origin); } -void waypoint_spawnforteleporter_boxes(entity e, vector org1, vector org2, vector destination1, vector destination2, float timetaken) +void waypoint_spawnforteleporter_boxes(entity e, int teleport_flag, vector org1, vector org2, vector destination1, vector destination2, float timetaken) { entity w; entity dw; - w = waypoint_spawn(org1, org2, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_NORELINK); + w = waypoint_spawn(org1, org2, WAYPOINTFLAG_GENERATED | teleport_flag | WAYPOINTFLAG_NORELINK); dw = waypoint_spawn(destination1, destination2, WAYPOINTFLAG_GENERATED); // one way link to the destination w.wp00 = dw; @@ -781,13 +1101,13 @@ void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, flo { org = waypoint_fixorigin(org); destination = waypoint_fixorigin(destination); - waypoint_spawnforteleporter_boxes(e, org, org, destination, destination, timetaken); + waypoint_spawnforteleporter_boxes(e, WAYPOINTFLAG_TELEPORT, org, org, destination, destination, timetaken); } void waypoint_spawnforteleporter(entity e, vector destination, float timetaken) { destination = waypoint_fixorigin(destination); - waypoint_spawnforteleporter_boxes(e, e.absmin, e.absmax, destination, destination, timetaken); + waypoint_spawnforteleporter_boxes(e, WAYPOINTFLAG_TELEPORT, e.absmin, e.absmax, destination, destination, timetaken); } entity waypoint_spawnpersonal(entity this, vector position) @@ -820,6 +1140,35 @@ void waypoint_showlink(entity wp1, entity wp2, int display_type) te_lightning2(NULL, wp1.origin, wp2.origin); } +void waypoint_showlinks_to(entity wp, int display_type) +{ + IL_EACH(g_waypoints, it != wp, + { + if (waypoint_islinked(it, wp)) + waypoint_showlink(it, wp, display_type); + }); +} + +void waypoint_showlinks_from(entity wp, int display_type) +{ + waypoint_showlink(wp.wp00, wp, display_type); waypoint_showlink(wp.wp16, wp, display_type); + waypoint_showlink(wp.wp01, wp, display_type); waypoint_showlink(wp.wp17, wp, display_type); + waypoint_showlink(wp.wp02, wp, display_type); waypoint_showlink(wp.wp18, wp, display_type); + waypoint_showlink(wp.wp03, wp, display_type); waypoint_showlink(wp.wp19, wp, display_type); + waypoint_showlink(wp.wp04, wp, display_type); waypoint_showlink(wp.wp20, wp, display_type); + waypoint_showlink(wp.wp05, wp, display_type); waypoint_showlink(wp.wp21, wp, display_type); + waypoint_showlink(wp.wp06, wp, display_type); waypoint_showlink(wp.wp22, wp, display_type); + waypoint_showlink(wp.wp07, wp, display_type); waypoint_showlink(wp.wp23, wp, display_type); + waypoint_showlink(wp.wp08, wp, display_type); waypoint_showlink(wp.wp24, wp, display_type); + waypoint_showlink(wp.wp09, wp, display_type); waypoint_showlink(wp.wp25, wp, display_type); + waypoint_showlink(wp.wp10, wp, display_type); waypoint_showlink(wp.wp26, wp, display_type); + waypoint_showlink(wp.wp11, wp, display_type); waypoint_showlink(wp.wp27, wp, display_type); + waypoint_showlink(wp.wp12, wp, display_type); waypoint_showlink(wp.wp28, wp, display_type); + waypoint_showlink(wp.wp13, wp, display_type); waypoint_showlink(wp.wp29, wp, display_type); + waypoint_showlink(wp.wp14, wp, display_type); waypoint_showlink(wp.wp30, wp, display_type); + waypoint_showlink(wp.wp15, wp, display_type); waypoint_showlink(wp.wp31, wp, display_type); +} + void botframe_showwaypointlinks() { if (time < botframe_waypointeditorlightningtime) @@ -842,38 +1191,10 @@ void botframe_showwaypointlinks() if (head) { te_lightning2(NULL, head.origin, it.origin); - waypoint_showlink(head.wp00, head, display_type); - waypoint_showlink(head.wp01, head, display_type); - waypoint_showlink(head.wp02, head, display_type); - waypoint_showlink(head.wp03, head, display_type); - waypoint_showlink(head.wp04, head, display_type); - waypoint_showlink(head.wp05, head, display_type); - waypoint_showlink(head.wp06, head, display_type); - waypoint_showlink(head.wp07, head, display_type); - waypoint_showlink(head.wp08, head, display_type); - waypoint_showlink(head.wp09, head, display_type); - waypoint_showlink(head.wp10, head, display_type); - waypoint_showlink(head.wp11, head, display_type); - waypoint_showlink(head.wp12, head, display_type); - waypoint_showlink(head.wp13, head, display_type); - waypoint_showlink(head.wp14, head, display_type); - waypoint_showlink(head.wp15, head, display_type); - waypoint_showlink(head.wp16, head, display_type); - waypoint_showlink(head.wp17, head, display_type); - waypoint_showlink(head.wp18, head, display_type); - waypoint_showlink(head.wp19, head, display_type); - waypoint_showlink(head.wp20, head, display_type); - waypoint_showlink(head.wp21, head, display_type); - waypoint_showlink(head.wp22, head, display_type); - waypoint_showlink(head.wp23, head, display_type); - waypoint_showlink(head.wp24, head, display_type); - waypoint_showlink(head.wp25, head, display_type); - waypoint_showlink(head.wp26, head, display_type); - waypoint_showlink(head.wp27, head, display_type); - waypoint_showlink(head.wp28, head, display_type); - waypoint_showlink(head.wp29, head, display_type); - waypoint_showlink(head.wp30, head, display_type); - waypoint_showlink(head.wp31, head, display_type); + if(PHYS_INPUT_BUTTON_CROUCH(it)) + waypoint_showlinks_to(head, display_type); + else + waypoint_showlinks_from(head, display_type); } } }); @@ -1069,6 +1390,8 @@ void botframe_deleteuselesswaypoints() it.wpflags |= WAYPOINTFLAG_USEFUL; if (it.wpflags & WAYPOINTFLAG_TELEPORT) it.wpflags |= WAYPOINTFLAG_USEFUL; + if (it.wpflags & WAYPOINTFLAG_LADDER) + it.wpflags |= WAYPOINTFLAG_USEFUL; if (it.wpflags & WAYPOINTFLAG_PROTECTED) it.wpflags |= WAYPOINTFLAG_USEFUL; // b) WP is closest WP for an item/spawnpoint/other entity diff --git a/qcsrc/server/bot/default/waypoints.qh b/qcsrc/server/bot/default/waypoints.qh index 38d57a04a1..f772d046c7 100644 --- a/qcsrc/server/bot/default/waypoints.qh +++ b/qcsrc/server/bot/default/waypoints.qh @@ -10,10 +10,11 @@ float botframe_waypointeditorlightningtime; float botframe_loadedforcedlinks; float botframe_cachedwaypointlinks; +// waypoint wp links to waypoint wp.wpXX (OUTGOING link) +// links are sorted by their cost (wpXXmincost) .entity wp00, wp01, wp02, wp03, wp04, wp05, wp06, wp07, wp08, wp09, wp10, wp11, wp12, wp13, wp14, wp15; .entity wp16, wp17, wp18, wp19, wp20, wp21, wp22, wp23, wp24, wp25, wp26, wp27, wp28, wp29, wp30, wp31; -// itemscore = (howmuchmoreIwant / howmuchIcanwant) / itemdistance .float wp00mincost, wp01mincost, wp02mincost, wp03mincost, wp04mincost, wp05mincost, wp06mincost, wp07mincost; .float wp08mincost, wp09mincost, wp10mincost, wp11mincost, wp12mincost, wp13mincost, wp14mincost, wp15mincost; .float wp16mincost, wp17mincost, wp18mincost, wp19mincost, wp20mincost, wp21mincost, wp22mincost, wp23mincost; @@ -29,13 +30,21 @@ float botframe_cachedwaypointlinks; */ spawnfunc(waypoint); +void waypoint_removelink(entity from, entity to); +bool waypoint_islinked(entity from, entity to); +void waypoint_addlink_customcost(entity from, entity to, float c); void waypoint_addlink(entity from, entity to); void waypoint_think(entity this); void waypoint_clearlinks(entity wp); void waypoint_schedulerelink(entity wp); -void waypoint_remove(entity e); -void waypoint_removeall(); +float waypoint_getlinkcost(entity from, entity to); +float waypoint_gettravelcost(vector v1, vector v2); +float waypoint_getlinearcost(float dist); +void waypoint_updatecost_foralllinks(); + +void waypoint_remove_fromeditor(entity pl); +void waypoint_remove(entity wp); void waypoint_schedulerelinkall(); void waypoint_load_links_hardwired(); void waypoint_save_links(); @@ -48,11 +57,14 @@ void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, flo void botframe_showwaypointlinks(); float waypoint_loadall(); -float waypoint_load_links(); +bool waypoint_load_links(); +void waypoint_spawn_fromeditor(entity pl); entity waypoint_spawn(vector m1, vector m2, float f); entity waypoint_spawnpersonal(entity this, vector position); +void waypoint_unreachable(entity pl); + vector waypoint_fixorigin(vector position); void botframe_autowaypoints(); diff --git a/qcsrc/server/bot/null/bot_null.qc b/qcsrc/server/bot/null/bot_null.qc index 68ae416706..6c60a70e35 100644 --- a/qcsrc/server/bot/null/bot_null.qc +++ b/qcsrc/server/bot/null/bot_null.qc @@ -28,9 +28,10 @@ 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) { } -bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode) { return false; } +bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode) { return false; } -void waypoint_remove(entity e) { } +void waypoint_remove_fromeditor(entity pl) { } +void waypoint_remove(entity wp) { } void waypoint_saveall() { } void waypoint_schedulerelinkall() { } void waypoint_schedulerelink(entity wp) { } @@ -38,5 +39,6 @@ void waypoint_spawnforitem(entity e) { } void waypoint_spawnforitem_force(entity e, vector org) { } void waypoint_spawnforteleporter(entity e, vector destination, float timetaken) { } void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken) { } +void waypoint_spawn_fromeditor(entity pl) { } entity waypoint_spawn(vector m1, vector m2, float f) { return NULL; } #endif diff --git a/qcsrc/server/command/sv_cmd.qc b/qcsrc/server/command/sv_cmd.qc index 145e75952a..7b33033de2 100644 --- a/qcsrc/server/command/sv_cmd.qc +++ b/qcsrc/server/command/sv_cmd.qc @@ -1567,8 +1567,10 @@ void GameCommand_trace(float request, float argc) if (argc == 4) { e = nextent(NULL); - if (tracewalk(e, stov(argv(2)), e.mins, e.maxs, stov(argv(3)), MOVE_NORMAL)) LOG_INFO("can walk\n"); - else LOG_INFO("cannot walk\n"); + if (tracewalk(e, stov(argv(2)), e.mins, e.maxs, stov(argv(3)), 0, MOVE_NORMAL)) + LOG_INFO("can walk\n"); + else + LOG_INFO("cannot walk\n"); return; } } diff --git a/qcsrc/server/impulse.qc b/qcsrc/server/impulse.qc index c6e3911a19..efa0f8425b 100644 --- a/qcsrc/server/impulse.qc +++ b/qcsrc/server/impulse.qc @@ -571,114 +571,16 @@ IMPULSE(waypoint_clear) sprint(this, "all waypoints cleared\n"); } -vector waypoint_getSymmetricalOrigin(vector org, int ctf_flags) -{ - vector new_org = org; - if (fabs(autocvar_g_waypointeditor_symmetrical) == 1) - { - vector map_center = havocbot_middlepoint; - if (autocvar_g_waypointeditor_symmetrical == -1) - map_center = autocvar_g_waypointeditor_symmetrical_origin; - - new_org = Rotate(org - map_center, 360 * DEG2RAD / ctf_flags) + map_center; - } - else if (fabs(autocvar_g_waypointeditor_symmetrical) == 2) - { - float m = havocbot_symmetryaxis_equation.x; - float q = havocbot_symmetryaxis_equation.y; - if (autocvar_g_waypointeditor_symmetrical == -2) - { - m = autocvar_g_waypointeditor_symmetrical_axis.x; - q = autocvar_g_waypointeditor_symmetrical_axis.y; - } - - new_org.x = (1 / (1 + m*m)) * ((1 - m*m) * org.x + 2 * m * org.y - 2 * m * q); - new_org.y = (1 / (1 + m*m)) * (2 * m * org.x + (m*m - 1) * org.y + 2 * q); - } - new_org.z = org.z; - return new_org; -} - IMPULSE(navwaypoint_spawn) { if (!autocvar_g_waypointeditor) return; - entity e; - vector org = this.origin; - int ctf_flags = havocbot_symmetryaxis_equation.z; - bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2) - || (autocvar_g_waypointeditor_symmetrical < 0)); - int order = ctf_flags; - if(autocvar_g_waypointeditor_symmetrical_order >= 2) - { - order = autocvar_g_waypointeditor_symmetrical_order; - ctf_flags = order; - } - - LABEL(add_wp); - e = waypoint_spawn(org, org, 0); - waypoint_schedulerelink(e); - bprint(strcat("Waypoint spawned at ", vtos(org), "\n")); - if(sym) - { - org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags); - if (vdist(org - this.origin, >, 32)) - { - if(order > 2) - order--; - else - sym = false; - goto add_wp; - } - } + waypoint_spawn_fromeditor(this); } IMPULSE(navwaypoint_remove) { if (!autocvar_g_waypointeditor) return; - entity e = navigation_findnearestwaypoint(this, false); - int ctf_flags = havocbot_symmetryaxis_equation.z; - bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2) - || (autocvar_g_waypointeditor_symmetrical < 0)); - int order = ctf_flags; - if(autocvar_g_waypointeditor_symmetrical_order >= 2) - { - order = autocvar_g_waypointeditor_symmetrical_order; - ctf_flags = order; - } - - LABEL(remove_wp); - if (!e) return; - if (e.wpflags & WAYPOINTFLAG_GENERATED) return; - - if (e.wphardwired) - { - LOG_INFO("^1Warning: ^7Removal of hardwired waypoints is not allowed in the editor. Please remove links from/to this waypoint (", vtos(e.origin), ") by hand from maps/", mapname, ".waypoints.hardwired\n"); - return; - } - - entity wp_sym = NULL; - if (sym) - { - vector org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags); - IL_EACH(g_waypoints, !(it.wpflags & WAYPOINTFLAG_GENERATED), { - if(vdist(org - it.origin, <, 3)) - { - wp_sym = it; - break; - } - }); - } - bprint(strcat("Waypoint removed at ", vtos(e.origin), "\n")); - waypoint_remove(e); - if (sym && wp_sym) - { - e = wp_sym; - if(order > 2) - order--; - else - sym = false; - goto remove_wp; - } + waypoint_remove_fromeditor(this); } IMPULSE(navwaypoint_relink) @@ -696,93 +598,5 @@ IMPULSE(navwaypoint_save) IMPULSE(navwaypoint_unreachable) { if (!autocvar_g_waypointeditor) return; - IL_EACH(g_waypoints, true, - { - it.colormod = '0.5 0.5 0.5'; - it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE); - }); - entity e2 = navigation_findnearestwaypoint(this, false); - navigation_markroutes(this, e2); - - int j, m; - - j = 0; - m = 0; - IL_EACH(g_waypoints, it.wpcost >= 10000000, - { - LOG_INFO("unreachable: ", etos(it), " ", vtos(it.origin), "\n"); - it.colormod_z = 8; - it.effects |= EF_NODEPTHTEST | EF_BLUE; - ++j; - ++m; - }); - if (j) LOG_INFOF("%d waypoints cannot be reached from here in any way (marked with blue light)\n", j); - navigation_markroutes_inverted(e2); - - j = 0; - IL_EACH(g_waypoints, it.wpcost >= 10000000, - { - LOG_INFO("cannot reach me: ", etos(it), " ", vtos(it.origin), "\n"); - it.colormod_x = 8; - if (!(it.effects & EF_NODEPTHTEST)) // not already reported before - ++m; - it.effects |= EF_NODEPTHTEST | EF_RED; - ++j; - }); - if (j) LOG_INFOF("%d waypoints cannot walk to here in any way (marked with red light)\n", j); - if (m) LOG_INFOF("%d waypoints have been marked total\n", m); - - j = 0; - IL_EACH(g_spawnpoints, true, - { - vector org = it.origin; - tracebox(it.origin, PL_MIN_CONST, PL_MAX_CONST, it.origin - '0 0 512', MOVE_NOMONSTERS, NULL); - setorigin(it, trace_endpos); - if (navigation_findnearestwaypoint(it, false)) - { - setorigin(it, org); - it.effects &= ~EF_NODEPTHTEST; - it.model = ""; - } - else - { - setorigin(it, org); - LOG_INFO("spawn without waypoint: ", etos(it), " ", vtos(it.origin), "\n"); - it.effects |= EF_NODEPTHTEST; - _setmodel(it, this.model); - it.frame = this.frame; - it.skin = this.skin; - it.colormod = '8 0.5 8'; - setsize(it, '0 0 0', '0 0 0'); - ++j; - } - }); - if (j) LOG_INFOF("%d spawnpoints have no nearest waypoint (marked by player model)\n", j); - - j = 0; - IL_EACH(g_items, true, - { - it.effects &= ~(EF_NODEPTHTEST | EF_RED | EF_BLUE); - it.colormod = '0.5 0.5 0.5'; - }); - IL_EACH(g_items, true, - { - if (navigation_findnearestwaypoint(it, false)) continue; - LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n"); - it.effects |= EF_NODEPTHTEST | EF_RED; - it.colormod_x = 8; - ++j; - }); - if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked away from (marked with red light)\n", j); - - j = 0; - IL_EACH(g_items, true, - { - if (navigation_findnearestwaypoint(it, true)) continue; - LOG_INFO("item without waypoint: ", etos(it), " ", vtos(it.origin), "\n"); - it.effects |= EF_NODEPTHTEST | EF_BLUE; - it.colormod_z = 8; - ++j; - }); - if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", j); + waypoint_unreachable(this); } diff --git a/qcsrc/server/sv_main.qc b/qcsrc/server/sv_main.qc index 7ba19dafd5..5633a06b55 100644 --- a/qcsrc/server/sv_main.qc +++ b/qcsrc/server/sv_main.qc @@ -216,8 +216,6 @@ void StartFrame() if (timeout_status == TIMEOUT_LEADTIME) // just before the timeout (when timeout_status will be TIMEOUT_ACTIVE) orig_slowmo = autocvar_slowmo; // slowmo will be restored after the timeout - skill = autocvar_skill; - // detect when the pre-game countdown (if any) has ended and the game has started bool game_delay = (time < game_starttime); if (autocvar_sv_eventlog && game_delay_last && !game_delay)