update
This commit is contained in:
68
systems/aggro/aggro_decay.gd
Normal file
68
systems/aggro/aggro_decay.gd
Normal file
@@ -0,0 +1,68 @@
|
||||
extends Node
|
||||
|
||||
var tracker: Node
|
||||
var config: AggroConfig
|
||||
var last_damage_time: Dictionary = {}
|
||||
|
||||
func process(delta: float) -> void:
|
||||
_update_combat_timers(delta)
|
||||
for enemy in tracker.aggro_tables.keys():
|
||||
if not is_instance_valid(enemy):
|
||||
tracker.aggro_tables.erase(enemy)
|
||||
tracker.players_in_range.erase(enemy)
|
||||
continue
|
||||
_decay_aggro(enemy, delta)
|
||||
tracker.update_target(enemy)
|
||||
|
||||
func _update_combat_timers(delta: float) -> void:
|
||||
for player in last_damage_time.keys():
|
||||
if not is_instance_valid(player):
|
||||
last_damage_time.erase(player)
|
||||
else:
|
||||
last_damage_time[player] += delta
|
||||
|
||||
func _decay_aggro(enemy: Node, delta: float) -> void:
|
||||
var table: Dictionary = tracker.aggro_tables[enemy]
|
||||
var base: BaseStats = Stats.get_base(enemy)
|
||||
var aggro_decay: float = base.aggro_decay if base is EnemyStats else 1.0
|
||||
for player in table.keys():
|
||||
if is_in_combat(player):
|
||||
continue
|
||||
var time_since_combat: float = last_damage_time.get(player, config.combat_timeout) - config.combat_timeout
|
||||
var decay: float = aggro_decay * delta
|
||||
decay += _exponential_decay(table[player], time_since_combat, delta)
|
||||
table[player] -= decay
|
||||
if table[player] <= 0:
|
||||
table.erase(player)
|
||||
|
||||
func reset_combat_timer(player: Node) -> void:
|
||||
last_damage_time[player] = 0.0
|
||||
|
||||
func is_in_combat(player: Node) -> bool:
|
||||
if tracker.is_player_in_any_range(player):
|
||||
return true
|
||||
return last_damage_time.get(player, config.combat_timeout + 1.0) < config.combat_timeout
|
||||
|
||||
func _exponential_decay(aggro: float, time_outside: float, delta: float) -> float:
|
||||
if time_outside <= 0:
|
||||
return 0.0
|
||||
return aggro * config.exponential_decay_factor * pow(2, time_outside) * delta
|
||||
|
||||
func spread_aggro(source: Node, attacker: Node, amount: float) -> void:
|
||||
if not is_instance_valid(source):
|
||||
return
|
||||
var radius: float = tracker.get_alert_radius(source)
|
||||
for enemy in tracker.get_enemies_in_radius(source, radius):
|
||||
tracker.add_aggro(enemy, attacker, amount)
|
||||
|
||||
func alert_nearby(enemy: Node, target: Node) -> void:
|
||||
var radius: float = tracker.get_alert_radius(enemy)
|
||||
for other in tracker.get_enemies_in_radius(enemy, radius):
|
||||
if "state" in other and other.state == other.State.IDLE:
|
||||
tracker.add_aggro(other, target, 1.0)
|
||||
other.target = target
|
||||
other.state = other.State.CHASE
|
||||
EventBus.enemy_engaged.emit(other, target)
|
||||
|
||||
func erase_entity(entity: Node) -> void:
|
||||
last_damage_time.erase(entity)
|
||||
1
systems/aggro/aggro_decay.gd.uid
Normal file
1
systems/aggro/aggro_decay.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cysg30lud2ta2
|
||||
52
systems/aggro/aggro_events.gd
Normal file
52
systems/aggro/aggro_events.gd
Normal file
@@ -0,0 +1,52 @@
|
||||
extends Node
|
||||
|
||||
var tracker: Node
|
||||
var decay: Node
|
||||
var config: AggroConfig
|
||||
|
||||
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)
|
||||
EventBus.enemy_lost.connect(_on_enemy_lost)
|
||||
|
||||
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
|
||||
tracker.add_player_in_range(enemy, player)
|
||||
tracker.add_aggro(enemy, player, 1.0)
|
||||
if "state" in enemy:
|
||||
enemy.target = player
|
||||
enemy.state = enemy.State.CHASE
|
||||
EventBus.enemy_engaged.emit(enemy, player)
|
||||
decay.alert_nearby(enemy, player)
|
||||
|
||||
func _on_enemy_lost(enemy: Node, player: Node) -> void:
|
||||
tracker.remove_player_in_range(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
|
||||
decay.reset_combat_timer(attacker)
|
||||
var multiplier := 1.0
|
||||
var role: Node = attacker.get_node_or_null("Role")
|
||||
if role and role.current_role == 0:
|
||||
multiplier = config.tank_multiplier
|
||||
var aggro: float = amount * multiplier
|
||||
tracker.add_aggro(target, attacker, aggro)
|
||||
decay.spread_aggro(target, attacker, aggro * config.spread_multiplier)
|
||||
|
||||
func _on_heal_requested(healer: Node, _target: Node, amount: float) -> void:
|
||||
if not healer.is_in_group("player"):
|
||||
return
|
||||
for enemy in tracker.aggro_tables:
|
||||
if is_instance_valid(enemy) and healer in tracker.aggro_tables[enemy]:
|
||||
tracker.add_aggro(enemy, healer, amount * config.heal_multiplier)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
tracker.erase_entity(entity)
|
||||
decay.erase_entity(entity)
|
||||
1
systems/aggro/aggro_events.gd.uid
Normal file
1
systems/aggro/aggro_events.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cyffo1g4uhmwh
|
||||
17
systems/aggro/aggro_system.gd
Normal file
17
systems/aggro/aggro_system.gd
Normal file
@@ -0,0 +1,17 @@
|
||||
extends Node
|
||||
|
||||
@export var config: AggroConfig = preload("res://resources/stats/aggro_config.tres")
|
||||
|
||||
@onready var tracker: Node = $AggroTracker
|
||||
@onready var decay: Node = $AggroDecay
|
||||
@onready var events: Node = $AggroEvents
|
||||
|
||||
func _ready() -> void:
|
||||
decay.tracker = tracker
|
||||
decay.config = config
|
||||
events.tracker = tracker
|
||||
events.decay = decay
|
||||
events.config = config
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
decay.process(delta)
|
||||
1
systems/aggro/aggro_system.gd.uid
Normal file
1
systems/aggro/aggro_system.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cm7ehl2pexcst
|
||||
82
systems/aggro/aggro_tracker.gd
Normal file
82
systems/aggro/aggro_tracker.gd
Normal file
@@ -0,0 +1,82 @@
|
||||
extends Node
|
||||
|
||||
var aggro_tables: Dictionary = {}
|
||||
var players_in_range: Dictionary = {}
|
||||
|
||||
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 remove_aggro(enemy: Node, player: Node, amount: float) -> void:
|
||||
if enemy in aggro_tables and player in aggro_tables[enemy]:
|
||||
aggro_tables[enemy][player] -= amount
|
||||
if aggro_tables[enemy][player] <= 0:
|
||||
aggro_tables[enemy].erase(player)
|
||||
|
||||
func add_player_in_range(enemy: Node, player: Node) -> void:
|
||||
if enemy not in players_in_range:
|
||||
players_in_range[enemy] = []
|
||||
if player not in players_in_range[enemy]:
|
||||
players_in_range[enemy].append(player)
|
||||
|
||||
func remove_player_in_range(enemy: Node, player: Node) -> void:
|
||||
if enemy in players_in_range:
|
||||
players_in_range[enemy].erase(player)
|
||||
|
||||
func is_player_in_any_range(player: Node) -> bool:
|
||||
for enemy in players_in_range:
|
||||
if is_instance_valid(enemy) and player in players_in_range[enemy]:
|
||||
return true
|
||||
return false
|
||||
|
||||
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 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 get_enemies_in_radius(source: Node, radius: float) -> Array:
|
||||
var result: Array = []
|
||||
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||
if enemy != source and is_instance_valid(enemy):
|
||||
var dist: float = source.global_position.distance_to(enemy.global_position)
|
||||
if dist <= radius:
|
||||
result.append(enemy)
|
||||
return result
|
||||
|
||||
func get_alert_radius(entity: Node) -> float:
|
||||
var base: BaseStats = Stats.get_base(entity)
|
||||
return base.alert_radius if base is EnemyStats else 10.0
|
||||
|
||||
func erase_entity(entity: Node) -> void:
|
||||
aggro_tables.erase(entity)
|
||||
players_in_range.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
|
||||
for enemy in players_in_range:
|
||||
if is_instance_valid(enemy):
|
||||
players_in_range[enemy].erase(entity)
|
||||
1
systems/aggro/aggro_tracker.gd.uid
Normal file
1
systems/aggro/aggro_tracker.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c7gsu2qddsor6
|
||||
Reference in New Issue
Block a user