extends Node @onready var role_system: Node = get_node("../RoleSystem") @onready var cooldown_system: Node = get_node("../CooldownSystem") func _ready() -> void: EventBus.ability_use_requested.connect(_on_ability_request) func _on_ability_request(player: Node, index: int) -> void: if not is_instance_valid(player) or not Stats.has(player): return var target_str: String = "" var tv: Variant = (player as Node).get("current_target") if tv != null and is_instance_valid(tv) and tv is Node: target_str = String((tv as Node).get_path()) if not multiplayer.is_server() and multiplayer.multiplayer_peer != null: _request_use.rpc_id(1, String(player.get_path()), index, target_str) return _execute(player, index, target_str) @rpc("any_peer", "reliable") func _request_use(path_str: String, index: int, target_str: String) -> void: var p: Node = get_node_or_null(NodePath(path_str)) if p == null: return if target_str != "": var t := get_node_or_null(NodePath(target_str)) if t and Stats.has(t): p.set("current_target", t) _execute(p, index, target_str) func _execute(player: Node, index: int, _target_str: String = "") -> void: if index < 0 or index > 3: return var role: int = int(Stats.get_stat(player, "role", GameState.ROLE_DAMAGE)) var set: AbilitySet = role_system.get_set(role) if set == null or index >= set.abilities.size(): return var ability: Ability = set.abilities[index] if ability == null: return if not cooldown_system.is_ready(player, index): return if ability.uses_gcd and not cooldown_system.is_gcd_ready(player): return cooldown_system.set_cooldown(player, index, ability.cooldown, float(Stats.get_stat(player, "gcd_time", 0.5)) if ability.uses_gcd else 0.0) EventBus.ability_used.emit(player, index, ability) _apply_ability(player, ability) func _apply_ability(player: Node, ability: Ability) -> void: match ability.type: Ability.Type.SINGLE: _apply_single(player, ability) Ability.Type.AOE: _apply_aoe(player, ability) Ability.Type.UTILITY: _apply_utility(player, ability) Ability.Type.ULT: _apply_ult(player, ability) _: pass func _apply_single(player: Node, ability: Ability) -> void: var dmg_mult: float = float(Stats.get_stat(player, "buff_damage", 1.0)) var heal_mult: float = float(Stats.get_stat(player, "buff_heal", 1.0)) var target: Node = _resolve_target(player, ability) if target == null: return if (player as Node3D).global_position.distance_to((target as Node3D).global_position) > ability.ability_range: return if ability.is_heal: EventBus.heal_requested.emit(player, target, ability.damage * heal_mult) else: EventBus.damage_requested.emit(player, target, ability.damage * dmg_mult, ability.element) if ability.shield_value > 0.0: _apply_shield(player, ability.shield_value) func _apply_aoe(player: Node, ability: Ability) -> void: var dmg_mult: float = float(Stats.get_stat(player, "buff_damage", 1.0)) var heal_mult: float = float(Stats.get_stat(player, "buff_heal", 1.0)) var origin: Vector3 = (player as Node3D).global_position var center: Vector3 = origin var target: Node = _resolve_target(player, ability) if target and (target as Node3D).global_position.distance_to(origin) <= ability.ability_range: center = (target as Node3D).global_position if ability.is_heal: for ally in get_tree().get_nodes_in_group("player"): if (ally as Node3D).global_position.distance_to(center) <= ability.aoe_radius: EventBus.heal_requested.emit(player, ally, ability.damage * heal_mult) else: var hits: int = 0 for foe in Stats.entities_in_group(&"enemies"): if (foe as Node3D).global_position.distance_to(center) <= ability.aoe_radius: EventBus.damage_requested.emit(player, foe, ability.damage * dmg_mult, ability.element) hits += 1 for foe in Stats.entities_in_group(&"portals"): if (foe as Node3D).global_position.distance_to(center) <= ability.aoe_radius: EventBus.damage_requested.emit(player, foe, ability.damage * dmg_mult, ability.element) hits += 1 for foe in Stats.entities_in_group(&"gates"): if (foe as Node3D).global_position.distance_to(center) <= ability.aoe_radius: EventBus.damage_requested.emit(player, foe, ability.damage * dmg_mult, ability.element) hits += 1 if ability.shield_value > 0.0: _apply_shield(player, ability.shield_value * hits) func _apply_utility(player: Node, ability: Ability) -> void: if ability.shield_multiplier > 0.0: var max_shield: float = float(Stats.get_stat(player, "max_shield", 0.0)) var add: float = max_shield * ability.shield_multiplier Stats.set_stat(player, "shield", min(max_shield, float(Stats.get_stat(player, "shield", 0.0)) + add)) EventBus.shield_changed.emit(player, Stats.get_stat(player, "shield"), max_shield) func _apply_ult(player: Node, ability: Ability) -> void: if ability.is_heal: _apply_aoe(player, ability) elif ability.shield_multiplier > 0.0: var max_shield: float = float(Stats.get_stat(player, "max_shield", 0.0)) Stats.set_stat(player, "shield", min(max_shield * (1.0 + ability.shield_multiplier), float(Stats.get_stat(player, "shield", 0.0)) + max_shield * ability.shield_multiplier)) EventBus.shield_changed.emit(player, Stats.get_stat(player, "shield"), max_shield) else: var target: Node = _resolve_target(player, ability) if target == null: return var dmg_mult: float = float(Stats.get_stat(player, "buff_damage", 1.0)) EventBus.damage_requested.emit(player, target, ability.damage * dmg_mult, ability.element) if ability.aoe_radius > 0.0: var center: Vector3 = (target as Node3D).global_position for foe in Stats.entities_in_group(&"enemies"): if foe == target: continue if (foe as Node3D).global_position.distance_to(center) <= ability.aoe_radius: EventBus.damage_requested.emit(player, foe, ability.damage * 0.5 * dmg_mult, ability.element) func _apply_shield(player: Node, amount: float) -> void: var max_shield: float = float(Stats.get_stat(player, "max_shield", 0.0)) Stats.set_stat(player, "shield", min(max_shield, float(Stats.get_stat(player, "shield", 0.0)) + amount)) EventBus.shield_changed.emit(player, Stats.get_stat(player, "shield"), max_shield) func _resolve_target(player: Node, ability: Ability) -> Node: if ability.is_heal: var lowest: Node = null var lowest_pct: float = 2.0 for ally in get_tree().get_nodes_in_group("player"): if not Stats.has(ally): continue var hp: float = float(Stats.get_stat(ally, "health", 0.0)) var max_hp: float = float(Stats.get_stat(ally, "max_health", 1.0)) if max_hp <= 0.0 or hp <= 0.0: continue var pct: float = hp / max_hp if pct < lowest_pct: lowest = ally lowest_pct = pct return lowest var tv: Variant = (player as Node).get("current_target") if tv != null and is_instance_valid(tv) and tv is Node and Stats.has(tv): return tv as Node var nearest: Node = null var nearest_dist: float = INF for foe in Stats.entities_in_group(&"enemies") + Stats.entities_in_group(&"portals") + Stats.entities_in_group(&"gates"): var d: float = (foe as Node3D).global_position.distance_to((player as Node3D).global_position) if d < nearest_dist and d <= ability.ability_range: nearest = foe nearest_dist = d return nearest