update
This commit is contained in:
169
systems/ability_system.gd
Normal file
169
systems/ability_system.gd
Normal file
@@ -0,0 +1,169 @@
|
||||
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
|
||||
1
systems/ability_system.gd.uid
Normal file
1
systems/ability_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://h0hts425epc6
|
||||
130
systems/aggro_system.gd
Normal file
130
systems/aggro_system.gd
Normal file
@@ -0,0 +1,130 @@
|
||||
extends Node
|
||||
|
||||
var aggro_tables: Dictionary = {}
|
||||
var seconds_outside: Dictionary = {}
|
||||
|
||||
func _ready() -> void:
|
||||
EventBus.damage_dealt.connect(_on_damage_dealt)
|
||||
EventBus.heal_requested.connect(_on_heal_requested)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
EventBus.enemy_detected.connect(_on_enemy_detected)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
for enemy in aggro_tables.keys():
|
||||
if not is_instance_valid(enemy):
|
||||
aggro_tables.erase(enemy)
|
||||
seconds_outside.erase(enemy)
|
||||
continue
|
||||
_decay_aggro(enemy, delta)
|
||||
_update_target(enemy)
|
||||
|
||||
func _decay_aggro(enemy: Node, delta: float) -> void:
|
||||
var table: Dictionary = aggro_tables[enemy]
|
||||
var base: BaseStats = Stats.get_base(enemy)
|
||||
var portal_radius: float = base.portal_radius if base is EnemyStats else 10.0
|
||||
var aggro_decay: float = base.aggro_decay if base is EnemyStats else 1.0
|
||||
|
||||
var outside_portal := false
|
||||
if "portal" in enemy and enemy.portal and is_instance_valid(enemy.portal):
|
||||
var dist: float = enemy.global_position.distance_to(enemy.portal.global_position)
|
||||
if dist > portal_radius:
|
||||
outside_portal = true
|
||||
seconds_outside[enemy] = seconds_outside.get(enemy, 0.0) + delta
|
||||
else:
|
||||
seconds_outside[enemy] = 0.0
|
||||
|
||||
for player in table.keys():
|
||||
var decay: float = aggro_decay * delta
|
||||
if outside_portal:
|
||||
var bonus: float = table[player] * 0.01 * pow(2, seconds_outside.get(enemy, 0.0)) * delta
|
||||
decay += bonus
|
||||
table[player] -= decay
|
||||
if not outside_portal and "portal" in enemy and enemy.portal and is_instance_valid(player):
|
||||
var player_dist: float = player.global_position.distance_to(enemy.portal.global_position)
|
||||
if player_dist <= portal_radius and table[player] < 1.0:
|
||||
table[player] = 1.0
|
||||
if table[player] <= 0:
|
||||
table.erase(player)
|
||||
|
||||
func _update_target(enemy: Node) -> void:
|
||||
if not "state" in enemy:
|
||||
return
|
||||
var table: Dictionary = aggro_tables[enemy]
|
||||
var top: Node = _get_top_target(table)
|
||||
if top and top != enemy.target:
|
||||
enemy.target = top
|
||||
if enemy.state == enemy.State.IDLE or enemy.state == enemy.State.RETURN:
|
||||
enemy.state = enemy.State.CHASE
|
||||
elif not top and enemy.state != enemy.State.IDLE and enemy.state != enemy.State.RETURN:
|
||||
enemy.target = null
|
||||
enemy.state = enemy.State.RETURN
|
||||
|
||||
func _add_aggro(enemy: Node, player: Node, amount: float) -> void:
|
||||
if enemy not in aggro_tables:
|
||||
aggro_tables[enemy] = {}
|
||||
if player in aggro_tables[enemy]:
|
||||
aggro_tables[enemy][player] += amount
|
||||
else:
|
||||
aggro_tables[enemy][player] = amount
|
||||
|
||||
func _get_top_target(table: Dictionary) -> Node:
|
||||
var top: Node = null
|
||||
var top_val := 0.0
|
||||
for player in table:
|
||||
if is_instance_valid(player) and table[player] > top_val:
|
||||
top_val = table[player]
|
||||
top = player
|
||||
return top
|
||||
|
||||
func _alert_nearby(enemy: Node, target: Node) -> void:
|
||||
var base: BaseStats = Stats.get_base(enemy)
|
||||
var alert_radius: float = base.alert_radius if base is EnemyStats else 3.0
|
||||
var enemies := enemy.get_tree().get_nodes_in_group("enemies")
|
||||
for other in enemies:
|
||||
if other != enemy and is_instance_valid(other) and "state" in other:
|
||||
if other.state == other.State.IDLE:
|
||||
var dist: float = enemy.global_position.distance_to(other.global_position)
|
||||
if dist <= alert_radius:
|
||||
_add_aggro(other, target, 1.0)
|
||||
other.target = target
|
||||
other.state = other.State.CHASE
|
||||
EventBus.enemy_engaged.emit(other, target)
|
||||
|
||||
func _on_enemy_detected(enemy: Node, player: Node) -> void:
|
||||
if not enemy.is_in_group("enemies"):
|
||||
return
|
||||
if "state" in enemy:
|
||||
if enemy.state == enemy.State.CHASE or enemy.state == enemy.State.ATTACK:
|
||||
return
|
||||
_add_aggro(enemy, player, 1.0)
|
||||
if "state" in enemy:
|
||||
enemy.target = player
|
||||
enemy.state = enemy.State.CHASE
|
||||
EventBus.enemy_engaged.emit(enemy, player)
|
||||
_alert_nearby(enemy, player)
|
||||
|
||||
func _on_damage_dealt(attacker: Node, target: Node, amount: float) -> void:
|
||||
if not target.is_in_group("enemies") and not target.is_in_group("portals"):
|
||||
return
|
||||
var multiplier := 1.0
|
||||
var role: Node = attacker.get_node_or_null("Role")
|
||||
if role and role.current_role == 0:
|
||||
multiplier = 2.0
|
||||
_add_aggro(target, attacker, amount * multiplier)
|
||||
|
||||
func _on_heal_requested(healer: Node, _target: Node, amount: float) -> void:
|
||||
if not healer.is_in_group("player"):
|
||||
return
|
||||
for enemy in aggro_tables:
|
||||
if is_instance_valid(enemy) and healer in aggro_tables[enemy]:
|
||||
_add_aggro(enemy, healer, amount * 0.5)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
aggro_tables.erase(entity)
|
||||
seconds_outside.erase(entity)
|
||||
for enemy in aggro_tables:
|
||||
if is_instance_valid(enemy):
|
||||
aggro_tables[enemy].erase(entity)
|
||||
if "target" in enemy and entity == enemy.target:
|
||||
enemy.target = null
|
||||
enemy.state = enemy.State.RETURN
|
||||
1
systems/aggro_system.gd.uid
Normal file
1
systems/aggro_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cm7ehl2pexcst
|
||||
37
systems/buff_system.gd
Normal file
37
systems/buff_system.gd
Normal file
@@ -0,0 +1,37 @@
|
||||
extends Node
|
||||
|
||||
func _ready() -> void:
|
||||
EventBus.role_changed.connect(_on_role_changed)
|
||||
|
||||
func _on_role_changed(player: Node, _role_type: 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:
|
||||
return
|
||||
var damage_mult := 1.0
|
||||
var heal_mult := 1.0
|
||||
var shield_mult := 1.0
|
||||
for ability in ability_set.abilities:
|
||||
if ability and ability.type == Ability.Type.PASSIVE:
|
||||
var bonus: float = ability.damage / 100.0
|
||||
match ability.passive_stat:
|
||||
"damage":
|
||||
damage_mult = 1.0 + bonus
|
||||
"heal":
|
||||
heal_mult = 1.0 + bonus
|
||||
"shield":
|
||||
shield_mult = 1.0 + bonus
|
||||
Stats.set_stat(player, "buff_damage", damage_mult)
|
||||
Stats.set_stat(player, "buff_heal", heal_mult)
|
||||
Stats.set_stat(player, "buff_shield", shield_mult)
|
||||
var base: BaseStats = Stats.get_base(player)
|
||||
if base:
|
||||
var new_max: float = base.max_shield * shield_mult
|
||||
Stats.set_stat(player, "max_shield", new_max)
|
||||
var shield: float = Stats.get_stat(player, "shield")
|
||||
shield = min(shield, new_max)
|
||||
Stats.set_stat(player, "shield", shield)
|
||||
EventBus.shield_changed.emit(player, shield, new_max)
|
||||
EventBus.buff_changed.emit(player, "damage", damage_mult)
|
||||
1
systems/buff_system.gd.uid
Normal file
1
systems/buff_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://da2jm0awq2lnh
|
||||
73
systems/cooldown_system.gd
Normal file
73
systems/cooldown_system.gd
Normal file
@@ -0,0 +1,73 @@
|
||||
extends Node
|
||||
|
||||
var cooldowns: Dictionary = {}
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("cooldown_system")
|
||||
EventBus.role_changed.connect(_on_role_changed)
|
||||
|
||||
func register(entity: Node, ability_count: int) -> void:
|
||||
cooldowns[entity] = {
|
||||
"cds": [] as Array[float],
|
||||
"max_cds": [] as Array[float],
|
||||
"gcd": 0.0,
|
||||
"aa": 0.0,
|
||||
}
|
||||
cooldowns[entity]["cds"].resize(ability_count)
|
||||
cooldowns[entity]["cds"].fill(0.0)
|
||||
cooldowns[entity]["max_cds"].resize(ability_count)
|
||||
cooldowns[entity]["max_cds"].fill(0.0)
|
||||
|
||||
func deregister(entity: Node) -> void:
|
||||
cooldowns.erase(entity)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
for entity in cooldowns:
|
||||
if not is_instance_valid(entity):
|
||||
continue
|
||||
var data: Dictionary = cooldowns[entity]
|
||||
if data["gcd"] > 0:
|
||||
data["gcd"] -= delta
|
||||
if data["aa"] > 0:
|
||||
data["aa"] -= delta
|
||||
var cds: Array = data["cds"]
|
||||
for i in range(cds.size()):
|
||||
if cds[i] > 0:
|
||||
cds[i] -= delta
|
||||
EventBus.cooldown_tick.emit(cds, data["max_cds"], data["gcd"])
|
||||
|
||||
func is_ready(entity: Node, index: int) -> bool:
|
||||
if entity not in cooldowns:
|
||||
return false
|
||||
return cooldowns[entity]["cds"][index] <= 0
|
||||
|
||||
func is_gcd_ready(entity: Node) -> bool:
|
||||
if entity not in cooldowns:
|
||||
return false
|
||||
return cooldowns[entity]["gcd"] <= 0
|
||||
|
||||
func is_aa_ready(entity: Node) -> bool:
|
||||
if entity not in cooldowns:
|
||||
return false
|
||||
return cooldowns[entity]["aa"] <= 0
|
||||
|
||||
func set_cooldown(entity: Node, index: int, cd: float, gcd: float) -> void:
|
||||
if entity not in cooldowns:
|
||||
return
|
||||
var data: Dictionary = cooldowns[entity]
|
||||
data["cds"][index] = cd
|
||||
data["max_cds"][index] = max(cd, gcd)
|
||||
if gcd > 0:
|
||||
data["gcd"] = gcd
|
||||
|
||||
func set_aa_cooldown(entity: Node, cd: float) -> void:
|
||||
if entity not in cooldowns:
|
||||
return
|
||||
cooldowns[entity]["aa"] = cd
|
||||
|
||||
func _on_role_changed(player: Node, _role_type: int) -> void:
|
||||
if player in cooldowns:
|
||||
var data: Dictionary = cooldowns[player]
|
||||
data["cds"].fill(0.0)
|
||||
data["max_cds"].fill(0.0)
|
||||
data["gcd"] = 0.0
|
||||
1
systems/cooldown_system.gd.uid
Normal file
1
systems/cooldown_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ddos7mo8rahou
|
||||
1
systems/damage_system.gd
Normal file
1
systems/damage_system.gd
Normal file
@@ -0,0 +1 @@
|
||||
extends Node
|
||||
1
systems/damage_system.gd.uid
Normal file
1
systems/damage_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cbd1bryh0e2dw
|
||||
36
systems/enemy_ai_system.gd
Normal file
36
systems/enemy_ai_system.gd
Normal file
@@ -0,0 +1,36 @@
|
||||
extends Node
|
||||
|
||||
var attack_timers: Dictionary = {}
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||
if not is_instance_valid(enemy) or not Stats.is_alive(enemy):
|
||||
continue
|
||||
if enemy.state != enemy.State.ATTACK:
|
||||
continue
|
||||
_handle_attack(enemy, delta)
|
||||
|
||||
func _handle_attack(enemy: Node, delta: float) -> void:
|
||||
if enemy not in attack_timers:
|
||||
attack_timers[enemy] = 0.0
|
||||
attack_timers[enemy] -= delta
|
||||
|
||||
if not is_instance_valid(enemy.target):
|
||||
enemy.state = enemy.State.RETURN
|
||||
return
|
||||
|
||||
var base: BaseStats = Stats.get_base(enemy)
|
||||
var attack_range: float = base.attack_range if base is EnemyStats else 2.0
|
||||
var dist: float = enemy.global_position.distance_to(enemy.target.global_position)
|
||||
if dist > attack_range:
|
||||
enemy.state = enemy.State.CHASE
|
||||
return
|
||||
|
||||
if attack_timers[enemy] <= 0:
|
||||
var attack_cooldown: float = base.attack_cooldown if base is EnemyStats else 1.5
|
||||
var attack_damage: float = base.attack_damage if base is EnemyStats else 5.0
|
||||
attack_timers[enemy] = attack_cooldown
|
||||
EventBus.damage_requested.emit(enemy, enemy.target, attack_damage)
|
||||
|
||||
enemy.velocity.x = 0
|
||||
enemy.velocity.z = 0
|
||||
1
systems/enemy_ai_system.gd.uid
Normal file
1
systems/enemy_ai_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bwhxu5586lc1l
|
||||
49
systems/health_system.gd
Normal file
49
systems/health_system.gd
Normal file
@@ -0,0 +1,49 @@
|
||||
extends Node
|
||||
|
||||
func _ready() -> void:
|
||||
EventBus.damage_requested.connect(_on_damage_requested)
|
||||
EventBus.heal_requested.connect(_on_heal_requested)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
for entity in Stats.entities:
|
||||
if not is_instance_valid(entity):
|
||||
continue
|
||||
var data: Dictionary = Stats.entities[entity]
|
||||
if not data["alive"]:
|
||||
continue
|
||||
var regen: float = data["health_regen"]
|
||||
if regen > 0 and data["health"] < data["max_health"]:
|
||||
data["health"] = min(data["health"] + regen * delta, data["max_health"])
|
||||
EventBus.health_changed.emit(entity, data["health"], data["max_health"])
|
||||
|
||||
func _on_damage_requested(attacker: Node, target: Node, amount: float) -> void:
|
||||
if not Stats.is_alive(target):
|
||||
return
|
||||
var remaining: float = amount
|
||||
var shield_system: Node = get_node_or_null("../ShieldSystem")
|
||||
if shield_system:
|
||||
remaining = shield_system.absorb(target, remaining)
|
||||
EventBus.damage_dealt.emit(attacker, target, amount)
|
||||
if remaining > 0:
|
||||
_take_damage(target, remaining)
|
||||
|
||||
func _take_damage(entity: Node, amount: float) -> void:
|
||||
var health: float = Stats.get_stat(entity, "health")
|
||||
health -= amount
|
||||
if health <= 0:
|
||||
health = 0
|
||||
Stats.set_stat(entity, "health", health)
|
||||
var max_health: float = Stats.get_stat(entity, "max_health")
|
||||
EventBus.health_changed.emit(entity, health, max_health)
|
||||
if health <= 0:
|
||||
Stats.set_stat(entity, "alive", false)
|
||||
EventBus.entity_died.emit(entity)
|
||||
|
||||
func _on_heal_requested(healer: Node, target: Node, amount: float) -> void:
|
||||
if not Stats.is_alive(target):
|
||||
return
|
||||
var health: float = Stats.get_stat(target, "health")
|
||||
var max_health: float = Stats.get_stat(target, "max_health")
|
||||
health = min(health + amount, max_health)
|
||||
Stats.set_stat(target, "health", health)
|
||||
EventBus.health_changed.emit(target, health, max_health)
|
||||
1
systems/health_system.gd.uid
Normal file
1
systems/health_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b3wkn5118dimy
|
||||
48
systems/respawn_system.gd
Normal file
48
systems/respawn_system.gd
Normal file
@@ -0,0 +1,48 @@
|
||||
extends Node
|
||||
|
||||
var dead_players: Dictionary = {}
|
||||
|
||||
func _ready() -> void:
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
for player in dead_players.keys():
|
||||
if not is_instance_valid(player):
|
||||
dead_players.erase(player)
|
||||
continue
|
||||
dead_players[player] -= delta
|
||||
EventBus.respawn_tick.emit(dead_players[player])
|
||||
if dead_players[player] <= 0:
|
||||
_respawn(player)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if not entity.is_in_group("player"):
|
||||
return
|
||||
if entity in dead_players:
|
||||
return
|
||||
var base: BaseStats = Stats.get_base(entity)
|
||||
var respawn_time: float = base.respawn_time if base is PlayerStats else 3.0
|
||||
dead_players[entity] = respawn_time
|
||||
entity.velocity = Vector3.ZERO
|
||||
entity.get_node("Mesh").visible = false
|
||||
entity.get_node("CollisionShape3D").disabled = true
|
||||
entity.get_node("Movement").set_physics_process(false)
|
||||
entity.get_node("Combat").set_process_unhandled_input(false)
|
||||
entity.get_node("Targeting").set_process_unhandled_input(false)
|
||||
|
||||
func _respawn(player: Node) -> void:
|
||||
dead_players.erase(player)
|
||||
player.global_position = Vector3(0, 1, -5)
|
||||
player.get_node("Mesh").visible = true
|
||||
player.get_node("CollisionShape3D").disabled = false
|
||||
player.get_node("Movement").set_physics_process(true)
|
||||
player.get_node("Combat").set_process_unhandled_input(true)
|
||||
player.get_node("Targeting").set_process_unhandled_input(true)
|
||||
var max_health: float = Stats.get_stat(player, "max_health")
|
||||
var max_shield: float = Stats.get_stat(player, "max_shield")
|
||||
Stats.set_stat(player, "health", max_health)
|
||||
Stats.set_stat(player, "shield", max_shield)
|
||||
Stats.set_stat(player, "alive", true)
|
||||
EventBus.health_changed.emit(player, max_health, max_health)
|
||||
EventBus.shield_changed.emit(player, max_shield, max_shield)
|
||||
EventBus.player_respawned.emit(player)
|
||||
1
systems/respawn_system.gd.uid
Normal file
1
systems/respawn_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b1qkvoqvmd21h
|
||||
38
systems/shield_system.gd
Normal file
38
systems/shield_system.gd
Normal file
@@ -0,0 +1,38 @@
|
||||
extends Node
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
for entity in Stats.entities:
|
||||
if not is_instance_valid(entity):
|
||||
continue
|
||||
var data: Dictionary = Stats.entities[entity]
|
||||
if not data["alive"]:
|
||||
continue
|
||||
var max_shield: float = data["max_shield"]
|
||||
if max_shield <= 0:
|
||||
continue
|
||||
var shield: float = data["shield"]
|
||||
if shield < max_shield:
|
||||
data["shield_regen_timer"] += delta
|
||||
if data["shield_regen_timer"] >= data["shield_regen_delay"]:
|
||||
var regen_rate: float = max_shield / data["shield_regen_time"]
|
||||
shield += regen_rate * delta
|
||||
if shield >= max_shield:
|
||||
shield = max_shield
|
||||
EventBus.shield_regenerated.emit(entity)
|
||||
data["shield"] = shield
|
||||
EventBus.shield_changed.emit(entity, shield, max_shield)
|
||||
|
||||
func absorb(entity: Node, amount: float) -> float:
|
||||
var shield: float = Stats.get_stat(entity, "shield")
|
||||
if shield == null or shield <= 0:
|
||||
return amount
|
||||
Stats.set_stat(entity, "shield_regen_timer", 0.0)
|
||||
var absorbed: float = min(amount, shield)
|
||||
shield -= absorbed
|
||||
Stats.set_stat(entity, "shield", shield)
|
||||
var max_shield: float = Stats.get_stat(entity, "max_shield")
|
||||
if shield <= 0:
|
||||
EventBus.shield_broken.emit(entity)
|
||||
EventBus.shield_changed.emit(entity, shield, max_shield)
|
||||
return amount - absorbed
|
||||
|
||||
1
systems/shield_system.gd.uid
Normal file
1
systems/shield_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://rsnpuf77o0sn
|
||||
52
systems/spawn_system.gd
Normal file
52
systems/spawn_system.gd
Normal file
@@ -0,0 +1,52 @@
|
||||
extends Node
|
||||
|
||||
const ENEMY_SCENE: PackedScene = preload("res://enemy/enemy.tscn")
|
||||
|
||||
var portal_data: Dictionary = {}
|
||||
|
||||
func _ready() -> void:
|
||||
EventBus.health_changed.connect(_on_health_changed)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
|
||||
func _on_health_changed(entity: Node, current: float, max_val: float) -> void:
|
||||
if not entity.is_in_group("portals"):
|
||||
return
|
||||
if entity not in portal_data:
|
||||
var base: BaseStats = Stats.get_base(entity)
|
||||
var thresholds: Array[float] = base.thresholds if base is PortalStats else [0.85, 0.70, 0.55, 0.40, 0.25, 0.10]
|
||||
var triggered: Array[bool] = []
|
||||
triggered.resize(thresholds.size())
|
||||
triggered.fill(false)
|
||||
portal_data[entity] = { "thresholds": thresholds, "triggered": triggered }
|
||||
if current <= 0:
|
||||
return
|
||||
var data: Dictionary = portal_data[entity]
|
||||
var ratio: float = current / max_val
|
||||
var base: BaseStats = Stats.get_base(entity)
|
||||
var spawn_count: int = base.spawn_count if base is PortalStats else 3
|
||||
for i in range(data["thresholds"].size()):
|
||||
if not data["triggered"][i] and ratio <= data["thresholds"][i]:
|
||||
data["triggered"][i] = true
|
||||
_spawn_enemies(entity, spawn_count)
|
||||
|
||||
func _spawn_enemies(portal: Node, count: int) -> void:
|
||||
var spawned: Array = []
|
||||
for j in range(count):
|
||||
var entity: Node = ENEMY_SCENE.instantiate()
|
||||
var offset := Vector3(randf_range(-2, 2), 0, randf_range(-2, 2))
|
||||
portal.get_parent().add_child(entity)
|
||||
entity.global_position = portal.global_position + offset
|
||||
entity.spawn_position = portal.global_position
|
||||
entity.portal = portal
|
||||
spawned.append(entity)
|
||||
var player: Node = get_tree().get_first_node_in_group("player")
|
||||
if player:
|
||||
var dist: float = portal.global_position.distance_to(player.global_position)
|
||||
if dist <= 10.0:
|
||||
for entity in spawned:
|
||||
EventBus.enemy_detected.emit(entity, player)
|
||||
EventBus.portal_spawn.emit(portal, spawned)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity.is_in_group("portals"):
|
||||
portal_data.erase(entity)
|
||||
1
systems/spawn_system.gd.uid
Normal file
1
systems/spawn_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c84voxmnaifyt
|
||||
Reference in New Issue
Block a user