class_name HealthComponent extends Node signal health_changed(current: int, maximum: int) signal depleted @export var maximum := 100 @export var current := 100 @export var hitstun_seconds := 0.4 var _hitstun_time_left := 0.0 func _ready() -> void: _emit_changed() func _process(delta: float) -> void: if _hitstun_time_left <= 0.0: return _hitstun_time_left = maxf(0.0, _hitstun_time_left - delta) if _hitstun_time_left <= 0.0 and current > 0: var state_machine := _state_machine_or_null() if state_machine != null and state_machine.has_method("set_life_state"): state_machine.call("set_life_state", &"Alive") func set_values(next_current: int, next_maximum: int) -> void: maximum = max(1, next_maximum) current = clampi(next_current, 0, maximum) if current > 0: _hitstun_time_left = 0.0 _emit_changed() func apply_damage(amount: int) -> void: if amount <= 0: return current = clampi(current - amount, 0, maximum) _emit_changed() if current == 0: depleted.emit() func receive_hit(result: Dictionary) -> void: var amount := int(result.get("damage", result.get("amount", 0))) apply_damage(amount) var state_machine := _state_machine_or_null() if state_machine == null or not state_machine.has_method("set_life_state"): return if current <= 0: _hitstun_time_left = 0.0 state_machine.call("set_life_state", &"Dead") elif amount > 0 and bool(result.get("interrupts", true)): _hitstun_time_left = maxf(0.0, hitstun_seconds) state_machine.call("set_life_state", &"Hitstun") func heal(amount: int) -> void: if amount <= 0: return current = clampi(current + amount, 0, maximum) _emit_changed() func _emit_changed() -> void: health_changed.emit(current, maximum) var bus := _event_bus_or_null() if bus != null and _is_player_health_component(): bus.emit_signal("player_health_changed", current, maximum) func _is_player_health_component() -> bool: var actor := get_parent() return actor != null and actor.name == "Player" func _event_bus_or_null() -> Node: if not is_inside_tree(): return null return get_tree().root.get_node_or_null("EventBus") func _state_machine_or_null() -> Node: if not is_inside_tree(): return null return get_node_or_null("../StateMachine")