Files

79 lines
3.4 KiB
GDScript

class_name BotDirector
extends RefCounted
var game: Node3D
var routes: BotRoutes
var steering: Dictionary[int, BotSteering] = {}
func _init(owner_game: Node3D) -> void:
game = owner_game
routes = BotRoutes.new(game.arena)
func update(p: Fighter, dt: float) -> void:
if p.hp <= 0:
return
p.bot_think -= dt
if not steering.has(p.peer_id): steering[p.peer_id] = BotSteering.new()
var motion: BotSteering = steering[p.peer_id]
var skill := BotSkill.profile(int(game.config.get("difficulty", 0)))
var enemy: Fighter = null
var nearest: float = skill.range
for candidate: Fighter in game.players.values():
if not game.enemies(p, candidate) or candidate.hp <= 0:
continue
var distance := p.global_position.distance_to(candidate.global_position)
if distance < nearest and game.visible_between(p.eye(), candidate.eye(), [p.get_rid(), candidate.get_rid()]):
nearest = distance
enemy = candidate
var shooting := false
if enemy:
if p.bot_enemy != enemy.peer_id:
p.bot_enemy = enemy.peer_id
p.bot_acquired = 0
p.bot_acquired += dt
# Aim at the torso; difficulty affects aim and decisions, never damage/HP.
var direction := (enemy.eye() - Vector3.UP * 0.25 - p.eye()).normalized()
var target_yaw := atan2(-direction.x, -direction.z)
var target_pitch := asin(direction.y)
var clock := Time.get_ticks_msec() * 0.0025 + p.peer_id * 1.7
p.yaw = lerp_angle(p.yaw, target_yaw + sin(clock) * float(skill.error), minf(dt * float(skill.tracking), 1))
p.pitch = lerpf(p.pitch, target_pitch + cos(clock * 1.3) * float(skill.error), minf(dt * float(skill.tracking), 1))
var burst_time := fmod(maxf(0, p.bot_acquired - float(skill.reaction)), float(skill.burst) + float(skill.pause))
shooting = p.bot_acquired >= float(skill.reaction) and burst_time < float(skill.burst) and absf(angle_difference(p.yaw, target_yaw)) < 0.12
else:
p.bot_enemy = 0
p.bot_acquired = 0
var movement := Vector2.ZERO
var wants_movement := not enemy or nearest > 12 or absf(enemy.global_position.y - p.global_position.y) > 2
if wants_movement and motion.needs_repath(p, dt):
p.bot_path.clear()
p.bot_think = 0
if motion.repaths >= 2: p.bot_phase += 1
if p.bot_think <= 0:
p.bot_think = randf_range(0.8, 1.3)
if enemy and wants_movement and motion.repaths < 2:
p.bot_target = enemy.global_position
elif p.bot_path.is_empty():
p.bot_phase += 1
p.bot_target = routes.patrol(p.peer_id, p.bot_phase)
if wants_movement:
p.bot_path = routes.path(p.global_position, p.bot_target)
while not p.bot_path.is_empty() and BotSteering.reached(p.global_position, p.bot_path[0]):
p.bot_path.remove_at(0)
if not p.bot_path.is_empty() and wants_movement:
var next: Vector3 = p.bot_path[0]
var world_dir := BotSteering.avoid(p, next - p.global_position, game.players.values())
if not enemy and world_dir.length_squared() > 0.01:
p.yaw = lerp_angle(p.yaw, atan2(-world_dir.x, -world_dir.z), 1 - exp(-dt * 9))
p.pitch = lerpf(p.pitch, 0, 1 - exp(-dt * 5))
var local_dir := Basis(Vector3.UP, -p.yaw) * world_dir
movement = Vector2(local_dir.x, local_dir.z)
p.input_data = {"move": movement, "fire": shooting, "ads": enemy != null,
"sprint": enemy == null, "reload": p.magazines[p.weapon] == 0}
if enemy and p.bot_acquired > 1.5 and nearest > 7 and nearest < 20 and p.grenades > 0 and p.bot_grenade_cooldown <= 0:
p.input_data["grenade"] = true
p.bot_grenade_cooldown = 12
if p.flash_left > 0.8:
p.input_data["fire"] = false
p.input_data["grenade"] = false