nodes/stats.pyΒΆ
Part of GDQuest Open RPG.
1"""Battler stats: hp, energy, attack/defense, speed, hit/evasion."""
2
3from __future__ import annotations
4
5from dataclasses import dataclass
6
7from simvx.core.signals import Signal
8
9
10@dataclass
11class StatsSpec:
12 """Static prototype of a battler's stats."""
13
14 name: str
15 max_health: int = 30
16 max_energy: int = 10
17 attack: int = 8
18 defense: int = 4
19 speed: int = 50
20 hit_chance: int = 95 # % to hit before evasion
21 evasion: int = 5 # % chance to dodge
22
23
24class BattlerStats:
25 """Live mutable stats with signals."""
26
27 def __init__(self, spec: StatsSpec) -> None:
28 self.spec = spec
29 self.max_health = spec.max_health
30 self.max_energy = spec.max_energy
31 self.attack = spec.attack
32 self.defense = spec.defense
33 self.speed = spec.speed
34 self.hit_chance = spec.hit_chance
35 self.evasion = spec.evasion
36 self.health = spec.max_health
37 self.energy = spec.max_energy
38
39 self.health_changed = Signal() # (current, max)
40 self.energy_changed = Signal() # (current, max)
41 self.health_depleted = Signal()
42
43 def take_damage(self, amount: int) -> int:
44 prev = self.health
45 self.health = max(0, self.health - max(0, amount))
46 actual = prev - self.health
47 self.health_changed.emit(self.health, self.max_health)
48 if self.health == 0:
49 self.health_depleted.emit()
50 return actual
51
52 def heal(self, amount: int) -> int:
53 prev = self.health
54 self.health = min(self.max_health, self.health + max(0, amount))
55 delta = self.health - prev
56 self.health_changed.emit(self.health, self.max_health)
57 return delta
58
59 def spend_energy(self, amount: int) -> bool:
60 if self.energy < amount:
61 return False
62 self.energy = max(0, self.energy - amount)
63 self.energy_changed.emit(self.energy, self.max_energy)
64 return True
65
66 def restore_energy(self, amount: int) -> None:
67 self.energy = min(self.max_energy, self.energy + amount)
68 self.energy_changed.emit(self.energy, self.max_energy)
69
70 @property
71 def is_alive(self) -> bool:
72 return self.health > 0
73
74
75# ---- prototypes ----------------------------------------------------------
76
77PARTY_PROTOS = {
78 "knight": StatsSpec(
79 name="Knight", max_health=42, max_energy=8, attack=10, defense=6, speed=45, hit_chance=95, evasion=5
80 ),
81 "wizard": StatsSpec(
82 name="Wizard", max_health=24, max_energy=14, attack=8, defense=3, speed=55, hit_chance=90, evasion=10
83 ),
84 "squirrel": StatsSpec(
85 name="Squirrel", max_health=20, max_energy=10, attack=6, defense=2, speed=70, hit_chance=98, evasion=20
86 ),
87}
88
89ENEMY_PROTOS = {
90 "wolf": StatsSpec(
91 name="Wolf", max_health=18, max_energy=4, attack=8, defense=3, speed=60, hit_chance=85, evasion=15
92 ),
93 "bear": StatsSpec(
94 name="Bear", max_health=32, max_energy=4, attack=12, defense=6, speed=35, hit_chance=80, evasion=5
95 ),
96 "bugcat": StatsSpec(
97 name="Bugcat", max_health=14, max_energy=6, attack=6, defense=2, speed=65, hit_chance=88, evasion=18
98 ),
99}