Files
tekton/scripts/npcs/mekton_bull.gd
T

163 lines
4.3 KiB
GDScript

extends Area3D
enum State {
ROAM,
CHARGE,
COOLDOWN
}
var current_state: State = State.ROAM
var gridmap: Node
var arena_manager: Node
var move_speed: float = 3.0
var charge_speed: float = 12.0
var target_pos: Vector3
var state_timer: float = 0.0
var slow_timer: float = 0.0
func _ready():
add_to_group("MektonBulls")
body_entered.connect(_on_body_entered)
func initialize(manager: Node, grid: Node, start_pos: Vector3):
arena_manager = manager
gridmap = grid
position = start_pos
_pick_roam_target()
func apply_slow(duration: float) -> void:
slow_timer = duration
func _physics_process(delta: float):
if not multiplayer.is_server():
return
state_timer -= delta
if slow_timer > 0:
slow_timer -= delta
match current_state:
State.ROAM:
_process_roam(delta)
if state_timer <= 0:
_try_start_charge()
State.CHARGE:
_process_charge(delta)
State.COOLDOWN:
if state_timer <= 0:
current_state = State.ROAM
_pick_roam_target()
func _process_roam(delta: float):
var dist = position.distance_to(target_pos)
if dist < 0.1:
_pick_roam_target()
else:
var dir = (target_pos - position).normalized()
dir.y = 0
if dir.length_squared() > 0:
var actual_speed = move_speed
if slow_timer > 0: actual_speed *= 0.5
position += dir * actual_speed * delta
# Face direction implicitly
var look_target = position + dir
look_target.y = position.y
if position.distance_squared_to(look_target) > 0.01:
look_at(look_target, Vector3.UP)
func _pick_roam_target():
if not arena_manager: return
var bounds = arena_manager.arena_size_for_phase(arena_manager.current_phase)
# Random walkable position within bounds
# We know 0,0 is boundary, bounds.x-1 is boundary
var min_x = 1
var max_x = bounds.x - 2
var min_z = 1
var max_z = bounds.y - 2
if min_x > max_x: max_x = min_x
if min_z > max_z: max_z = min_z
# Avoid exact center (where the static delivery target presumably sits)
var cx = int(bounds.x / 2.0)
var cz = int(bounds.y / 2.0)
var rx = cx
var rz = cz
while rx == cx and rz == cz:
rx = randi_range(min_x, max_x)
rz = randi_range(min_z, max_z)
var world_x = rx * gridmap.cell_size.x + gridmap.cell_size.x / 2.0
var world_z = rz * gridmap.cell_size.z + gridmap.cell_size.z / 2.0
target_pos = Vector3(world_x, position.y, world_z)
state_timer = randf_range(2.0, 4.0)
func _try_start_charge():
# Find closest player
var players = get_tree().get_nodes_in_group("Players")
var closest_player = null
var min_dist = INF
for p in players:
if p.is_in_group("Players") and p.has_method("is_eliminated") and not p.is_eliminated():
var d = position.distance_to(p.position)
if d < min_dist and d < 15.0: # Range check
min_dist = d
closest_player = p
if closest_player:
current_state = State.CHARGE
target_pos = closest_player.position
target_pos.y = position.y
state_timer = 2.0 # Max charge duration
if has_node("/root/SfxManager"):
get_node("/root/SfxManager").play_rpc("bull_charge")
else:
_pick_roam_target()
func _process_charge(delta: float):
var dir = (target_pos - position).normalized()
dir.y = 0
var actual_speed = charge_speed
if slow_timer > 0: actual_speed *= 0.5
position += dir * actual_speed * delta
var look_target = position + dir
look_target.y = position.y
if position.distance_squared_to(look_target) > 0.01:
look_at(look_target, Vector3.UP)
var dist = position.distance_to(target_pos)
if dist < 0.5 or state_timer <= 0:
# Hit destination or timeout
current_state = State.COOLDOWN
state_timer = 1.5
func _on_body_entered(body: Node3D):
if body.is_in_group("Players") and multiplayer.is_server():
# Knock them out
if body.has_method("eliminate"):
body.eliminate()
else:
print("[MektonBull] Knocked out player", body.name)
# Dispatch via manager
if arena_manager and arena_manager.has_signal("player_eliminated"):
arena_manager.player_eliminated.emit(body.name.to_int())
# Polish: SFX + Camera Shake
rpc("sync_bull_impact")
@rpc("authority", "call_local", "unreliable")
func sync_bull_impact() -> void:
if has_node("/root/SfxManager"):
get_node("/root/SfxManager").play("bull_impact")
var root = get_tree().root
var main = root.get_node_or_null("Main")
if main and main.get("screen_shake_manager"):
main.screen_shake_manager.shake(0.2, 0.5)