nodes/stats.py

Part of HeartBeast Action RPG.

 1"""Stats node: signal-based health tracking.
 2
 3Mirrors the upstream HeartBeast Stats scene: a standalone node that tracks
 4current/max health and emits signals on changes. Used by Player, Enemy,
 5and any other entity with HP.
 6"""
 7
 8from __future__ import annotations
 9
10from simvx.core import Node, Signal
11
12
13class Stats(Node):
14    """Signal-based health tracker, matches the upstream HeartBeast Stats scene."""
15
16    health_changed = Signal()  # (new_health: int)
17    max_health_changed = Signal()  # (new_max: int)
18    no_health = Signal()  # emitted when health drops to 0
19
20    def __init__(self, max_health: int = 6, **kwargs):
21        super().__init__(**kwargs)
22        self._max_health = max_health
23        self._health = max_health
24        self.health_changed.emit(self._health)
25        self.max_health_changed.emit(self._max_health)
26
27    # ── Properties ───────────────────────────────────────────────────────────
28
29    @property
30    def health(self) -> int:
31        return self._health
32
33    @health.setter
34    def health(self, value: int):
35        old = self._health
36        self._health = max(0, min(value, self._max_health))
37        if self._health != old:
38            self.health_changed.emit(self._health)
39        if self._health <= 0:
40            self.no_health.emit()
41
42    @property
43    def max_health(self) -> int:
44        return self._max_health
45
46    @max_health.setter
47    def max_health(self, value: int):
48        self._max_health = max(1, value)
49        self.max_health_changed.emit(self._max_health)
50        # Clamp current health
51        if self._health > self._max_health:
52            self._health = self._max_health
53            self.health_changed.emit(self._health)