class_name BotSteering extends RefCounted var last_position := Vector3.INF var stalled := 0.0 var repaths := 0 var life := -1 func needs_repath(p: Fighter, delta: float) -> bool: if life != p.life: life = p.life last_position = p.global_position stalled = 0 repaths = 0 if p.bot_path.is_empty(): stalled = 0 return false if p.global_position.distance_to(last_position) > 0.3: last_position = p.global_position stalled = 0 repaths = 0 else: stalled += delta if stalled < 1.25: return false stalled = 0 repaths += 1 return true static func reached(position: Vector3, waypoint: Vector3) -> bool: # Navigation surfaces sit one voxel above the physical floor. Compare height # separately so stair landings do not leave an unreachable waypoint forever. return Vector2(position.x - waypoint.x, position.z - waypoint.z).length() < 0.5 and absf(position.y - waypoint.y) < 0.65 static func clear_direction(p: Fighter, direction: Vector3) -> bool: var pose := p.global_transform pose.origin.y += 0.26 return not p.test_move(pose, direction * 0.35) static func avoid(p: Fighter, desired: Vector3, roster: Array) -> Vector3: var direction := Vector3(desired.x, 0, desired.z).normalized() var side := Vector3(-direction.z, 0, direction.x) var separation := Vector3.ZERO for other: Fighter in roster: if other == p or other.hp <= 0 or absf(other.position.y - p.position.y) > 1.2: continue var offset := p.position - other.position offset.y = 0 var distance := offset.length() if distance > 1.8: continue var away := offset.normalized() if distance > 0.01 else side * (1 if p.peer_id > other.peer_id else -1) separation += away * maxf(0, 1.3 - distance) # Both approaching actors keep right; unlike random strafe they do not # mirror one another into the same gap each frame. if direction.dot(-away) > 0.2: separation += side * (1.8 - distance) var candidate := (direction + separation * 1.4).normalized() if clear_direction(p, candidate): return candidate if clear_direction(p, direction): return direction for angle in [0.7, -0.7, 1.3, -1.3]: var alternative := direction.rotated(Vector3.UP, angle) if clear_direction(p, alternative): return alternative * 0.65 return Vector3.ZERO