You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
108 lines
2.7 KiB
108 lines
2.7 KiB
extends BaseUnit |
|
class_name BaseEnemy |
|
|
|
## Base class for all enemies in the game |
|
## Provides common enemy functionality like AI, pathfinding, and targeting |
|
|
|
signal target_changed(new_target: Node) |
|
|
|
## Current target (usually a player) |
|
var current_target: Node = null |
|
## Enemy detection/aggro range |
|
@export var detection_range: float = 10.0 |
|
## Whether this enemy is aggressive (will attack players) |
|
@export var is_aggressive: bool = true |
|
|
|
func _ready(): |
|
super._ready() |
|
|
|
# Enemies should respawn by default |
|
can_respawn = true |
|
|
|
# Connect to health signals for AI reactions |
|
health_changed.connect(_on_enemy_health_changed) |
|
died.connect(_on_enemy_died) |
|
respawned.connect(_on_enemy_respawned) |
|
|
|
func _physics_process(delta): |
|
# Only server handles enemy AI |
|
if not multiplayer.is_server(): |
|
return |
|
|
|
if is_dead: |
|
return |
|
|
|
# Update target if needed |
|
_update_target() |
|
|
|
## Find and update current target |
|
func _update_target(): |
|
# Subclasses can override this to implement custom targeting logic |
|
pass |
|
|
|
## Get all players in range |
|
func get_players_in_range(range_dist: float) -> Array[Node]: |
|
var players_in_range: Array[Node] = [] |
|
|
|
# Find the players container |
|
var level = get_tree().get_current_scene() |
|
if not level or not level.has_node("PlayersContainer"): |
|
return players_in_range |
|
|
|
var players_container = level.get_node("PlayersContainer") |
|
|
|
for player in players_container.get_children(): |
|
if player is Character and not player.is_dead: |
|
var distance = global_position.distance_to(player.global_position) |
|
if distance <= range_dist: |
|
players_in_range.append(player) |
|
|
|
return players_in_range |
|
|
|
## Get nearest player |
|
func get_nearest_player() -> Node: |
|
var players = get_players_in_range(detection_range) |
|
|
|
if players.is_empty(): |
|
return null |
|
|
|
var nearest_player = null |
|
var nearest_distance = INF |
|
|
|
for player in players: |
|
var distance = global_position.distance_to(player.global_position) |
|
if distance < nearest_distance: |
|
nearest_distance = distance |
|
nearest_player = player |
|
|
|
return nearest_player |
|
|
|
## Health changed callback |
|
func _on_enemy_health_changed(old_health: float, new_health: float): |
|
# Subclasses can override to react to damage |
|
pass |
|
|
|
## Death callback |
|
func _on_enemy_died(killer_id: int): |
|
print("[Enemy ", name, "] died. Killer ID: ", killer_id) |
|
|
|
# Subclasses can override for death effects |
|
pass |
|
|
|
## Respawn callback |
|
func _on_enemy_respawned(): |
|
print("[Enemy ", name, "] respawned at ", global_position) |
|
|
|
# Clear target on respawn |
|
current_target = null |
|
target_changed.emit(null) |
|
|
|
# Subclasses can override for respawn effects |
|
pass |
|
|
|
## Override hurt animation |
|
@rpc("any_peer", "call_local", "reliable") |
|
func _play_hurt_animation(): |
|
# Flash red or play hurt animation |
|
# Subclasses should implement this with their specific animations |
|
pass
|
|
|