Files
mmo/systems/ability_system.gd
Marek Lenczewski f21e30eb55 prototype vibe
2026-04-16 17:20:57 +02:00

130 lines
5.2 KiB
GDScript

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 * PlayerData.level_scale
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
var targets: Array = get_tree().get_nodes_in_group("enemies") + get_tree().get_nodes_in_group("portals")
for target in targets:
var dist: float = player.global_position.distance_to(target.global_position)
if dist <= ability.ability_range:
EventBus.damage_requested.emit(player, target, dmg)
if ability.element != 0:
EventBus.element_damage_dealt.emit(player, target, 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
var splash_targets: Array = get_tree().get_nodes_in_group("enemies") + get_tree().get_nodes_in_group("portals")
for other in splash_targets:
if other != target and is_instance_valid(other):
var other_dist: float = target.global_position.distance_to(other.global_position)
if other_dist <= splash_range:
EventBus.damage_requested.emit(player, other, dmg * 2.0)
if ability.element != 0:
EventBus.element_damage_dealt.emit(player, other, dmg * 2.0, ability.element)
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg * 5.0)
return true