lgbot: steer only within LG reach on the same floor; let the AI walk around walls
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
f494223305
commit
a998614224
4 files changed
+89
-22
No files matched your search
@@ -27,7 +27,10 @@ PRESETS = {
|
||||
}
|
||||
PUSH_RANGE, BACKOFF_RANGE = 250, 700
|
||||
BACKOFF_HEALTH, PUSH_LEAD = 60, 30
|
||||
MAX_TARGET_DIST = 1500
|
||||
# Steering is straight-line (no pathfinding), so only steer when the target is within LG reach and
|
||||
# on about the same floor; otherwise the bot's own navigation walks it over.
|
||||
MAX_TARGET_DIST = 900
|
||||
MAX_TARGET_DZ = 200
|
||||
BOT_STEAM_ID = 90071996842377216 # minqlx gives bots this + their client number
|
||||
|
||||
|
||||
@@ -129,15 +132,43 @@ def fix_z(vz):
|
||||
return 0.0 if 200 <= vz <= 320 else vz
|
||||
|
||||
|
||||
def pick_target(bot_xy, candidates, max_dist=MAX_TARGET_DIST):
|
||||
def pick_target(bot_pos, candidates, max_dist=MAX_TARGET_DIST, max_dz=MAX_TARGET_DZ):
|
||||
"""Nearest candidate (pos = x, y, z) within max_dist horizontally and max_dz vertically."""
|
||||
best, best_d = None, max_dist
|
||||
for xy, obj in candidates:
|
||||
d = math.hypot(xy[0] - bot_xy[0], xy[1] - bot_xy[1])
|
||||
for pos, obj in candidates:
|
||||
if abs(pos[2] - bot_pos[2]) > max_dz:
|
||||
continue
|
||||
d = math.hypot(pos[0] - bot_pos[0], pos[1] - bot_pos[1])
|
||||
if d <= best_d:
|
||||
best, best_d = obj, d
|
||||
return best
|
||||
|
||||
|
||||
class StuckGuard:
|
||||
"""Lets go of the bot when steering keeps getting blocked (a wall, a ledge): if the speed it
|
||||
makes along the commanded direction stays under `ratio` of the command for `after_ms`, steering
|
||||
is off for `release_ms` so the bot AI can walk around the obstacle."""
|
||||
|
||||
def __init__(self, ratio=0.6, after_ms=500, release_ms=1500):
|
||||
self.ratio, self.after_ms, self.release_ms = ratio, after_ms, release_ms
|
||||
self.slow_since = None
|
||||
self.off_until = 0
|
||||
|
||||
def update(self, now_ms, cmd_speed, along_speed):
|
||||
if now_ms < self.off_until:
|
||||
return False
|
||||
if cmd_speed > 1 and along_speed < cmd_speed * self.ratio:
|
||||
if self.slow_since is None:
|
||||
self.slow_since = now_ms
|
||||
elif now_ms - self.slow_since >= self.after_ms:
|
||||
self.slow_since = None
|
||||
self.off_until = now_ms + self.release_ms
|
||||
return False
|
||||
else:
|
||||
self.slow_since = None
|
||||
return True
|
||||
|
||||
|
||||
try:
|
||||
import minqlx
|
||||
except ImportError: # imported by tests / the setup script outside the server
|
||||
@@ -156,7 +187,9 @@ if minqlx:
|
||||
self.settings, self.read_at, self.log_at = None, 0.0, 0.0
|
||||
self.debug = self.target_bots = False
|
||||
self.failed = False
|
||||
self.last_cmd, self.slow_frames, self.stuck = None, 0, 0 # debug stats
|
||||
self.guard = StuckGuard()
|
||||
self.last_cmd = None # last command (for the stuck guard)
|
||||
self.dbg_cmd, self.slow_frames, self.stuck = None, 0, 0 # debug stats
|
||||
self.add_hook("frame", self.handle_frame)
|
||||
|
||||
def handle_frame(self):
|
||||
@@ -177,6 +210,7 @@ if minqlx:
|
||||
return self.settings
|
||||
|
||||
def idle(self, now, why):
|
||||
self.last_cmd = None # not steering this frame: nothing for the stuck guard to judge
|
||||
if self.debug and now - self.log_at >= 1.0:
|
||||
self.log_at = now
|
||||
self.logger.info("lgbot idle: %s", why)
|
||||
@@ -197,34 +231,43 @@ if minqlx:
|
||||
if is_bot(p.steam_id) and not self.target_bots:
|
||||
continue
|
||||
pos = p.position()
|
||||
cands.append(((pos.x, pos.y), p))
|
||||
cands.append(((pos.x, pos.y, pos.z), p))
|
||||
bpos = bot.position()
|
||||
target = pick_target((bpos.x, bpos.y), cands)
|
||||
if target is None: # nobody to duel: stock AI roams
|
||||
return self.idle(now, "no target within %d (%d candidates)" % (MAX_TARGET_DIST, len(cands)))
|
||||
target = pick_target((bpos.x, bpos.y, bpos.z), cands)
|
||||
if target is None: # nobody to duel close by: the bot AI walks over
|
||||
return self.idle(now, "no target within %d on this floor (%d candidates)" % (MAX_TARGET_DIST, len(cands)))
|
||||
vel = bot.velocity()
|
||||
cmd_speed, along = 0.0, 0.0
|
||||
if self.last_cmd:
|
||||
cmd_speed = math.hypot(*self.last_cmd)
|
||||
if cmd_speed > 1:
|
||||
along = (vel.x * self.last_cmd[0] + vel.y * self.last_cmd[1]) / cmd_speed
|
||||
if not self.guard.update(int(now * 1000), cmd_speed, along):
|
||||
return self.idle(now, "blocked, letting the bot AI walk around it")
|
||||
tpos = target.position()
|
||||
direction = self.strafer.step(int(now * 1000), s)
|
||||
want = wanted_range(s, bot.health, target.health)
|
||||
vx, vy = steer((bpos.x, bpos.y), (tpos.x, tpos.y), direction, s, want)
|
||||
vel = bot.velocity()
|
||||
comp = FRICTION_COMP if abs(vel.z) < 1 else 1.0
|
||||
bot.velocity(x=vx * comp, y=vy * comp, z=fix_z(vel.z))
|
||||
self.last_cmd = (vx, vy)
|
||||
if self.debug:
|
||||
self.debug_line(now, vel, (vx, vy), tpos, bpos, want, direction)
|
||||
|
||||
def debug_line(self, now, vel, cmd, tpos, bpos, want, direction):
|
||||
# cos = how well last frame's command survived the frame (1 = exactly our direction);
|
||||
# stuck = times actual speed stayed under half the command for > 0.5 s (walls)
|
||||
# stuck = times actual speed stayed under half the command for > 0.5 s (walls; the
|
||||
# StuckGuard is what acts on it, this is only the count)
|
||||
actual = math.hypot(vel.x, vel.y)
|
||||
cos = 0.0
|
||||
if self.last_cmd:
|
||||
lc = math.hypot(*self.last_cmd)
|
||||
if self.dbg_cmd:
|
||||
lc = math.hypot(*self.dbg_cmd)
|
||||
if lc > 1 and actual > 1:
|
||||
cos = (vel.x * self.last_cmd[0] + vel.y * self.last_cmd[1]) / (lc * actual)
|
||||
cos = (vel.x * self.dbg_cmd[0] + vel.y * self.dbg_cmd[1]) / (lc * actual)
|
||||
self.slow_frames = self.slow_frames + 1 if actual < lc / 2 else 0
|
||||
if self.slow_frames == 20:
|
||||
self.stuck += 1
|
||||
self.last_cmd = cmd
|
||||
self.dbg_cmd = cmd
|
||||
if now - self.log_at >= 1.0:
|
||||
self.log_at = now
|
||||
self.logger.info("lgbot dist=%d want=%d dir=%d cmd=%d actual=%d cos=%.2f stuck=%d z=%d",
|
||||
|
||||
Reference in new issue
Block a user