extends CharacterBody2D @export var attack_duration: float = 0.4 @export var attack_cooldown_duration: float = 0.6 @onready var attack_area: Area2D = $AttackArea @onready var attack_timer: Timer = $AttackTimer @onready var attack_cooldown_timer: Timer = $AttackCooldownTimer @onready var sprite: AnimatedSprite2D = $AnimatedSprite2D # TODO: these stats are shared across most entities; extract to resource? @export var speed: int = 200 var damage: int = 10 var can_attack: bool = true var desired_rotation: float = 0 # Resources / money var resources: int = 0 # Health var maxHealth: int = 100 var healthPoints: int = 100 var isAlive: bool = true @onready var invulnerable_cooldown_timer: Timer = $InvulnerableCooldownTimer var hasiframes: bool = false # only used for iframe after collision for now var target = Vector2.ZERO # either vector2 or Node2D func _ready() -> void: var screen_size = get_viewport_rect().size GameManager.init_screen_size(screen_size.x, screen_size.y) attack_area.monitoring = false # no collision until attacking TODO: Think about being attacked attack_timer.wait_time = attack_duration attack_cooldown_timer.wait_time = attack_cooldown_duration sprite.play("default") func _input(event): # TODO: only does clicks/taps; accept mouse drags if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed: target = get_global_mouse_position() func _physics_process(delta): var pos if typeof(target) == TYPE_VECTOR2: pos = target elif "position" in target: pos = target.position else: return var dir: Vector2 = position.direction_to(pos) if position.distance_to(pos) > 2: self.desired_rotation = atan2(dir.y, dir.x) move_and_collide(speed * dir * delta) position = GameManager.get_boundaried_position(position) func _process(delta): if isAlive: if Input.is_action_pressed("try_attack"): try_attack() # smoothly rotate if self.rotation != self.desired_rotation: self.rotation = lerp_angle(self.rotation, self.desired_rotation, clampf(4 * delta, 0, 1)) func try_attack() -> void: if not can_attack: return attack() func attack() -> void: can_attack = false attack_area.monitoring = true attack_timer.start() sprite.play("attacking") func _on_attack_timeout() -> void: attack_area.monitoring = false sprite.play("default") attack_cooldown_timer.start() func _on_attack_cooldown_timeout() -> void: can_attack = true func _on_attack_hit(body: Node2D) -> void: var hit_hittable = false if body.is_in_group("prey") or body.is_in_group("predator"): if body.has_method("handle_damage"): body.handle_damage(damage, self) hit_hittable = true if hit_hittable: await get_tree().create_timer(0.2).timeout sprite.play("default") func _on_invulnerable_cooldown_timeout() -> void: hasiframes = false func handle_damage(dmg: int, src: Node) -> void: # TODO: enhanced damage logic (src entity effects?) if not hasiframes: print("Player took %d damage." % dmg) healthPoints -= dmg hasiframes = true invulnerable_cooldown_timer.start() if isAlive and healthPoints <= 0: die() func collect_resource(damnt: int) -> void: resources += damnt print("Resource collected! Total: ", resources) func die() -> void: print("Player died.") isAlive = false GameManager.switch_scene("respawn") # TODO: Is this used or should it be removed? (currently unused) func respawm() -> void: healthPoints = maxHealth isAlive = true hasiframes = true attack_cooldown_timer.start()