131 lines
4.7 KiB
GDScript
131 lines
4.7 KiB
GDScript
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
|