nodes/level_up_ui.pyΒΆ
Part of Dungeon Explorer.
1"""Stat point allocation screen shown on level up."""
2
3from simvx.core import Control, Property
4from simvx.core.input import MouseButton
5from simvx.core.ui.enums import AnchorPreset
6
7from ._back_button import back_button_hit, check_menu_click, draw_back_button
8
9
10class LevelUpUI(Control):
11 """Stat point allocation overlay."""
12
13 visible = Property(
14 False,
15 coerce=bool,
16 hint="Whether this node and its subtree are drawn",
17 on_change="_on_visible_changed",
18 )
19
20 STAT_NAMES = ["Strength", "Dexterity", "Vitality", "Intelligence"]
21 STAT_KEYS = ["strength", "dexterity", "vitality", "intelligence"]
22
23 def __init__(self, **kwargs):
24 super().__init__(name="LevelUpUI", **kwargs)
25 self.set_anchor_preset(AnchorPreset.FULL_RECT)
26
27 self._selected = 0
28 self._player = None
29
30 def set_player(self, player) -> None:
31 self._player = player
32
33 def show(self):
34 self._selected = 0
35 self.show_overlay("blocking", dismiss=True)
36
37 # -- Input --
38
39 def _on_gui_input(self, event):
40 if not self.visible:
41 return
42 if event.button == MouseButton.LEFT and event.pressed:
43 self._handle_click(float(event.position[0]), float(event.position[1]), event)
44 return
45 if event.key and event.pressed:
46 if event.key in ("w", "up"):
47 self._selected = (self._selected - 1) % len(self.STAT_NAMES)
48 event.handled = True
49 elif event.key in ("s", "down"):
50 self._selected = (self._selected + 1) % len(self.STAT_NAMES)
51 event.handled = True
52 elif event.key in ("space", "enter", "return", "e"):
53 self._allocate()
54 if self._player is None or self._player.stat_points <= 0:
55 self.cancel_requested.emit()
56 self.close_overlay()
57 event.handled = True
58
59 def _handle_click(self, mx: float, my: float, event):
60 if back_button_hit(mx, my):
61 self.cancel_requested.emit()
62 self.close_overlay()
63 event.handled = True
64 return
65 sw, _ = self._screen_size()
66 cx = sw / 2
67 w = 300
68 x = cx - w / 2
69 sy = 280
70 idx = check_menu_click(mx, my, len(self.STAT_NAMES), x + 20, sy, w - 40, 24, 30)
71 if idx is not None:
72 self._selected = idx
73 self._allocate()
74 if self._player is None or self._player.stat_points <= 0:
75 self.cancel_requested.emit()
76 self.close_overlay()
77 event.handled = True
78
79 def _allocate(self):
80 if self._player is None or self._player.stat_points <= 0:
81 return
82 key = self.STAT_KEYS[self._selected]
83 current = getattr(self._player, key, 0)
84 setattr(self._player, key, current + 1)
85 self._player.stat_points -= 1
86
87 # -- Rendering --
88
89 def _screen_size(self) -> tuple[float, float]:
90 if self.tree is not None:
91 sw, sh = self.tree.screen_size
92 return float(sw), float(sh)
93 return 1280.0, 720.0
94
95 def on_draw(self, renderer):
96 if not self.visible or self._player is None:
97 return
98 p = self._player
99 sw, sh = self._screen_size()
100 cx = sw / 2
101 w, h = 300, 260
102 x, y = cx - w / 2, 200
103
104 renderer.draw_rect((0, 0), (sw, sh), colour=(0.0, 0.0, 0.0, 0.5), filled=True)
105 renderer.draw_rect((x, y), (w, h), colour=(0.1, 0.1, 0.15, 0.95), filled=True)
106 renderer.draw_rect((x, y), (w, 2), colour=(1.0, 0.9, 0.3, 1.0), filled=True)
107 renderer.draw_text("LEVEL UP!", (cx - 40, y + 15), scale=1.8, colour=(1.0, 0.9, 0.3, 1.0))
108 renderer.draw_text(f"Points: {p.stat_points}", (cx - 30, y + 45), scale=1.0, colour=(0.8, 0.8, 0.8))
109
110 sy = y + 80
111 for i, (name, key) in enumerate(zip(self.STAT_NAMES, self.STAT_KEYS, strict=False)):
112 val = getattr(p, key, 0)
113 colour = (1.0, 0.9, 0.3, 1.0) if i == self._selected else (0.7, 0.7, 0.7, 1.0)
114 prefix = "> " if i == self._selected else " "
115 renderer.draw_text(f"{prefix}{name}: {val}", (x + 20, sy + i * 30), scale=1.2, colour=colour)
116
117 renderer.draw_text("Space: Allocate Esc: Close", (x + 20, y + h - 25), scale=0.8, colour=(0.5, 0.5, 0.5, 1.0))
118 draw_back_button(renderer)