Files
slopvivors/scenes/enemies/enemy_base.gd

194 lines
5.1 KiB
GDScript

class_name EnemyBase
extends CharacterBody2D
@export var move_speed: float = 100
@export var max_health: float = 10.0
@export var default_contact_damage: float = 0.0
@export var target_distance: float = 6.0
@export var path_update_interval: float = 1.5
@export var xp_dropped: float = 5.0
@export var enemy_rarity: GlobalConst.Rarity = GlobalConst.Rarity.NORMAL
var modifiers: Array[EnemyMod] = []
@onready var target_cast: RayCast2D = $TargetCast
@onready var animation_player: AnimationPlayer = $AnimationPlayer
@onready var contact_damage_cd: Timer = $ContactDamageCD
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D
@onready var collision_shape_2d: CollisionShape2D = $CollisionShape2D
@onready var shape_cast_2d: ShapeCast2D = $ShapeCast2D
@onready var sprite_2d: Sprite2D = $Sprite2D
@onready var label: Label = $Label
var player: Player
var enemy_name: String
var target: Node2D
var god_mode: bool = false
var is_dead: bool = false
var health: float
var _path_update_timer: float = 0.0
func _ready() -> void:
enemy_name = _gen_name()
match enemy_rarity:
GlobalConst.Rarity.NORMAL:
label.visible = false
GlobalConst.Rarity.RARE:
var mods = EnemyModPool.get_random_mods(2)
label.visible = true
modifiers = mods
GlobalConst.Rarity.EPIC:
var mods = EnemyModPool.get_random_mods(4)
label.visible = true
modifiers = mods
if modifiers.size() > 0:
enemy_name += " the %s" % modifiers.pick_random().adjective
label.add_theme_color_override("font_color", GlobalConst.rarity_to_color(enemy_rarity))
label.text = enemy_name
health = get_calculated("max_health")
shape_cast_2d.shape.radius = collision_shape_2d.shape.radius
shape_cast_2d.enabled = false
sprite_2d.material = sprite_2d.material.duplicate()
_find_player()
func _find_player():
if not player:
player = get_tree().get_first_node_in_group(GlobalConst.GROUP_PLAYER)
target = player
func _gen_name() -> String:
return "Unnamed enemy"
func _physics_process(delta: float) -> void:
if not target:
return
do_movement(delta)
check_contact_damage()
func do_movement(delta: float) -> void:
if not target:
return
if global_position.distance_to(target.global_position) < target_distance:
return
_path_update_timer -= delta + randf()
if _has_direct_path():
shape_cast_2d.enabled = false
_do_simple_movement()
else:
_do_nav_agent_movement()
if velocity.x < 0:
sprite_2d.flip_h = true
else:
sprite_2d.flip_h = false
func _has_direct_path():
target_cast.target_position = to_local(target.global_position)
target_cast.enabled = true
return not target_cast.is_colliding()
func _do_simple_movement():
var direction = global_position.direction_to(target.global_position)
var distance = global_position.distance_to(target.global_position)
if distance > 4:
velocity = direction * get_calculated("move_speed")
move_and_slide()
func _do_nav_agent_movement():
if _path_update_timer <= 0.0:
_path_update_timer = path_update_interval
nav_agent.target_position = target.global_position
if nav_agent.is_navigation_finished():
return
var next_point = nav_agent.get_next_path_position()
var direction = (next_point - global_position).normalized()
shape_cast_2d.target_position = to_local(next_point)
shape_cast_2d.enabled = true
if shape_cast_2d.is_colliding():
direction = direction.bounce(shape_cast_2d.get_collision_normal(0)).normalized()
velocity = direction * get_calculated("move_speed")
move_and_slide()
func check_contact_damage():
if default_contact_damage == 0.0:
return
if global_position.distance_to(target.global_position) > target_distance + 2:
return
deal_contact_damage()
func deal_contact_damage():
if target.is_in_group("damagable"):
if contact_damage_cd.is_stopped():
target.take_damage(default_contact_damage)
contact_damage_cd.start()
func take_damage(value: float, is_crit: bool = false):
if god_mode:
return
health -= value
var dm = preload("res://scenes/damage_numbers.tscn").instantiate()
dm.damage_taken = value
dm.critical_damage = is_crit
animation_player.play("generic_anims/take_damage")
add_child(dm)
if health <= 0:
die()
func cheer():
target_distance = 2
if not animation_player.is_playing():
animation_player.play("generic_anims/cheer")
func die():
if is_dead:
return
is_dead = true
drop_xp_orb()
target = null
velocity = Vector2.ZERO
animation_player.play("generic_anims/die")
func drop_xp_orb() -> void:
var orb: XPOrb = preload("res://scenes/xp_orb.tscn").instantiate()
orb.value = xp_dropped * (1 + (modifiers.size() * 3))
orb.position = position
get_parent().call_deferred("add_child", orb)
func _on_animation_player_animation_finished(anim_name: StringName) -> void:
if is_dead:
queue_free()
func get_calculated(key: String) -> Variant:
# set max move speed to players move speed
if key == "move_speed":
return clampf(
EnemyMod.get_calculated(self, key),
0,
player.player_stats.get_final("move_speed", player.modifiers)
)
return EnemyMod.get_calculated(self, key)
func has_property(key: String) -> bool:
for prop in get_property_list():
if prop.name == key:
return true
return false