tracetoss(e, e);
if(trace_startsolid)
return false;
+
+ if(!jp.height)
+ {
+ // since tracetoss starting from jumppad's origin often fails when target
+ // is very close to real destination, start it directly from target's
+ // origin instead
+ e.velocity_z = 0;
+ setorigin(e, targ.origin + stepheightvec);
+ tracetoss(e, e);
+ if(trace_startsolid)
+ {
+ setorigin(e, targ.origin + stepheightvec / 2);
+ tracetoss(e, e);
+ if(trace_startsolid)
+ {
+ setorigin(e, targ.origin);
+ tracetoss(e, e);
+ if(trace_startsolid)
+ return false;
+ }
+ }
+ }
+
if(e.move_movetype == MOVETYPE_NONE)
{
tracebox(trace_endpos, e.mins, e.maxs, trace_endpos - eZ * 1500, true, jp);
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;
entity e = spawn();
setsize(e, PL_MIN_CONST, PL_MAX_CONST);
e.velocity = trigger_push_calculatevelocity(org, t, this.height);
+ vel = e.velocity;
vector best_target = '0 0 0';
vector best_org = '0 0 0';
vector best_vel = '0 0 0';
vector ofs = flatdir * 0.5 * min(fabs(this.absmax.x - this.absmin.x), fabs(this.absmax.y - this.absmin.y));
new_org = org + ofs;
e.velocity = trigger_push_calculatevelocity(new_org, t, this.height);
+ vel = e.velocity;
+ if(vlen(vec2(e.velocity)) < autocvar_sv_maxspeed)
+ e.velocity = autocvar_sv_maxspeed * flatdir;
if (trigger_push_testorigin(e, t, this, new_org) && (!valid_best_target || trace_endpos.z > best_target.z + 50))
{
best_target = trace_endpos;
best_org = new_org;
- best_vel = e.velocity;
+ best_vel = vel;
valid_best_target = true;
}
new_org = org - ofs;
e.velocity = trigger_push_calculatevelocity(new_org, t, this.height);
+ vel = e.velocity;
+ if(vlen(vec2(e.velocity)) < autocvar_sv_maxspeed)
+ e.velocity = autocvar_sv_maxspeed * flatdir;
if (trigger_push_testorigin(e, t, this, new_org) && (!valid_best_target || trace_endpos.z > best_target.z + 50))
{
best_target = trace_endpos;
best_org = new_org;
- best_vel = e.velocity;
+ best_vel = vel;
valid_best_target = true;
}
}
+
if (valid_best_target)
{
- float distxy = (vlen((best_target - eZ * best_target.z) - (best_org - eZ * best_org.z)));
- float velxy = vlen(best_vel - eZ * best_vel.z);
- waypoint_spawnforteleporter(this, best_target, distxy / velxy);
+ if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, best_target + PL_MIN_CONST, best_target + PL_MAX_CONST)))
+ {
+ float velxy = vlen(vec2(best_vel));
+ float cost = vlen(vec2(t.origin - best_org)) / velxy;
+ if(velxy < autocvar_sv_maxspeed)
+ velxy = autocvar_sv_maxspeed;
+ cost += vlen(vec2(best_target - t.origin)) / velxy;
+ waypoint_spawnforteleporter(this, best_target, cost);
+ }
}
delete(e);
#endif
setsize(e, PL_MIN_CONST, PL_MAX_CONST);
e.velocity = this.movedir;
tracetoss(e, e);
- waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
+ if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, trace_endpos + PL_MIN_CONST, trace_endpos + PL_MAX_CONST)))
+ waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
delete(e);
}
#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;
// 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))
}
}
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))
}
// 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
// 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;
// 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;
// (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;
.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;