extends Node func _ready() -> void: EventBus.ability_use.connect(_on_ability_use) func _on_ability_use(_player: Node, ability_index: int) -> void: if not PlayerData.alive: return var ability_set: AbilitySet = PlayerData.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 if PlayerData.cooldowns[ability_index] > 0: return if ability.uses_gcd and PlayerData.gcd > 0: return var success: bool = _execute_ability(ability) if not success: return var gcd: float = PlayerData.gcd_time if ability.uses_gcd else 0.0 PlayerData.cooldowns[ability_index] = ability.cooldown PlayerData.max_cooldowns[ability_index] = max(ability.cooldown, gcd) if gcd > 0: PlayerData.gcd = gcd func _execute_ability(ability: Ability) -> bool: var stat: String = "heal" if ability.is_heal else "damage" var dmg: float = _apply_passive(ability.damage, stat) var player: Node = get_tree().get_first_node_in_group("player") match ability.type: Ability.Type.SINGLE: return _execute_single(player, ability, dmg) Ability.Type.AOE: return _execute_aoe(player, ability, dmg) Ability.Type.UTILITY: return _execute_utility(ability) Ability.Type.ULT: return _execute_ult(player, ability, dmg) return false func _apply_passive(base: float, stat: String) -> float: var mult: float = 1.0 match stat: "damage": mult = PlayerData.buff_damage "heal": mult = PlayerData.buff_heal return base * mult func _in_range(ability: Ability) -> bool: if ability.ability_range <= 0 or ability.is_heal: return true if not is_instance_valid(PlayerData.target): return false var player: Node = get_tree().get_first_node_in_group("player") var dist: float = player.global_position.distance_to(PlayerData.target.global_position) return dist <= ability.ability_range func _execute_single(player: 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(ability): return false if not is_instance_valid(PlayerData.target): return false EventBus.damage_requested.emit(player, PlayerData.target, dmg) if ability.element != 0: EventBus.element_damage_dealt.emit(player, PlayerData.target, dmg, ability.element) 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) EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg) return true var hit := false for enemy in get_tree().get_nodes_in_group("enemies"): var dist: float = player.global_position.distance_to(enemy.global_position) if dist <= ability.ability_range: EventBus.damage_requested.emit(player, enemy, dmg) if ability.element != 0: EventBus.element_damage_dealt.emit(player, enemy, dmg, ability.element) hit = true if hit: EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg) return hit func _execute_utility(ability: Ability) -> bool: if PlayerData.max_shield <= 0: return false var shield: float = PlayerData.shield if ability.damage > 0: shield = PlayerData.max_shield * (ability.damage / 100.0) else: if shield >= PlayerData.max_shield: return false shield = PlayerData.max_shield PlayerData.set_shield(shield) return true func _execute_ult(player: Node, ability: Ability, dmg: float) -> bool: if ability.is_heal: EventBus.heal_requested.emit(player, player, dmg) var aoe_range: float = ability.aoe_radius if ability.aoe_radius > 0 else ability.ability_range EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg) return true if not _in_range(ability): return false if not is_instance_valid(PlayerData.target): return false var target: Node3D = PlayerData.target EventBus.damage_requested.emit(player, target, dmg * 5.0) if ability.element != 0: EventBus.element_damage_dealt.emit(player, target, dmg * 5.0, ability.element) var splash_range: float = ability.aoe_radius if ability.aoe_radius > 0 else ability.ability_range for enemy in get_tree().get_nodes_in_group("enemies"): if enemy != target and is_instance_valid(enemy): var enemy_dist: float = target.global_position.distance_to(enemy.global_position) if enemy_dist <= splash_range: EventBus.damage_requested.emit(player, enemy, dmg * 2.0) if ability.element != 0: EventBus.element_damage_dealt.emit(player, enemy, dmg * 2.0, ability.element) EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg * 5.0) return true