From: terencehill Date: Sun, 13 Aug 2017 15:43:51 +0000 (+0200) Subject: Merge branch 'master' into terencehill/bot_waypoints X-Git-Tag: xonotic-v0.8.5~2378^2~87 X-Git-Url: http://git.xonotic.org/?a=commitdiff_plain;h=fb5876e8defeb6af31e62002be26bdfa2beb56a7;hp=7e474d576080259e21690de30778b17029b5c0f2;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into terencehill/bot_waypoints --- diff --git a/qcsrc/common/triggers/func/ladder.qc b/qcsrc/common/triggers/func/ladder.qc index f4a7ffb02..92f361a14 100644 --- a/qcsrc/common/triggers/func/ladder.qc +++ b/qcsrc/common/triggers/func/ladder.qc @@ -42,9 +42,71 @@ 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; + + entity tracetest_ent = spawn(); + setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST); + tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + + 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, tracetest_ent); + if(trace_startsolid) + { + tracebox(top_max + stepheightvec, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent); + if(trace_startsolid) + { + tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent); + if(trace_startsolid) + { + if(this.absmax.x - this.absmin.x > PL_MAX_CONST.x - PL_MIN_CONST.x + && this.absmax.y - this.absmin.y < this.absmax.x - this.absmin.x) + { + // move top on one side + top_max.y = top_min.y = this.absmin.y + (PL_MAX_CONST.y - PL_MIN_CONST.y) * 0.75; + } + else if(this.absmax.y - this.absmin.y > PL_MAX_CONST.y - PL_MIN_CONST.y + && this.absmax.x - this.absmin.x < this.absmax.y - this.absmin.y) + { + // move top on one side + top_max.x = top_min.x = this.absmin.x + (PL_MAX_CONST.x - PL_MIN_CONST.x) * 0.75; + } + tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent); + if(trace_startsolid) + { + if(this.absmax.x - this.absmin.x > PL_MAX_CONST.x - PL_MIN_CONST.x + && this.absmax.y - this.absmin.y < this.absmax.x - this.absmin.x) + { + // alternatively on the other side + top_max.y = top_min.y = this.absmax.y - (PL_MAX_CONST.y - PL_MIN_CONST.y) * 0.75; + } + else if(this.absmax.y - this.absmin.y > PL_MAX_CONST.y - PL_MIN_CONST.y + && this.absmax.x - this.absmin.x < this.absmax.y - this.absmin.y) + { + // alternatively on the other side + top_max.x = top_min.x = this.absmax.x - (PL_MAX_CONST.x - PL_MIN_CONST.x) * 0.75; + } + tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent); + } + } + } + } + if(trace_startsolid || trace_endpos.z < this.absmax.z) + { + delete(tracetest_ent); + 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/teleporters.qc b/qcsrc/common/triggers/teleporters.qc index 451afa95b..13a0c41d3 100644 --- a/qcsrc/common/triggers/teleporters.qc +++ b/qcsrc/common/triggers/teleporters.qc @@ -235,7 +235,13 @@ void teleport_findtarget(entity this) ++n; #ifdef SVQC if(e.move_movetype == MOVETYPE_NONE) - waypoint_spawnforteleporter(this, e.origin, 0); + { + entity tracetest_ent = spawn(); + setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST); + tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + waypoint_spawnforteleporter(this, e.origin, 0, tracetest_ent); + delete(tracetest_ent); + } if(e.classname != "info_teleport_destination") LOG_INFO("^3MAPPER ERROR: teleporter does target an invalid teleport destination entity. Angles will not work.\n"); #endif diff --git a/qcsrc/common/triggers/trigger/jumppads.qc b/qcsrc/common/triggers/trigger/jumppads.qc index df9650745..533f65989 100644 --- a/qcsrc/common/triggers/trigger/jumppads.qc +++ b/qcsrc/common/triggers/trigger/jumppads.qc @@ -27,10 +27,7 @@ REGISTER_NET_LINKED(ENT_CLIENT_TARGET_PUSH) ht - jump height, measured from the higher one of org and tgt's midpoint Returns: velocity for the jump - the global trigger_push_calculatevelocity_flighttime is set to the total - jump time */ - vector trigger_push_calculatevelocity(vector org, entity tgt, float ht) { float grav, sdist, zdist, vs, vz, jumpheight; @@ -88,6 +85,7 @@ vector trigger_push_calculatevelocity(vector org, entity tgt, float ht) if(zdist == 0) solution_x = solution.y; // solution_x is 0 in this case, so don't use it, but rather use solution_y (which will be sqrt(0.5 * jumpheight / grav), actually) + float flighttime; if(zdist < 0) { // down-jump @@ -96,14 +94,14 @@ vector trigger_push_calculatevelocity(vector org, entity tgt, float ht) // almost straight line type // jump apex is before the jump // we must take the larger one - trigger_push_calculatevelocity_flighttime = solution.y; + flighttime = solution.y; } else { // regular jump // jump apex is during the jump // we must take the larger one too - trigger_push_calculatevelocity_flighttime = solution.y; + flighttime = solution.y; } } else @@ -114,17 +112,17 @@ vector trigger_push_calculatevelocity(vector org, entity tgt, float ht) // almost straight line type // jump apex is after the jump // we must take the smaller one - trigger_push_calculatevelocity_flighttime = solution.x; + flighttime = solution.x; } else { // regular jump // jump apex is during the jump // we must take the larger one - trigger_push_calculatevelocity_flighttime = solution.y; + flighttime = solution.y; } } - vs = sdist / trigger_push_calculatevelocity_flighttime; + vs = sdist / flighttime; // finally calculate the velocity return sdir * vs + '0 0 1' * vz; @@ -272,6 +270,37 @@ 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 tracetest_ent, entity targ, entity jp, vector org) +{ + setorigin(tracetest_ent, org); + tracetoss(tracetest_ent, tracetest_ent); + 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 + tracetest_ent.velocity.z = 0; + setorigin(tracetest_ent, targ.origin + stepheightvec); + tracetoss(tracetest_ent, tracetest_ent); + if(trace_startsolid) + { + setorigin(tracetest_ent, targ.origin + stepheightvec / 2); + tracetoss(tracetest_ent, tracetest_ent); + if(trace_startsolid) + { + setorigin(tracetest_ent, targ.origin); + tracetoss(tracetest_ent, tracetest_ent); + if(trace_startsolid) + return false; + } + } + } + tracebox(trace_endpos, tracetest_ent.mins, tracetest_ent.maxs, trace_endpos - eZ * 1500, true, tracetest_ent); + return true; +} #endif void trigger_push_findtarget(entity this) { @@ -282,17 +311,80 @@ 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 + if(t.move_movetype != MOVETYPE_NONE) + continue; + entity e = spawn(); - setorigin(e, org); setsize(e, PL_MIN_CONST, PL_MAX_CONST); + e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; 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 (vdist(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 (vdist(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, e); + } + } delete(e); #endif } @@ -320,11 +412,13 @@ void trigger_push_findtarget(entity this) else { entity e = spawn(); - setorigin(e, org); setsize(e, PL_MIN_CONST, PL_MAX_CONST); + e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + setorigin(e, org); 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), e); delete(e); } diff --git a/qcsrc/common/triggers/trigger/jumppads.qh b/qcsrc/common/triggers/trigger/jumppads.qh index 76d244da5..a1260c4e5 100644 --- a/qcsrc/common/triggers/trigger/jumppads.qh +++ b/qcsrc/common/triggers/trigger/jumppads.qh @@ -11,8 +11,6 @@ const int NUM_JUMPPADSUSED = 3; .float jumppadcount; .entity jumppadsused[NUM_JUMPPADSUSED]; -float trigger_push_calculatevelocity_flighttime; - #ifdef SVQC void SUB_UseTargets(entity this, entity actor, entity trigger); void trigger_push_use(entity this, entity actor, entity trigger); @@ -28,10 +26,7 @@ void trigger_push_use(entity this, entity actor, entity trigger); ht - jump height, measured from the higher one of org and tgt's midpoint Returns: velocity for the jump - the global trigger_push_calculatevelocity_flighttime is set to the total - jump time */ - vector trigger_push_calculatevelocity(vector org, entity tgt, float ht); void trigger_push_touch(entity this, entity toucher); diff --git a/qcsrc/ecs/systems/physics.qc b/qcsrc/ecs/systems/physics.qc index e6c2f7ccd..ec97bb2a1 100644 --- a/qcsrc/ecs/systems/physics.qc +++ b/qcsrc/ecs/systems/physics.qc @@ -108,6 +108,7 @@ void sys_phys_update(entity this, float dt) this.com_phys_water = true; sys_phys_simulate(this, dt); this.com_phys_water = false; + this.jumppadcount = 0; } else if (time < this.ladder_time) { this.com_phys_friction = PHYS_FRICTION(this); this.com_phys_vel_max = PHYS_MAXSPEED(this) * maxspeed_mod; diff --git a/qcsrc/server/bot/api.qh b/qcsrc/server/bot/api.qh index f33cc4f26..98f85e28b 100644 --- a/qcsrc/server/bot/api.qh +++ b/qcsrc/server/bot/api.qh @@ -11,6 +11,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; @@ -86,17 +87,20 @@ 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); 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_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent); +void waypoint_spawnforteleporter_wz(entity e, vector org, vector destination, float timetaken, entity tracetest_ent); +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 6cb24f0be..878f076a9 100644 --- a/qcsrc/server/bot/default/bot.qc +++ b/qcsrc/server/bot/default/bot.qc @@ -579,9 +579,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() @@ -687,6 +686,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); @@ -736,7 +748,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/bot.qh b/qcsrc/server/bot/default/bot.qh index b72fad9bd..ea37ccf8f 100644 --- a/qcsrc/server/bot/default/bot.qh +++ b/qcsrc/server/bot/default/bot.qh @@ -78,6 +78,12 @@ float botframe_nextdangertime; float bot_cvar_nextthink; float bot_ignore_bots; // let bots not attack other bots (only works in non-teamplay) +int _content_type; +#define IN_LAVA(pos) (_content_type = pointcontents(pos), (_content_type == CONTENT_LAVA || _content_type == CONTENT_SLIME)) +#define IN_LIQUID(pos) (_content_type = pointcontents(pos), (_content_type == CONTENT_WATER || _content_type == CONTENT_LAVA || _content_type == CONTENT_SLIME)) +#define SUBMERGED(pos) IN_LIQUID(pos + autocvar_sv_player_viewoffset) +#define WETFEET(pos) IN_LIQUID(pos + eZ * (m1.z + 1)) + /* * Functions */ diff --git a/qcsrc/server/bot/default/havocbot/havocbot.qc b/qcsrc/server/bot/default/havocbot/havocbot.qc index 46acf8828..306f3a05e 100644 --- a/qcsrc/server/bot/default/havocbot/havocbot.qc +++ b/qcsrc/server/bot/default/havocbot/havocbot.qc @@ -46,7 +46,6 @@ void havocbot_ai(entity this) this.havocbot_role(this); // little too far down the rabbit hole } - // TODO: tracewalk() should take care of this job (better path finding under water) // if we don't have a goal and we're under water look for a waypoint near the "shore" and push it if(!(IS_DEAD(this) || STAT(FROZEN, this))) if(!this.goalcurrent) @@ -139,11 +138,23 @@ void havocbot_ai(entity this) this.aistatus |= AI_STATUS_ROAMING; this.aistatus &= ~AI_STATUS_ATTACKING; - vector now,v,next;//,heading; + vector v, now, next; float aimdistance,skillblend,distanceblend,blend; - next = now = ( (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5) - (this.origin + this.view_ofs); + + SET_DESTCOORDS(this.goalcurrent, this.origin, v); + if(this.goalcurrent.wpisbox) + { + // avoid a glitch when bot is teleported but teleport waypoint isn't removed yet + if(this.goalstack02 && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT + && this.lastteleporttime > 0 && time - this.lastteleporttime < 0.15) + v = (this.goalstack02.absmin + this.goalstack02.absmax) * 0.5; + // aim to teleport origin if bot is inside teleport waypoint but hasn't touched the real teleport yet + else if(boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin, this.origin)) + v = this.goalcurrent.origin; + } + next = now = v - (this.origin + this.view_ofs); aimdistance = vlen(now); - //heading = this.velocity; + //dprint(this.goalstack01.classname,etos(this.goalstack01),"\n"); if( this.goalstack01 != this && this.goalstack01 && !wasfreed(this.goalstack01) && ((this.aistatus & AI_STATUS_RUNNING) == 0) && @@ -308,12 +319,12 @@ void havocbot_bunnyhop(entity this, vector dir) this.bot_timelastseengoal = 0; } - gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5; + SET_DESTCOORDS(this.goalcurrent, this.origin, gco); bunnyhopdistance = vlen(this.origin - gco); // Run only to visible goals if(IS_ONGROUND(this)) - if(vlen(this.velocity - eZ * this.velocity.z) >= autocvar_sv_maxspeed) // if -really- running + if(vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed)) // if -really- running if(checkpvs(this.origin + this.view_ofs, this.goalcurrent)) { this.bot_lastseengoal = this.goalcurrent; @@ -415,17 +426,32 @@ 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; vector diff; vector dir; vector flatdir; - vector m1; - vector m2; vector evadeobstacle; vector evadelava; float maxspeed; @@ -509,17 +535,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) { - if(fabs(this.velocity.z)<50) + this.aistatus |= AI_STATUS_OUT_JUMPPAD; + navigation_poptouchedgoals(this); + return; + } + else if(this.aistatus & AI_STATUS_OUT_JUMPPAD) + { + // 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) @@ -541,11 +575,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)) @@ -718,12 +763,19 @@ void havocbot_movetogoal(entity this) if(autocvar_bot_debug_goalstack) debuggoalstack(this); - m1 = this.goalcurrent.origin + this.goalcurrent.mins; - m2 = this.goalcurrent.origin + this.goalcurrent.maxs; - destorg = this.origin; - 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); + bool bunnyhop_forbidden = false;; + SET_DESTCOORDS(this.goalcurrent, this.origin, destorg); + + // in case bot ends up inside the teleport waypoint without touching + // the teleport itself, head to the teleport origin + if(this.goalcurrent.wpisbox && boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin + eZ * this.mins.z, this.origin + eZ * this.maxs.z)) + { + bunnyhop_forbidden = true; + destorg = this.goalcurrent.origin; + if(destorg.z > this.origin.z) + PHYS_INPUT_BUTTON_JUMP(this) = true; + } + diff = destorg - this.origin; //dist = vlen(diff); dir = normalize(diff); @@ -733,8 +785,8 @@ void havocbot_movetogoal(entity this) //if (this.bot_dodgevector_time < time) { - // this.bot_dodgevector_time = time + cvar("bot_ai_dodgeupdateinterval"); - // this.bot_dodgevector_jumpbutton = 1; + //this.bot_dodgevector_time = time + cvar("bot_ai_dodgeupdateinterval"); + //this.bot_dodgevector_jumpbutton = 1; evadeobstacle = '0 0 0'; evadelava = '0 0 0'; @@ -744,18 +796,20 @@ void havocbot_movetogoal(entity this) { if(this.waterlevel>WATERLEVEL_SWIMMING) { - // flatdir_z = 1; - this.aistatus |= AI_STATUS_OUT_WATER; + if(!this.goalcurrent) + this.aistatus |= AI_STATUS_OUT_WATER; + else if(gco.z > this.origin.z) + PHYS_INPUT_BUTTON_JUMP(this) = true; } else { + dir = flatdir; if(this.velocity.z >= 0 && !(this.watertype == CONTENT_WATER && gco.z < this.origin.z) && ( !(this.waterlevel == WATERLEVEL_WETFEET && this.watertype == CONTENT_WATER) || this.aistatus & AI_STATUS_OUT_WATER)) PHYS_INPUT_BUTTON_JUMP(this) = true; else PHYS_INPUT_BUTTON_JUMP(this) = false; } - dir = normalize(flatdir); } else { @@ -784,33 +838,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 @@ -821,7 +854,6 @@ void havocbot_movetogoal(entity this) traceline(this.origin + this.view_ofs, dst_ahead, true, NULL); bool unreachable = false; - bool ignorehazards = false; s = CONTENT_SOLID; if(trace_fraction == 1 && this.jumppadcount == 0 && !this.goalcurrent.wphardwired ) if((IS_ONGROUND(this)) || (this.aistatus & AI_STATUS_RUNNING) || (this.aistatus & AI_STATUS_ROAMING) || PHYS_INPUT_BUTTON_JUMP(this)) @@ -835,16 +867,7 @@ void havocbot_movetogoal(entity this) s = pointcontents(trace_endpos + '0 0 1'); if (s != CONTENT_SOLID) if (s == CONTENT_LAVA || s == CONTENT_SLIME) - { evadelava = normalize(this.velocity) * -1; - if(this.waterlevel >= WATERLEVEL_WETFEET && (this.watertype == CONTENT_LAVA || this.watertype == CONTENT_SLIME)) - ignorehazards = true; - } - else if (s == CONTENT_WATER) - { - if(this.waterlevel >= WATERLEVEL_WETFEET && this.watertype == CONTENT_WATER) - ignorehazards = true; - } else if (s == CONTENT_SKY) evadeobstacle = normalize(this.velocity) * -1; else if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos)) @@ -873,8 +896,7 @@ void havocbot_movetogoal(entity this) if(evadeobstacle || evadelava || (s == CONTENT_WATER)) { - if(!ignorehazards) - this.aistatus |= AI_STATUS_DANGER_AHEAD; + this.aistatus |= AI_STATUS_DANGER_AHEAD; if(IS_PLAYER(this.goalcurrent)) unreachable = true; } @@ -923,8 +945,8 @@ void havocbot_movetogoal(entity this) havocbot_keyboard_movement(this, destorg); // Bunnyhop! -// if(this.aistatus & AI_STATUS_ROAMING) - if(this.goalcurrent) + //if(this.aistatus & AI_STATUS_ROAMING) + if(!bunnyhop_forbidden && this.goalcurrent) if(skill+this.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset) havocbot_bunnyhop(this, dir); @@ -1265,7 +1287,9 @@ float havocbot_moveto(entity this, vector pos) debuggoalstack(this); // Heading - vector dir = ( ( this.goalcurrent.absmin + this.goalcurrent.absmax ) * 0.5 ) - (this.origin + this.view_ofs); + vector dir; + SET_DESTCOORDS(this.goalcurrent, this.origin, dir); + dir = dir - (this.origin + this.view_ofs); dir.z = 0; bot_aimdir(this, dir, -1); diff --git a/qcsrc/server/bot/default/havocbot/roles.qc b/qcsrc/server/bot/default/havocbot/roles.qc index 94aed9c96..d33aadfac 100644 --- a/qcsrc/server/bot/default/havocbot/roles.qc +++ b/qcsrc/server/bot/default/havocbot/roles.qc @@ -87,8 +87,9 @@ void havocbot_goalrating_items(entity this, float ratingscale, vector org, float continue; traceline(o, o + '0 0 -1500', true, NULL); - if(Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(trace_endpos + '0 0 1')) & DPCONTENTS_LIQUIDSMASK) + if(IN_LAVA(trace_endpos + '0 0 1')) continue; + // this tracebox_hits_trigger_hurt call isn't needed: // dropped weapons are removed as soon as they fall on a trigger_hurt // and can't be rated while they are in the air @@ -97,9 +98,7 @@ void havocbot_goalrating_items(entity this, float ratingscale, vector org, float } else { - // Ignore items under water - // TODO: can't .waterlevel be used here? - if(Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(it.origin + ((it.mins + it.maxs) * 0.5))) & DPCONTENTS_LIQUIDSMASK) + if(IN_LAVA(it.origin + (it.mins + it.maxs) * 0.5)) continue; } diff --git a/qcsrc/server/bot/default/navigation.qc b/qcsrc/server/bot/default/navigation.qc index d0061b900..1fdee2e10 100644 --- a/qcsrc/server/bot/default/navigation.qc +++ b/qcsrc/server/bot/default/navigation.qc @@ -39,49 +39,94 @@ void navigation_dynamicgoal_unset(entity this) this.nearestwaypointtimeout = -1; } -// rough simulation of walking from one point to another to test if a path -// can be traveled, used for waypoint linking and havocbot +bool navigation_check_submerged_state(entity ent, vector pos) +{ + bool submerged; + if(IS_PLAYER(ent)) + submerged = (ent.waterlevel == WATERLEVEL_SUBMERGED); + else if(ent.nav_submerged_state != SUBMERGED_UNDEFINED) + submerged = (ent.nav_submerged_state == SUBMERGED_YES); + else + { + submerged = SUBMERGED(pos); + // NOTE: SUBMERGED check of box waypoint origin may fail even if origin + // is actually submerged because often they are inside some solid. + // That's why submerged state is saved now that we know current pos is + // not stuck in solid (previous tracewalk call to this pos was successfully) + if(!ent.navigation_dynamicgoal) + ent.nav_submerged_state = (submerged) ? SUBMERGED_YES : SUBMERGED_NO; + } + return submerged; +} -bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float movemode) +bool navigation_checkladders(entity e, vector org, vector m1, vector m2, vector end, vector end2, int movemode) { - vector org; - vector move; - vector dir; - float dist; - float totaldist; - float stepdist; - float ignorehazards; - float swimming; - entity tw_ladder = NULL; + IL_EACH(g_ladders, it.classname == "func_ladder", + { + if(it.bot_pickup) + if(boxesoverlap(org + m1 + '-1 -1 -1', org + m2 + '1 1 1', it.absmin, it.absmax)) + if(boxesoverlap(end, end2, it.absmin + vec2(m1) + '-1 -1 0', it.absmax + vec2(m2) + '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; +} + +vector resurface_limited(vector org, float lim, vector m1) +{ + if (WETFEET(org + eZ * (lim - org.z))) + org.z = lim; + else + { + float RES_min_h = org.z; + float RES_max_h = lim; + do { + org.z = 0.5 * (RES_min_h + RES_max_h); + if(WETFEET(org)) + RES_min_h = org.z; + else + RES_max_h = org.z; + } while (RES_max_h - RES_min_h >= 1); + org.z = RES_min_h; + } + return org; +} +#define RESURFACE_LIMITED(org, lim) org = resurface_limited(org, lim, m1) + +#define NAV_WALK 0 +#define NAV_SWIM_ONWATER 1 +#define NAV_SWIM_UNDERWATER 2 +// rough simulation of walking from one point to another to test if a path +// can be traveled, used for waypoint linking and havocbot +// 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) +{ 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 flatdir = end - start; + flatdir.z = 0; + float flatdist = vlen(flatdir); + flatdir = normalize(flatdir); + float stepdist = 32; + bool ignorehazards = false; + int nav_action; // Analyze starting point traceline(start, start, MOVE_NORMAL, e); if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA)) ignorehazards = true; - else - { - traceline( start, start + '0 0 -65536', MOVE_NORMAL, e); - if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA)) - { - ignorehazards = true; - swimming = true; - } - } + tracebox(start, m1, m2, start, MOVE_NOMONSTERS, e); if (trace_startsolid) { @@ -93,78 +138,309 @@ 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; + + vector fixed_end = end; + vector move; + + if (flatdist > 0 && WETFEET(org)) + { + if (SUBMERGED(org)) + nav_action = NAV_SWIM_UNDERWATER; + else + { + // tracebox down by player's height + // useful to know if water level is so low that bot can still walk + tracebox(org, m1, m2, org - eZ * (m2.z - m1.z), movemode, e); + if (SUBMERGED(trace_endpos)) + { + org = trace_endpos; + nav_action = NAV_SWIM_UNDERWATER; + } + else + nav_action = NAV_WALK; + } + } + else + nav_action = NAV_WALK; + // Movement loop - move = end - org; - for (;;) + while (true) { - if (boxesoverlap(end, end, org + m1 + '-1 -1 -1', org + m2 + '1 1 1')) + if (flatdist <= 0) { - // Succeeded - if(autocvar_bot_debug_tracewalk) - debugnodestatus(org, DEBUG_NODE_SUCCESS); + bool success = true; + if (org.z > end2.z + 1) + { + tracebox(org, m1, m2, end2, movemode, e); + org = trace_endpos; + if (org.z > end2.z + 1) + success = false; + } + else if (org.z < end.z - 1) + { + tracebox(org, m1, m2, org - jumpheight_vec, movemode, e); + if (SUBMERGED(trace_endpos)) + { + vector v = trace_endpos; + tracebox(v, m1, m2, end, movemode, e); + if(trace_endpos.z >= end.z - 1) + { + RESURFACE_LIMITED(v, trace_endpos.z); + trace_endpos = v; + } + } + else if (trace_endpos.z > org.z - jumpheight_vec.z) + tracebox(trace_endpos, m1, m2, trace_endpos + jumpheight_vec, movemode, e); + org = trace_endpos; + if (org.z < end.z - 1) + success = false; + } + + if (success) + { + // Succeeded + if(autocvar_bot_debug_tracewalk) + { + debugnode(e, org); + debugnodestatus(org, DEBUG_NODE_SUCCESS); + } - //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n"); - return true; + //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n"); + return true; + } } + if(autocvar_bot_debug_tracewalk) debugnode(e, org); - if (dist <= 0) + if (flatdist <= 0) break; - if (stepdist > dist) - stepdist = dist; - dist = dist - stepdist; - traceline(org, org, MOVE_NORMAL, e); - if (!ignorehazards) - { - if (trace_dpstartcontents & (DPCONTENTS_SLIME | DPCONTENTS_LAVA)) - { - // hazards blocking path - if(autocvar_bot_debug_tracewalk) - debugnodestatus(org, DEBUG_NODE_FAIL); - //print("tracewalk: ", vtos(start), " hits a hazard when trying to reach ", vtos(end), "\n"); - return false; + if (stepdist > flatdist) + stepdist = flatdist; + if(nav_action == NAV_SWIM_UNDERWATER || (nav_action == NAV_SWIM_ONWATER && org.z > end2.z)) + { + // can't use movement direction here to calculate move because of + // precision errors especially when direction has a high enough z value + //water_dir = normalize(water_end - org); + //move = org + water_dir * stepdist; + fixed_end.z = bound(end.z, org.z, end2.z); + if (stepdist == flatdist) { + move = fixed_end; + flatdist = 0; + } else { + move = org + (fixed_end - org) * (stepdist / flatdist); + flatdist = vlen(vec2(fixed_end - move)); } } - if (trace_dpstartcontents & DPCONTENTS_LIQUIDSMASK) + else // horiz. direction { - move = normalize(end - org); - tracebox(org, m1, m2, org + move * stepdist, movemode, e); + flatdist -= stepdist; + move = org + flatdir * stepdist; + } - if(autocvar_bot_debug_tracewalk) - debugnode(e, trace_endpos); + if(nav_action == NAV_SWIM_ONWATER) + { + tracebox(org, m1, m2, move, movemode, e); // swim + // hit something 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) + // stepswim + tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e); + + if (trace_fraction < 1 || trace_startsolid) // can't jump obstacle out of water { - if(autocvar_bot_debug_tracewalk) - debugnode(e, org); + org = trace_endpos; + if(navigation_checkladders(e, org, m1, m2, end, end2, movemode)) + { + if(autocvar_bot_debug_tracewalk) + { + debugnode(e, org); + debugnodestatus(org, DEBUG_NODE_SUCCESS); + } - if(pointcontents(org) == CONTENT_EMPTY) - break; - } + //print("tracewalk: ", vtos(start), " can reach ", vtos(end), "\n"); + return true; + } - if(pointcontents(org + '0 0 1') != CONTENT_EMPTY) - { if(autocvar_bot_debug_tracewalk) debugnodestatus(org, DEBUG_NODE_FAIL); return false; - //print("tracewalk: ", vtos(start), " failed under water\n"); + //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n"); + } + + //succesful stepswim + + if (flatdist <= 0) + { + org = trace_endpos; + continue; } - continue; + if (org.z <= move.z) // going horiz. + { + tracebox(trace_endpos, m1, m2, move, movemode, e); + org = trace_endpos; + nav_action = NAV_WALK; + continue; + } } - else + + if (org.z <= move.z) // going horiz. + { + org = trace_endpos; + nav_action = NAV_SWIM_ONWATER; + } + else // going down + { org = trace_endpos; + if (SUBMERGED(org)) + nav_action = NAV_SWIM_UNDERWATER; + else + nav_action = NAV_SWIM_ONWATER; + } } - else + else if(nav_action == NAV_SWIM_UNDERWATER) + { + if (move.z >= org.z) // swimming upwards or horiz. + { + tracebox(org, m1, m2, move, movemode, e); // swim + + bool stepswum = false; + + // hit something + if (trace_fraction < 1) + { + // stepswim + vector stepswim_move = move + stepheightvec; + if (flatdist > 0 && stepswim_move.z > end2.z + stepheightvec.z) // don't allow stepswim to go higher than destination + stepswim_move.z = end2.z; + + tracebox(org + stepheightvec, m1, m2, stepswim_move, movemode, e); + + // hit something + if (trace_startsolid) + { + if(autocvar_bot_debug_tracewalk) + debugnodestatus(org, DEBUG_NODE_FAIL); + + //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n"); + return false; + } + + if (trace_fraction < 1) + { + float org_z_prev = org.z; + RESURFACE_LIMITED(org, end2.z); + if(org.z == org_z_prev) + { + if(autocvar_bot_debug_tracewalk) + debugnodestatus(org, DEBUG_NODE_FAIL); + + //print("tracewalk: ", vtos(start), " can't reach ", vtos(end), "\n"); + return false; + } + if(SUBMERGED(org)) + nav_action = NAV_SWIM_UNDERWATER; + else + nav_action = NAV_SWIM_ONWATER; + + // we didn't advance horiz. in this step, flatdist decrease should be reverted + // but we can't do it properly right now... apply this workaround instead + if (flatdist <= 0) + flatdist = 1; + + continue; + } + + //succesful stepswim + + if (flatdist <= 0) + { + org = trace_endpos; + continue; + } + + stepswum = true; + } + + if (!WETFEET(trace_endpos)) + { + tracebox(trace_endpos, m1, m2, trace_endpos - eZ * (stepdist + (m2.z - m1.z)), movemode, e); + // if stepswum we'll land on the obstacle, avoid the SUBMERGED check + if (!stepswum && SUBMERGED(trace_endpos)) + { + RESURFACE_LIMITED(trace_endpos, end2.z); + org = trace_endpos; + nav_action = NAV_SWIM_ONWATER; + continue; + } + + // not submerged + org = trace_endpos; + nav_action = NAV_WALK; + continue; + } + + // wetfeet + org = trace_endpos; + nav_action = NAV_SWIM_UNDERWATER; + continue; + } + else //if (move.z < org.z) // swimming downwards + { + tracebox(org, m1, m2, move, movemode, e); // swim + + // hit something + if (trace_fraction < 1) + { + // stepswim + tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e); + + // hit something + if (trace_fraction < 1 || trace_startsolid) // can't jump obstacle out of water + { + if(autocvar_bot_debug_tracewalk) + debugnodestatus(move, DEBUG_NODE_FAIL); + + //print("tracewalk: ", vtos(start), " hit something when trying to reach ", vtos(end), "\n"); + return false; + } + + //succesful stepswim + + if (flatdist <= 0) + { + org = trace_endpos; + continue; + } + + if (trace_endpos.z > org.z && !SUBMERGED(trace_endpos)) + { + // stepswim caused upwards direction + tracebox(trace_endpos, m1, m2, trace_endpos - stepheightvec, movemode, e); + if (!SUBMERGED(trace_endpos)) + { + org = trace_endpos; + nav_action = NAV_WALK; + continue; + } + } + } + + org = trace_endpos; + nav_action = NAV_SWIM_UNDERWATER; + continue; + } + } + else if(nav_action == NAV_WALK) { - move = dir * stepdist + org; + // walk tracebox(org, m1, m2, move, movemode, e); if(autocvar_bot_debug_tracewalk) @@ -177,48 +453,55 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m tracebox(org + stepheightvec, m1, m2, move + stepheightvec, movemode, e); if (trace_fraction < 1 || trace_startsolid) { - tracebox(org + jumpstepheightvec, m1, m2, move + jumpstepheightvec, movemode, e); - if (trace_fraction < 1 || trace_startsolid) + if (trace_startsolid) // hit ceiling above org + { + // reduce stepwalk height + tracebox(org, m1, m2, org + stepheightvec, movemode, e); + tracebox(trace_endpos, m1, m2, move + eZ * (trace_endpos.z - move.z), movemode, e); + } + else //if (trace_fraction < 1) + { + tracebox(org + jumpstepheightvec, m1, m2, move + jumpstepheightvec, movemode, e); + if (trace_startsolid) // hit ceiling above org + { + // reduce jumpstepwalk height + tracebox(org, m1, m2, org + jumpstepheightvec, movemode, e); + tracebox(trace_endpos, m1, m2, move + eZ * (trace_endpos.z - move.z), movemode, e); + } + } + + if (trace_fraction < 1) { + vector v = trace_endpos; + v.z = org.z + jumpheight_vec.z; + if(navigation_checkladders(e, v, m1, m2, end, end2, movemode)) + { + if(autocvar_bot_debug_tracewalk) + { + debugnode(e, v); + debugnodestatus(v, 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; move = trace_endpos; while(trace_ent.classname == "door_rotating" || trace_ent.classname == "door") { - nextmove = move + (dir * stepdist); + nextmove = move + (flatdir * stepdist); traceline( move, nextmove, movemode, e); 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; + flatdist = vlen(vec2(end - move)); } else { @@ -246,28 +529,43 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float m // (this is the same logic as the Quake walkmove function used) tracebox(move, m1, m2, move + '0 0 -65536', movemode, e); - // moved successfully - if(swimming) + org = trace_endpos; + + if (!ignorehazards) { - float c; - c = pointcontents(org + '0 0 1'); - if (!(c == CONTENT_WATER || c == CONTENT_LAVA || c == CONTENT_SLIME)) - swimming = false; - else - continue; + if (IN_LAVA(org)) + { + if(autocvar_bot_debug_tracewalk) + { + debugnode(e, trace_endpos); + debugnodestatus(org, DEBUG_NODE_FAIL); + } + + //print("tracewalk: ", vtos(start), " hits a hazard when trying to reach ", vtos(end), "\n"); + return false; + } } - org = trace_endpos; - } + if (flatdist <= 0) + { + if(move.z >= end2.z && org.z < end2.z) + org.z = end2.z; + continue; + } - 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(org.z > move.z - 1 || !SUBMERGED(org)) + { + nav_action = NAV_WALK; + continue; + } + + // ended up submerged while walking if(autocvar_bot_debug_tracewalk) - debugnodestatus(org, DEBUG_NODE_FAIL); + debugnode(e, org); - return false; + RESURFACE_LIMITED(org, move.z); + nav_action = NAV_SWIM_ONWATER; + continue; } } @@ -287,6 +585,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; @@ -331,6 +632,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; @@ -373,6 +677,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; @@ -410,23 +717,24 @@ void navigation_poproute(entity this) this.goalstack31 = NULL; } -float navigation_waypoint_will_link(vector v, vector org, entity ent, float walkfromwp, float bestdist) +// walking to wp (walkfromwp == false) v2 and v2_height will be used as +// waypoint destination coordinates instead of v (only useful for box waypoints) +// for normal waypoints v2 == v and v2_height == 0 +float navigation_waypoint_will_link(vector v, vector org, entity ent, vector v2, float v2_height, float walkfromwp, float bestdist) { - float dist; - dist = vlen(v - org); - if (bestdist > dist) + if (vdist(v - org, <, bestdist)) { traceline(v, org, true, ent); if (trace_fraction == 1) { 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, v2, v2_height, bot_navigation_movemode)) return true; } } @@ -447,8 +755,12 @@ entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfrom return it; }); - vector org = ent.origin + 0.5 * (ent.mins + ent.maxs); - org.z = ent.origin.z + ent.mins.z - PL_MIN_CONST.z; // player height + vector org = ent.origin; + if(ent.size) + { + org += 0.5 * (ent.mins + ent.maxs); + org.z = ent.origin.z + ent.mins.z - PL_MIN_CONST.z; // player height + } // TODO possibly make other code have the same support for bboxes if(ent.tag_entity) org = org + ent.tag_entity.origin; @@ -456,22 +768,30 @@ entity navigation_findnearestwaypoint_withdist_except(entity ent, float walkfrom te_plasmaburn(org); entity best = NULL; - vector v; + vector v, v2; + float v2_height; + + 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; + + SET_TRACEWALK_DESTCOORDS_2(it, org, v, v2, v2_height); + if(navigation_waypoint_will_link(v, org, ent, v2, v2_height, walkfromwp, 1050)) + navigation_item_addlink(it, ent); + }); + } // box check failed, try walk IL_EACH(g_waypoints, it != ent, { - if(it.wpisbox) - { - vector wm1 = it.origin + it.mins; - vector wm2 = it.origin + it.maxs; - 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, bestdist)) + if(walkfromwp && (it.wpflags & WAYPOINTFLAG_NORELINK)) + continue; + SET_TRACEWALK_DESTCOORDS_2(it, org, v, v2, v2_height); + if(navigation_waypoint_will_link(v, org, ent, v2, v2_height, walkfromwp, bestdist)) { bestdist = vlen(v - org); best = it; @@ -494,31 +814,23 @@ entity navigation_findnearestwaypoint(entity ent, float walkfromwp) // finds the waypoints near the bot initiating a navigation query float navigation_markroutes_nearestwaypoints(entity this, float maxdist) { - vector v, m1, m2; -// navigation_testtracewalk = true; + vector v; + //navigation_testtracewalk = true; int c = 0; + float v_height; IL_EACH(g_waypoints, !it.wpconsidered, { - if (it.wpisbox) - { - m1 = it.origin + it.mins; - m2 = it.origin + it.maxs; - 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); - } - else - v = it.origin; + SET_TRACEWALK_DESTCOORDS(it, this.origin, v, v_height); + 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, this, it) + it.dmg; it.wpfire = 1; it.enemy = NULL; c = c + 1; @@ -530,25 +842,27 @@ 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; + vector m1, m2; vector v; if (wp.wpisbox) { - m1 = wp.absmin; - m2 = wp.absmax; + m1 = wp.origin + wp.mins; + m2 = wp.origin + wp.maxs; v.x = bound(m1_x, p.x, m2_x); v.y = bound(m1_y, p.y, m2_y); v.z = bound(m1_z, p.z, m2_z); } 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, w, wp); + if (wp.wpcost > cost) { - wp.wpcost = cost2; + wp.wpcost = cost; wp.enemy = w; wp.wpfire = 1; wp.wpnearestpoint = v; @@ -683,16 +997,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); @@ -710,6 +1017,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; @@ -751,7 +1061,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"); @@ -759,7 +1069,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; @@ -770,7 +1080,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? @@ -863,7 +1173,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)); @@ -891,8 +1220,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, nwp, e); + 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) { @@ -910,6 +1240,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 @@ -927,7 +1264,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; + float dest_height; + SET_TRACEWALK_DESTCOORDS(e, e.origin, dest, dest_height); + if (tracewalk(this, startposition, STAT(PL_MIN, this), STAT(PL_MAX, this), dest, dest_height, bot_navigation_movemode)) return true; entity nearest_wp = NULL; @@ -946,8 +1286,19 @@ 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) + { + SET_TRACEWALK_DESTCOORDS(e, e.origin, dest, dest_height); + 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 (;;) @@ -967,17 +1318,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) @@ -986,31 +1332,38 @@ 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); + if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT) + return; + // 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(vec2(this.velocity), >=, autocvar_sv_maxspeed)) // if -really- running { if(vdist(this.origin - this.goalcurrent.origin, <, 150)) { @@ -1026,6 +1379,8 @@ void navigation_poptouchedgoals(entity this) } navigation_poproute(this); + if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT) + return; } } } @@ -1039,10 +1394,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 @@ -1054,6 +1406,8 @@ void navigation_poptouchedgoals(entity this) } navigation_poproute(this); + if(this.goalcurrent && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT) + return; } } @@ -1095,11 +1449,13 @@ void botframe_updatedangerousobjects(float maxupdate) vector m1, m2, v, o; float c, d, danger; c = 0; + entity wp_cur; IL_EACH(g_waypoints, true, { danger = 0; - m1 = it.mins; - m2 = it.maxs; + m1 = it.absmin; + m2 = it.absmax; + wp_cur = it; IL_EACH(g_bot_dodge, it.bot_dodge, { v = it.origin; @@ -1107,7 +1463,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, it, wp_cur); if (d > 0) { traceline(o, v, true, NULL); @@ -1145,7 +1501,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 8f1f03ad1..a826715a8 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,83 @@ 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)) + +// if ent is a box waypoint or an item v is set to coords of ent that are closer to org +#define SET_DESTCOORDS(ent, org, v) MACRO_BEGIN { \ + if ((ent.classname != "waypoint") || ent.wpisbox) { \ + vector wm1 = ent.origin + ent.mins; \ + vector wm2 = ent.origin + ent.maxs; \ + 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 = ent.origin; \ + } \ +} MACRO_END + +// if ent is a box waypoint or an item v is set to coords of ent that are closer to org +// (but v.z is set to the lowest coord of ent), v_height is set to ent's height +#define SET_TRACEWALK_DESTCOORDS(ent, org, v, v_height) MACRO_BEGIN { \ + if ((ent.classname != "waypoint") || ent.wpisbox) { \ + vector wm1 = ent.origin + ent.mins; \ + vector wm2 = ent.origin + ent.maxs; \ + v.x = bound(wm1.x, org.x, wm2.x); \ + v.y = bound(wm1.y, org.y, wm2.y); \ + v.z = wm1.z; \ + v_height = wm2.z - wm1.z; \ + } else { \ + v = ent.origin; \ + v_height = 0; \ + } \ +} MACRO_END + +// if ent is a box waypoint or an item v and v2 are set to coords of ent that are closer to org +// (but v2.z is set to the lowest coord of ent), v2_height is set to ent's height +#define SET_TRACEWALK_DESTCOORDS_2(ent, org, v, v2, v2_height) MACRO_BEGIN { \ + if ((ent.classname != "waypoint") || ent.wpisbox) { \ + vector wm1 = ent.origin + ent.mins; \ + vector wm2 = ent.origin + ent.maxs; \ + 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); \ + v2.x = v.x; \ + v2.y = v.y; \ + v2.z = wm1.z; \ + v2_height = wm2.z - wm1.z; \ + } else { \ + v = ent.origin; \ + v2 = v; \ + v2_height = 0; \ + } \ +} MACRO_END .entity wp_goal_prev0; .entity wp_goal_prev1; -.float nearestwaypointtimeout; .float lastteleporttime; .float blacklisted; @@ -52,6 +124,12 @@ void navigation_dynamicgoal_init(entity this, bool initially_static); void navigation_dynamicgoal_set(entity this); void navigation_dynamicgoal_unset(entity this); +.int nav_submerged_state; +#define SUBMERGED_UNDEFINED 0 +#define SUBMERGED_NO 1 +#define SUBMERGED_YES 2 +bool navigation_check_submerged_state(entity ent, vector pos); + /* * Functions @@ -63,7 +141,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); @@ -83,4 +161,4 @@ void navigation_unstuck(entity this); void botframe_updatedangerousobjects(float maxupdate); entity navigation_findnearestwaypoint(entity ent, float walkfromwp); -float navigation_waypoint_will_link(vector v, vector org, entity ent, float walkfromwp, float bestdist); +float navigation_waypoint_will_link(vector v, vector org, entity ent, vector v2, float v2_height, float walkfromwp, float bestdist); diff --git a/qcsrc/server/bot/default/waypoints.qc b/qcsrc/server/bot/default/waypoints.qc index c8a958ddb..50af1d519 100644 --- a/qcsrc/server/bot/default/waypoints.qc +++ b/qcsrc/server/bot/default/waypoints.qc @@ -13,10 +13,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) @@ -31,6 +170,8 @@ void waypoint_setupmodel(entity wp) wp.colormod = '1 0 0'; else if (wp.wpflags & WAYPOINTFLAG_GENERATED) wp.colormod = '1 1 0'; + else if (wp.wphardwired) + wp.colormod = '0.5 0 1'; else wp.colormod = '1 1 1'; } @@ -45,7 +186,13 @@ 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, em2 = m2; + if (!(f & WAYPOINTFLAG_GENERATED) && 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; }); @@ -92,6 +239,120 @@ 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); + if(!e) + { + LOG_INFOF("Couldn't spawn waypoint at %v\n", org); + return; + } + waypoint_schedulerelink(e); + bprint(strcat("Waypoint spawned at ", vtos(e.origin), "\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)) @@ -145,41 +406,113 @@ 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; +} +float waypoint_getlinearcost_underwater(float dist) +{ + // NOTE: this value is hardcoded on the engine too, see SV_WaterMove + return dist / (autocvar_sv_maxspeed * 0.7); +} - if (waypoint_islinked(from, to)) - return; +float waypoint_gettravelcost(vector from, vector to, entity from_ent, entity to_ent) +{ + bool submerged_from = navigation_check_submerged_state(from_ent, from); + bool submerged_to = navigation_check_submerged_state(to_ent, to); + + if (submerged_from && submerged_to) + return waypoint_getlinearcost_underwater(vlen(to - from)); + + 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; - 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; - 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); + 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; } - else - c = vlen(to.origin - from.origin); + + if (submerged_from || submerged_to) + return (c + waypoint_getlinearcost_underwater(vlen(to - from))) / 2; + return c; +} + +float waypoint_getlinkcost(entity from, entity to) +{ + vector v1 = from.origin; + vector v2 = to.origin; + if (from.wpisbox) + { + vector m1 = from.absmin, m2 = from.absmax; + v1.x = bound(m1.x, v2.x, m2.x); + v1.y = bound(m1.y, v2.y, m2.y); + v1.z = bound(m1.z, v2.z, m2.z); + } + if (to.wpisbox) + { + vector m1 = to.absmin, m2 = to.absmax; + v2.x = bound(m1.x, v1.x, m2.x); + v2.y = bound(m1.y, v1.y, m2.y); + v2.z = bound(m1.z, v1.z, m2.z); + } + return waypoint_gettravelcost(v1, v2, from, to); +} + +// 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; @@ -216,20 +549,24 @@ 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!) void waypoint_think(entity this) { - vector sv, sm1, sm2, ev, em1, em2, dv; + vector sv, sv2, ev, ev2, dv; + float sv2_height, ev2_height; bot_calculate_stepheightvec(); bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL); //dprint("waypoint_think wpisbox = ", ftos(this.wpisbox), "\n"); - sm1 = this.origin + this.mins; - sm2 = this.origin + this.maxs; IL_EACH(g_waypoints, this != it, { if (boxesoverlap(this.absmin, this.absmax, it.absmin, it.absmax)) @@ -245,16 +582,10 @@ void waypoint_think(entity this) ++relink_pvsculled; continue; } - sv = it.origin; - sv.x = bound(sm1_x, sv.x, sm2_x); - sv.y = bound(sm1_y, sv.y, sm2_y); - sv.z = bound(sm1_z, sv.z, sm2_z); - ev = this.origin; - em1 = it.origin + it.mins; - em2 = it.origin + it.maxs; - ev.x = bound(em1_x, ev.x, em2_x); - ev.y = bound(em1_y, ev.y, em2_y); - ev.z = bound(em1_z, ev.z, em2_z); + + SET_TRACEWALK_DESTCOORDS_2(this, it.origin, sv, sv2, sv2_height); + SET_TRACEWALK_DESTCOORDS_2(it, this.origin, ev, ev2, ev2_height); + dv = ev - sv; dv.z = 0; if(vdist(dv, >=, 1050)) // max search distance in XY @@ -262,35 +593,30 @@ void waypoint_think(entity this) ++relink_lengthculled; continue; } + navigation_testtracewalk = 0; - if (!this.wpisbox) - { - tracebox(sv - PL_MIN_CONST.z * '0 0 1', PL_MIN_CONST, PL_MAX_CONST, sv, false, this); - if (!trace_startsolid) - { - //dprint("sv deviation", vtos(trace_endpos - sv), "\n"); - sv = trace_endpos + '0 0 1'; - } - } - if (!it.wpisbox) - { - tracebox(ev - PL_MIN_CONST.z * '0 0 1', PL_MIN_CONST, PL_MAX_CONST, ev, false, it); - if (!trace_startsolid) - { - //dprint("ev deviation", vtos(trace_endpos - ev), "\n"); - 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 + { + if (tracewalk(this, sv, PL_MIN_CONST, PL_MAX_CONST, ev2, ev2_height, MOVE_NOMONSTERS)) + waypoint_addlink(this, it); + else + relink_walkculled += 0.5; + } + + if (it.wpisbox) relink_walkculled += 0.5; + else + { + if (tracewalk(it, ev, PL_MIN_CONST, PL_MAX_CONST, sv2, sv2_height, MOVE_NOMONSTERS)) + waypoint_addlink(it, this); + else + relink_walkculled += 0.5; + } } }); navigation_testtracewalk = 0; @@ -344,25 +670,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() @@ -376,7 +683,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; @@ -554,6 +861,8 @@ void waypoint_load_or_remove_links_hardwired(bool removal_mode) waypoint_addlink(wp_from, wp_to); wp_from.wphardwired = true; wp_to.wphardwired = true; + waypoint_setupmodel(wp_from); + waypoint_setupmodel(wp_to); } fclose(file); @@ -562,9 +871,6 @@ void waypoint_load_or_remove_links_hardwired(bool removal_mode) LOG_TRACE("loaded ", ftos(c), " waypoint links from maps/", mapname, ".waypoints.hardwired"); } -void waypoint_load_links_hardwired() { waypoint_load_or_remove_links_hardwired(false); } -void waypoint_remove_links_hardwired() { waypoint_load_or_remove_links_hardwired(true); } - entity waypoint_get_link(entity w, float i) { switch(i) @@ -717,20 +1023,18 @@ float waypoint_loadall() return cwp + cwb; } -vector waypoint_fixorigin(vector position) +vector waypoint_fixorigin(vector position, entity tracetest_ent) { - tracebox(position + '0 0 1' * (1 - PL_MIN_CONST.z), PL_MIN_CONST, PL_MAX_CONST, position + '0 0 -512', MOVE_NOMONSTERS, NULL); + tracebox(position + '0 0 1' * (1 - PL_MIN_CONST.z), PL_MIN_CONST, PL_MAX_CONST, position + '0 0 -512', MOVE_NOMONSTERS, tracetest_ent); if(trace_fraction < 1) position = trace_endpos; - //traceline(position, position + '0 0 -512', MOVE_NOMONSTERS, NULL); - //print("position is ", ftos(trace_endpos_z - position_z), " above solid\n"); return position; } void waypoint_spawnforitem_force(entity e, vector org) { // Fix the waypoint altitude if necessary - org = waypoint_fixorigin(org); + org = waypoint_fixorigin(org, NULL); // don't spawn an item spawnfunc_waypoint if it already exists IL_EACH(g_waypoints, true, @@ -764,11 +1068,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; @@ -779,17 +1083,17 @@ void waypoint_spawnforteleporter_boxes(entity e, vector org1, vector org2, vecto e.nearestwaypointtimeout = -1; } -void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken) +void waypoint_spawnforteleporter_wz(entity e, vector org, vector destination, float timetaken, entity tracetest_ent) { - org = waypoint_fixorigin(org); - destination = waypoint_fixorigin(destination); - waypoint_spawnforteleporter_boxes(e, org, org, destination, destination, timetaken); + org = waypoint_fixorigin(org, tracetest_ent); + destination = waypoint_fixorigin(destination, tracetest_ent); + waypoint_spawnforteleporter_boxes(e, WAYPOINTFLAG_TELEPORT, org, org, destination, destination, timetaken); } -void waypoint_spawnforteleporter(entity e, vector destination, float timetaken) +void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent) { - destination = waypoint_fixorigin(destination); - waypoint_spawnforteleporter_boxes(e, e.absmin, e.absmax, destination, destination, timetaken); + destination = waypoint_fixorigin(destination, tracetest_ent); + waypoint_spawnforteleporter_boxes(e, WAYPOINTFLAG_TELEPORT, e.absmin - PL_MAX_CONST + '1 1 1', e.absmax - PL_MIN_CONST + '-1 -1 -1', destination, destination, timetaken); } entity waypoint_spawnpersonal(entity this, vector position) @@ -799,7 +1103,7 @@ entity waypoint_spawnpersonal(entity this, vector position) // drop the waypoint to a proper location: // first move it up by a player height // then move it down to hit the floor with player bbox size - position = waypoint_fixorigin(position); + position = waypoint_fixorigin(position, this); w = waypoint_spawn(position, position, WAYPOINTFLAG_GENERATED | WAYPOINTFLAG_PERSONAL); w.nearestwaypoint = NULL; @@ -822,6 +1126,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) @@ -844,38 +1177,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); } } }); @@ -927,7 +1232,7 @@ float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .en // if wp -> porg, then OK float maxdist; - if(navigation_waypoint_will_link(wp.origin, porg, p, walkfromwp, 1050)) + if(navigation_waypoint_will_link(wp.origin, porg, p, wp.origin, 0, walkfromwp, 1050)) { // we may find a better one maxdist = vlen(wp.origin - porg); @@ -943,8 +1248,8 @@ float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .en { float d = vlen(wp.origin - it.origin) + vlen(it.origin - porg); if(d < bestdist) - if(navigation_waypoint_will_link(wp.origin, it.origin, p, walkfromwp, 1050)) - if(navigation_waypoint_will_link(it.origin, porg, p, walkfromwp, 1050)) + if(navigation_waypoint_will_link(wp.origin, it.origin, p, wp.origin, 0, walkfromwp, 1050)) + if(navigation_waypoint_will_link(it.origin, porg, p, it.origin, 0, walkfromwp, 1050)) { bestdist = d; p.(fld) = it; @@ -998,7 +1303,7 @@ float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .en if(wp) { - if(!navigation_waypoint_will_link(wp.origin, o, p, walkfromwp, 1050)) + if(!navigation_waypoint_will_link(wp.origin, o, p, wp.origin, 0, walkfromwp, 1050)) { // we cannot walk from wp.origin to o // get closer to tmax @@ -1024,7 +1329,7 @@ float botframe_autowaypoints_fix_from(entity p, float walkfromwp, entity wp, .en // if we get here, o is valid regarding waypoints // check if o is connected right to the player // we break if it succeeds, as that means o is a good waypoint location - if(navigation_waypoint_will_link(o, porg, p, walkfromwp, 1050)) + if(navigation_waypoint_will_link(o, porg, p, o, 0, walkfromwp, 1050)) break; // o is no good, we need to get closer to the player @@ -1071,6 +1376,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 38d57a04a..6eab8a4d5 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,30 +30,43 @@ 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 from, vector to, entity from_ent, entity to_ent); +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(); void waypoint_saveall(); void waypoint_spawnforitem_force(entity e, vector org); void waypoint_spawnforitem(entity e); -void waypoint_spawnforteleporter(entity e, vector destination, float timetaken); -void waypoint_spawnforteleporter_v(entity e, vector org, vector destination, float timetaken); +void waypoint_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent); +void waypoint_spawnforteleporter_wz(entity e, vector org, vector destination, float timetaken, entity tracetest_ent); void botframe_showwaypointlinks(); float waypoint_loadall(); -float waypoint_load_links(); +bool waypoint_load_links(); +#define waypoint_load_links_hardwired() waypoint_load_or_remove_links_hardwired(false) +#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); entity waypoint_spawn(vector m1, vector m2, float f); entity waypoint_spawnpersonal(entity this, vector position); -vector waypoint_fixorigin(vector position); +void waypoint_unreachable(entity pl); + +vector waypoint_fixorigin(vector position, entity tracetest_ent); void botframe_autowaypoints(); diff --git a/qcsrc/server/bot/null/bot_null.qc b/qcsrc/server/bot/null/bot_null.qc index 68ae41670..ddf7abd9d 100644 --- a/qcsrc/server/bot/null/bot_null.qc +++ b/qcsrc/server/bot/null/bot_null.qc @@ -28,15 +28,17 @@ 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) { } 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_spawnforteleporter(entity e, vector destination, float timetaken, entity tracetest_ent) { } +void waypoint_spawnforteleporter_wz(entity e, vector org, vector destination, float timetaken, entity tracetest_ent) { } +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 49bee362e..241a48e5c 100644 --- a/qcsrc/server/command/sv_cmd.qc +++ b/qcsrc/server/command/sv_cmd.qc @@ -1566,11 +1566,13 @@ void GameCommand_trace(float request, float argc) case "walk": { - if (argc == 4) + if (argc == 4 || argc == 5) { 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)), stof(argv(4)), MOVE_NORMAL)) + LOG_INFO("can walk\n"); + else + LOG_INFO("cannot walk\n"); return; } } @@ -1596,7 +1598,9 @@ void GameCommand_trace(float request, float argc) LOG_INFO("Incorrect parameters for ^2trace^7\n"); case CMD_REQUEST_USAGE: { - LOG_INFO("\nUsage:^3 sv_cmd trace command (startpos endpos)\n"); + LOG_INFO("\nUsage:^3 sv_cmd trace command [startpos endpos] [endpos_height]\n"); + LOG_INFO(" Where startpos and endpos are parameters for 'walk' and 'showline' commands,\n"); + LOG_INFO(" 'endpos_height' is an optional parameter for 'walk' command,\n"); LOG_INFO(" Full list of commands here: \"debug, debug2, walk, showline.\"\n"); LOG_INFO("See also: ^2bbox, gettaginfo^7\n"); return; diff --git a/qcsrc/server/impulse.qc b/qcsrc/server/impulse.qc index d9036dea5..f528f07b4 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 74dce96a2..442ffa88d 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) @@ -413,6 +411,9 @@ LABEL(cvar_fail) void WarpZone_PostInitialize_Callback() { // create waypoint links for warpzones + entity tracetest_ent = spawn(); + setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST); + tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; //for(entity e = warpzone_first; e; e = e.warpzone_next) for(entity e = NULL; (e = find(e, classname, "trigger_warpzone")); ) { @@ -423,6 +424,7 @@ void WarpZone_PostInitialize_Callback() dst = (e.enemy.absmin + e.enemy.absmax) * 0.5; makevectors(e.enemy.warpzone_angles); dst = dst + ((e.enemy.warpzone_origin - dst) * v_forward) * v_forward - 16 * v_right; - waypoint_spawnforteleporter_v(e, src, dst, 0); + waypoint_spawnforteleporter_wz(e, src, dst, 0, tracetest_ent); } + delete(tracetest_ent); }