delete files in nickes
This commit is contained in:
@@ -11,6 +11,7 @@ var current_health: float = 50.0
|
||||
var is_dead: bool = false
|
||||
var target_player: Node = null
|
||||
var attack_timer: float = 0.0
|
||||
var killer_player: Node = null # Track who killed this enemy (for kill credit)
|
||||
|
||||
# Knockback
|
||||
var is_knocked_back: bool = false
|
||||
@@ -48,7 +49,7 @@ func _ready():
|
||||
# CRITICAL: Set collision mask to include interactable objects (layer 2) and walls (layer 7)
|
||||
# This allows enemies to collide with interactable objects so they can path around them
|
||||
# Walls are on layer 7 (bit 6 = 64), not layer 4!
|
||||
collision_mask = 1 | 2 | 64 # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64)
|
||||
collision_mask = 1 | 2 | 64 # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64)
|
||||
|
||||
func _physics_process(delta):
|
||||
if is_dead:
|
||||
@@ -162,18 +163,18 @@ func _check_interactable_object_collision():
|
||||
|
||||
# Try to path around the object by moving perpendicular to collision normal
|
||||
# This creates a side-stepping behavior to go around obstacles
|
||||
var perpendicular = Vector2(-collision_normal.y, collision_normal.x) # Rotate 90 degrees
|
||||
var perpendicular = Vector2(-collision_normal.y, collision_normal.x) # Rotate 90 degrees
|
||||
|
||||
# Choose perpendicular direction that moves toward target (if we have one)
|
||||
if target_player and is_instance_valid(target_player):
|
||||
var to_target = (target_player.global_position - global_position).normalized()
|
||||
# If perpendicular dot product with target direction is negative, flip it
|
||||
if perpendicular.dot(to_target) < 0:
|
||||
perpendicular = -perpendicular
|
||||
perpendicular = - perpendicular
|
||||
|
||||
# Apply perpendicular movement (side-step around object)
|
||||
var side_step_velocity = perpendicular * move_speed * 0.7 # 70% of move speed for side-step
|
||||
velocity = velocity.lerp(side_step_velocity, 0.3) # Smoothly blend with existing velocity
|
||||
var side_step_velocity = perpendicular * move_speed * 0.7 # 70% of move speed for side-step
|
||||
velocity = velocity.lerp(side_step_velocity, 0.3) # Smoothly blend with existing velocity
|
||||
|
||||
# Also add some push-away from object to create clearance
|
||||
var push_away = collision_normal * move_speed * 0.3
|
||||
@@ -184,7 +185,7 @@ func _check_interactable_object_collision():
|
||||
velocity = velocity.normalized() * move_speed
|
||||
|
||||
# For humanoid enemies, sometimes try to destroy the object
|
||||
if has_method("_try_attack_object") and randf() < 0.1: # 10% chance per frame when blocked
|
||||
if has_method("_try_attack_object") and randf() < 0.1: # 10% chance per frame when blocked
|
||||
call("_try_attack_object", blocked_objects[0].object)
|
||||
|
||||
func _attack_player(player):
|
||||
@@ -239,6 +240,25 @@ func _find_nearest_player_in_range(max_range: float) -> Node:
|
||||
|
||||
return nearest
|
||||
|
||||
func _find_nearest_player_to_position(pos: Vector2, max_range: float = 100.0) -> Node:
|
||||
# Find the nearest player to a specific position (used to find attacker)
|
||||
var players = get_tree().get_nodes_in_group("player")
|
||||
if players.is_empty():
|
||||
return null
|
||||
|
||||
var nearest: Node = null
|
||||
var nearest_dist = max_range
|
||||
|
||||
for player in players:
|
||||
if not is_instance_valid(player):
|
||||
continue
|
||||
var dist = pos.distance_to(player.global_position)
|
||||
if dist <= max_range and dist < nearest_dist:
|
||||
nearest_dist = dist
|
||||
nearest = player
|
||||
|
||||
return nearest
|
||||
|
||||
func take_damage(amount: float, from_position: Vector2):
|
||||
# Only process damage on server/authority
|
||||
if not is_multiplayer_authority():
|
||||
@@ -247,6 +267,12 @@ func take_damage(amount: float, from_position: Vector2):
|
||||
if is_dead:
|
||||
return
|
||||
|
||||
# Find the nearest player to the attack position (likely the attacker)
|
||||
# This allows us to credit kills correctly
|
||||
var nearest_player = _find_nearest_player_to_position(from_position)
|
||||
if nearest_player:
|
||||
killer_player = nearest_player # Update killer to the most recent attacker
|
||||
|
||||
current_health -= amount
|
||||
print(name, " took ", amount, " damage! Health: ", current_health)
|
||||
|
||||
@@ -279,8 +305,20 @@ func take_damage(amount: float, from_position: Vector2):
|
||||
_sync_damage_visual.rpc()
|
||||
|
||||
if current_health <= 0:
|
||||
# Prevent multiple death triggers
|
||||
if is_dead:
|
||||
return # Already dying
|
||||
|
||||
# Don't set is_dead here - let _die() set it to avoid early return bug
|
||||
# Mark as dead in _die() function instead of here
|
||||
|
||||
# Delay death slightly so knockback is visible
|
||||
call_deferred("_die")
|
||||
|
||||
# Notify doors that an enemy has died (if spawned from spawner)
|
||||
# This needs to happen after _die() sets is_dead, so defer it
|
||||
if has_meta("spawned_from_spawner") and get_meta("spawned_from_spawner"):
|
||||
call_deferred("_notify_doors_enemy_died")
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func rpc_take_damage(amount: float, from_position: Vector2):
|
||||
@@ -318,7 +356,7 @@ func _show_damage_number(amount: float, from_position: Vector2):
|
||||
var entities_node = game_world.get_node_or_null("Entities")
|
||||
if entities_node:
|
||||
entities_node.add_child(damage_label)
|
||||
damage_label.global_position = global_position + Vector2(0, -16) # Above enemy head
|
||||
damage_label.global_position = global_position + Vector2(0, -16) # Above enemy head
|
||||
else:
|
||||
get_tree().current_scene.add_child(damage_label)
|
||||
damage_label.global_position = global_position + Vector2(0, -16)
|
||||
@@ -347,6 +385,27 @@ func _on_take_damage():
|
||||
# Override in subclasses for custom damage reactions
|
||||
pass
|
||||
|
||||
func _notify_doors_enemy_died():
|
||||
# Notify all doors that require enemies to check puzzle state
|
||||
# This ensures doors open immediately when the last enemy dies
|
||||
if not has_meta("spawned_from_spawner") or not get_meta("spawned_from_spawner"):
|
||||
return # Only notify if this enemy was spawned from a spawner
|
||||
|
||||
# Find all doors in the scene that require enemies
|
||||
for door in get_tree().get_nodes_in_group("blocking_door"):
|
||||
if not is_instance_valid(door):
|
||||
continue
|
||||
|
||||
if not door.has_method("_check_puzzle_state"):
|
||||
continue
|
||||
|
||||
# Check if this door requires enemies (requires_enemies is a property defined in door.gd)
|
||||
# Access property directly - it's always defined in door.gd class
|
||||
if door.requires_enemies:
|
||||
# Trigger puzzle state check immediately (doors will verify if all enemies are dead)
|
||||
door.call_deferred("_check_puzzle_state")
|
||||
print("Enemy: Notified door ", door.name, " to check puzzle state after enemy death")
|
||||
|
||||
func _set_animation(_anim_name: String):
|
||||
# Virtual function - override in subclasses that use animation state system
|
||||
# (e.g., enemy_humanoid.gd uses player-like animation system)
|
||||
@@ -359,6 +418,11 @@ func _die():
|
||||
is_dead = true
|
||||
print(name, " died!")
|
||||
|
||||
# Credit kill to the player who dealt the fatal damage
|
||||
if killer_player and is_instance_valid(killer_player) and killer_player.character_stats:
|
||||
killer_player.character_stats.kills += 1
|
||||
print(name, " kill credited to ", killer_player.name, " (total kills: ", killer_player.character_stats.kills, ")")
|
||||
|
||||
# Spawn loot immediately (before death animation)
|
||||
_spawn_loot()
|
||||
|
||||
@@ -465,7 +529,7 @@ func _sync_position(pos: Vector2, vel: Vector2, z_pos: float = 0.0, dir: int = 0
|
||||
# Clients receive position and animation updates from server
|
||||
# Only process if we're not the authority (i.e., we're a client)
|
||||
if is_multiplayer_authority():
|
||||
return # Server ignores its own updates
|
||||
return # Server ignores its own updates
|
||||
|
||||
# Debug: Log when client receives position update (first few times)
|
||||
if not has_meta("position_sync_count"):
|
||||
@@ -474,7 +538,7 @@ func _sync_position(pos: Vector2, vel: Vector2, z_pos: float = 0.0, dir: int = 0
|
||||
|
||||
var sync_count = get_meta("position_sync_count") + 1
|
||||
set_meta("position_sync_count", sync_count)
|
||||
if sync_count <= 3: # Log first 3 syncs
|
||||
if sync_count <= 3: # Log first 3 syncs
|
||||
print("Enemy ", name, " (client) received position sync #", sync_count, ": pos=", pos)
|
||||
|
||||
# Update position and state
|
||||
@@ -504,7 +568,7 @@ func _sync_damage_visual():
|
||||
# Clients receive damage visual sync
|
||||
# Only process if we're not the authority (i.e., we're a client)
|
||||
if is_multiplayer_authority():
|
||||
return # Server ignores its own updates
|
||||
return # Server ignores its own updates
|
||||
|
||||
_flash_damage()
|
||||
|
||||
@@ -513,7 +577,7 @@ func _sync_death():
|
||||
# Clients receive death sync and play death animation locally
|
||||
# Only process if we're not the authority (i.e., we're a client)
|
||||
if is_multiplayer_authority():
|
||||
return # Server ignores its own updates
|
||||
return # Server ignores its own updates
|
||||
|
||||
if not is_dead:
|
||||
is_dead = true
|
||||
@@ -521,7 +585,7 @@ func _sync_death():
|
||||
|
||||
# Remove collision layer so they don't collide with players, but still collide with walls
|
||||
# This matches what happens on the server when rats/slimes die
|
||||
set_collision_layer_value(2, false) # Remove from enemy collision layer (layer 2)
|
||||
set_collision_layer_value(2, false) # Remove from enemy collision layer (layer 2)
|
||||
|
||||
# Immediately mark as dead and stop AI/physics
|
||||
# This prevents "inactive" enemies that are already dead
|
||||
|
||||
Reference in New Issue
Block a user