extends Resource class_name Ability enum Type { SINGLE, AOE, UTILITY, ULT, PASSIVE } @export var ability_name: String = "" @export var type: Type = Type.SINGLE @export var damage: float = 0.0 @export var ability_range: float = 3.0 @export var cooldown: float = 0.0 @export var uses_gcd: bool = true @export var aoe_radius: float = 0.0 @export var icon: String = "" @export var is_heal: bool = false @export var passive_stat: String = "damage" func execute(player: Node, targeting: Node) -> bool: var stat: String = "heal" if is_heal else "damage" var dmg: float = _get_modified_damage(player, damage, stat) match type: Type.SINGLE: return _execute_single(player, targeting, dmg) Type.AOE: return _execute_aoe(player, dmg) Type.UTILITY: return _execute_utility(player) Type.ULT: return _execute_ult(player, targeting, dmg) return false func _get_modified_damage(player: Node, base: float, stat: String = "damage") -> float: var combat: Node = player.get_node("Combat") return combat.apply_passive(base, stat) func _in_range(player: Node, targeting: Node) -> bool: if ability_range <= 0: return true if 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_range func _execute_single(player: Node, targeting: Node, dmg: float) -> bool: if 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): 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, dmg: float) -> bool: if is_heal: EventBus.heal_requested.emit(player, player, dmg) var players := player.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_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 := player.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_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) -> bool: var shield: Node = player.get_node_or_null("Shield") if shield: if damage > 0: shield.current_shield = shield.max_shield * (damage / 100.0) else: if shield.current_shield >= shield.max_shield: return false shield.current_shield = shield.max_shield EventBus.shield_changed.emit(player, shield.current_shield, shield.max_shield) return true return false func _execute_ult(player: Node, targeting: Node, dmg: float) -> bool: if is_heal: EventBus.heal_requested.emit(player, player, dmg) var players := player.get_tree().get_nodes_in_group("player") var aoe_range: float = aoe_radius if aoe_radius > 0 else 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): 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 = aoe_radius if aoe_radius > 0 else ability_range var enemies := player.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