153 lines
4.4 KiB
GDScript
153 lines
4.4 KiB
GDScript
extends "res://scripts/enemy_base.gd"
|
|
|
|
# Rat Enemy - Moves in 4 directions, chases players
|
|
|
|
enum RatState { IDLE, WANDERING }
|
|
var state: RatState = RatState.IDLE
|
|
var state_timer: float = 0.0
|
|
|
|
var idle_duration: float = 3.0 # Longer idle time
|
|
var wander_duration: float = 2.0 # Short wander time
|
|
var detection_range: float = 150.0
|
|
|
|
func _ready():
|
|
super._ready()
|
|
|
|
max_health = 15.0 # Reduced from 25.0 for better balance
|
|
current_health = max_health
|
|
move_speed = 40.0 # Much slower
|
|
damage = 8.0
|
|
exp_reward = 5.0 # Rats give low EXP
|
|
|
|
state_timer = idle_duration
|
|
|
|
# CRITICAL: Ensure collision mask is set correctly (walls are on 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)
|
|
|
|
# Override to set weak stats for rats
|
|
func _initialize_character_stats():
|
|
super._initialize_character_stats()
|
|
# Rats are weak enemies - very low END for low DEF
|
|
character_stats.baseStats.end = 5 # END=5 gives DEF=1.0 (5 * 0.2)
|
|
character_stats.baseStats.str = 7 # Low STR = low damage
|
|
character_stats.baseStats.dex = 8 # Slightly higher DEX (rats are fast)
|
|
character_stats.baseStats.int = 5
|
|
character_stats.baseStats.wis = 5
|
|
character_stats.baseStats.lck = 5
|
|
# Re-initialize hp and mp with new stats
|
|
character_stats.hp = character_stats.maxhp
|
|
character_stats.mp = character_stats.maxmp
|
|
# Sync max_health from character_stats
|
|
max_health = character_stats.maxhp
|
|
current_health = max_health
|
|
|
|
func _ai_behavior(delta):
|
|
# Update state timer
|
|
state_timer -= delta
|
|
|
|
# Find nearest player
|
|
target_player = _find_nearest_player()
|
|
|
|
# State machine
|
|
match state:
|
|
RatState.IDLE:
|
|
_idle_behavior(delta)
|
|
RatState.WANDERING:
|
|
_wandering_behavior(delta)
|
|
|
|
# Update animation
|
|
_update_animation(delta)
|
|
|
|
func _idle_behavior(_delta):
|
|
velocity = Vector2.ZERO
|
|
|
|
# Show idle frame
|
|
anim_frame = 2
|
|
|
|
# Switch to wandering after idle duration (don't chase players)
|
|
if state_timer <= 0:
|
|
state = RatState.WANDERING
|
|
state_timer = wander_duration
|
|
|
|
func _wandering_behavior(_delta):
|
|
# Pick a random direction at the start of wandering
|
|
if state_timer >= wander_duration - 0.1: # Only change direction at start
|
|
var dirs = [Vector2.UP, Vector2.DOWN, Vector2.LEFT, Vector2.RIGHT]
|
|
var random_dir = dirs[randi() % dirs.size()]
|
|
velocity = random_dir * move_speed
|
|
current_direction = _get_direction_from_vector(random_dir)
|
|
|
|
# Keep moving in the same direction during wander
|
|
# (velocity is already set, just maintain it)
|
|
|
|
# Switch back to idle after wander duration
|
|
if state_timer <= 0:
|
|
state = RatState.IDLE
|
|
state_timer = idle_duration
|
|
velocity = Vector2.ZERO # Stop moving
|
|
|
|
func _snap_to_4_directions(dir: Vector2) -> Vector2:
|
|
# Snap to closest cardinal direction
|
|
if abs(dir.x) > abs(dir.y):
|
|
return Vector2(sign(dir.x), 0)
|
|
else:
|
|
return Vector2(0, sign(dir.y))
|
|
|
|
func _update_animation(delta):
|
|
if state == RatState.IDLE or velocity.length() < 1.0:
|
|
# Show idle frame
|
|
anim_frame = 2
|
|
else:
|
|
# Animate moving (frames 0, 1, 2)
|
|
anim_time += delta
|
|
if anim_time >= anim_speed:
|
|
anim_time = 0.0
|
|
anim_frame = (anim_frame + 1) % 3
|
|
|
|
# Map directions to 4 sprite directions (only use cardinal directions)
|
|
var sprite_dir = _get_sprite_direction_4()
|
|
|
|
# Set sprite frame
|
|
if sprite:
|
|
sprite.frame = anim_frame + (sprite_dir * 3)
|
|
|
|
func _get_sprite_direction_4() -> int:
|
|
# Only use 4 cardinal directions
|
|
match current_direction:
|
|
Direction.DOWN, Direction.DOWN_LEFT, Direction.DOWN_RIGHT:
|
|
return 0 # Down
|
|
Direction.LEFT, Direction.UP_LEFT:
|
|
return 1 # Left
|
|
Direction.RIGHT, Direction.UP_RIGHT:
|
|
return 2 # Right
|
|
Direction.UP:
|
|
return 3 # Up
|
|
return 0
|
|
|
|
func _update_client_visuals():
|
|
# Update visuals on clients based on synced state
|
|
super._update_client_visuals()
|
|
|
|
# Update sprite frame based on synced anim_frame and direction
|
|
if sprite:
|
|
var sprite_dir = _get_sprite_direction_4()
|
|
sprite.frame = anim_frame + (sprite_dir * 3) # 3 frames per direction
|
|
|
|
func _die():
|
|
if is_dead:
|
|
return
|
|
|
|
# Remove collision layer so they don't collide with players, but still collide with walls
|
|
set_collision_layer_value(2, false) # Remove from enemy collision layer (layer 2)
|
|
|
|
# Call parent _die() which handles death sync and _play_death_animation()
|
|
super._die()
|
|
|
|
func _play_death_animation():
|
|
# Simple fade out
|
|
var fade_tween = create_tween()
|
|
fade_tween.tween_property(sprite, "modulate:a", 0.0, 0.4)
|
|
|
|
await fade_tween.finished
|
|
queue_free()
|