extends Node func _ready() -> void: EventBus.ability_use_requested.connect(_on_ability_use_requested) func _process(_delta: float) -> void: var players := get_tree().get_nodes_in_group("player") for player in players: if not Stats.is_alive(player): continue _try_auto_attack(player) func _try_auto_attack(player: Node) -> void: var targeting: Node = player.get_node_or_null("Targeting") if not targeting or not targeting.in_combat or not targeting.current_target: return if not is_instance_valid(targeting.current_target): return var cooldown_system: Node = get_node("../CooldownSystem") if not cooldown_system.is_aa_ready(player): return var role: Node = player.get_node("Role") var ability_set: AbilitySet = role.get_ability_set() if not ability_set: return var aa_damage: float = ability_set.aa_damage var aa_range: float = ability_set.aa_range var aa_is_heal: bool = ability_set.aa_is_heal var dmg: float = _apply_passive(player, aa_damage, "heal" if aa_is_heal else "damage") if aa_is_heal: EventBus.heal_requested.emit(player, player, dmg) else: var dist: float = player.global_position.distance_to(targeting.current_target.global_position) if dist > aa_range: return EventBus.damage_requested.emit(player, targeting.current_target, dmg) var base: BaseStats = Stats.get_base(player) var aa_cd: float = base.aa_cooldown if base is PlayerStats else 0.5 cooldown_system.set_aa_cooldown(player, aa_cd) func _on_ability_use_requested(player: Node, ability_index: int) -> void: var role: Node = player.get_node_or_null("Role") if not role: return var ability_set: AbilitySet = role.get_ability_set() if not ability_set or ability_index >= ability_set.abilities.size(): return var ability: Ability = ability_set.abilities[ability_index] if not ability or ability.type == Ability.Type.PASSIVE: return var cooldown_system: Node = get_node("../CooldownSystem") if not cooldown_system.is_ready(player, ability_index): return if ability.uses_gcd and not cooldown_system.is_gcd_ready(player): return var success: bool = _execute_ability(player, ability) if not success: return var base: BaseStats = Stats.get_base(player) var gcd_time: float = base.gcd_time if base is PlayerStats else 0.5 var gcd: float = gcd_time if ability.uses_gcd else 0.0 cooldown_system.set_cooldown(player, ability_index, ability.cooldown, gcd) func _execute_ability(player: Node, ability: Ability) -> bool: var targeting: Node = player.get_node("Targeting") var stat: String = "heal" if ability.is_heal else "damage" var dmg: float = _apply_passive(player, ability.damage, stat) match ability.type: Ability.Type.SINGLE: return _execute_single(player, targeting, ability, dmg) Ability.Type.AOE: return _execute_aoe(player, ability, dmg) Ability.Type.UTILITY: return _execute_utility(player, ability) Ability.Type.ULT: return _execute_ult(player, targeting, ability, dmg) return false func _apply_passive(player: Node, base: float, stat: String) -> float: var mult: Variant = Stats.get_stat(player, "buff_" + stat) if mult != null: return base * mult return base func _in_range(player: Node, targeting: Node, ability: Ability) -> bool: if ability.ability_range <= 0 or ability.is_heal: return true if not is_instance_valid(targeting.current_target): return false var dist: float = player.global_position.distance_to(targeting.current_target.global_position) return dist <= ability.ability_range func _execute_single(player: Node, targeting: Node, ability: Ability, dmg: float) -> bool: if ability.is_heal: EventBus.heal_requested.emit(player, player, dmg) EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg) return true if not _in_range(player, targeting, ability): return false if not is_instance_valid(targeting.current_target): return false EventBus.damage_requested.emit(player, targeting.current_target, dmg) EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg) return true func _execute_aoe(player: Node, ability: Ability, dmg: float) -> bool: if ability.is_heal: EventBus.heal_requested.emit(player, player, dmg) var players := get_tree().get_nodes_in_group("player") for p in players: if p != player and is_instance_valid(p): var dist: float = player.global_position.distance_to(p.global_position) if dist <= ability.ability_range: EventBus.heal_requested.emit(player, p, dmg) EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg) return true var hit := false var enemies := get_tree().get_nodes_in_group("enemies") for enemy in enemies: var dist: float = player.global_position.distance_to(enemy.global_position) if dist <= ability.ability_range: EventBus.damage_requested.emit(player, enemy, dmg) hit = true if hit: EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg) return hit func _execute_utility(player: Node, ability: Ability) -> bool: var max_shield: float = Stats.get_stat(player, "max_shield") if max_shield <= 0: return false var shield: float = Stats.get_stat(player, "shield") if ability.damage > 0: shield = max_shield * (ability.damage / 100.0) else: if shield >= max_shield: return false shield = max_shield Stats.set_stat(player, "shield", shield) EventBus.shield_changed.emit(player, shield, max_shield) return true func _execute_ult(player: Node, targeting: Node, ability: Ability, dmg: float) -> bool: if ability.is_heal: EventBus.heal_requested.emit(player, player, dmg) var players := get_tree().get_nodes_in_group("player") var aoe_range: float = ability.aoe_radius if ability.aoe_radius > 0 else ability.ability_range for p in players: if p != player and is_instance_valid(p): var dist: float = player.global_position.distance_to(p.global_position) if dist <= aoe_range: EventBus.heal_requested.emit(player, p, dmg * 0.4) EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg) return true if not _in_range(player, targeting, ability): return false if not is_instance_valid(targeting.current_target): return false var target: Node3D = targeting.current_target EventBus.damage_requested.emit(player, target, dmg * 5.0) var aoe_range: float = ability.aoe_radius if ability.aoe_radius > 0 else ability.ability_range var enemies := get_tree().get_nodes_in_group("enemies") for enemy in enemies: if enemy != target and is_instance_valid(enemy): var enemy_dist: float = target.global_position.distance_to(enemy.global_position) if enemy_dist <= aoe_range: EventBus.damage_requested.emit(player, enemy, dmg * 2.0) EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg * 5.0) return true