nodes/death_screen.pyΒΆ
Part of Dungeon Explorer.
1"""Death / respawn overlay: red vignette, stat summary, pulsing respawn prompt."""
2
3import math
4
5from simvx.core import Anchor, Control, Property
6from simvx.core.input import Key, KeyEvent, Pointer, PointerEvent, PointerPhase
7
8
9class DeathScreen(Control):
10 """Full-screen death overlay with red vignette and stats.
11
12 No back button. Esc / outside click is suppressed (player must press
13 Space or click anywhere after the 2 second reveal). The respawn action
14 is delivered via the ``on_respawn`` callback supplied at construction.
15 """
16
17 visible = Property(
18 False,
19 coerce=bool,
20 hint="Whether this node and its subtree are drawn",
21 on_change="_on_visible_changed",
22 )
23
24 # on_draw animates every frame off self._timer (fade-in alpha, staggered
25 # reveals, pulsing respawn prompt) -> retained 2D must re-run it each frame.
26 dynamic = True
27
28 def __init__(self, on_respawn=None, **kwargs):
29 super().__init__(name="DeathScreen", anchor=Anchor.FULL_RECT, **kwargs)
30
31 self._on_respawn = on_respawn
32 self._timer = 0.0
33 self._player = None
34 self._dungeon_level = 1
35 self._gold_lost = 0
36
37 # -- Public API --
38
39 def set_context(self, player=None, dungeon_level: int = 1) -> None:
40 self._player = player
41 self._dungeon_level = dungeon_level
42
43 def show(self):
44 self._timer = 0.0
45 if self._player:
46 self._gold_lost = max(1, self._player.gold // 10)
47 self.show_overlay("blocking", dismiss=False)
48
49 # -- Frame update / input --
50
51 def on_update(self, dt: float):
52 self._timer += dt
53
54 def on_gui_input(self, event):
55 if not self.visible:
56 return
57 # Space / Enter / mouse-press after 2s reveal = respawn.
58 if self._timer < 2.0:
59 return
60 if isinstance(event, PointerEvent):
61 if event.button in Pointer.PRIMARY and event.phase is PointerPhase.DOWN:
62 self._respawn()
63 return True
64 elif isinstance(event, KeyEvent) and event.pressed and event.key in (Key.SPACE, Key.ENTER, Key.E):
65 self._respawn()
66 return True
67
68 def _respawn(self):
69 self.cancel_requested.emit()
70 self.close_overlay()
71 if self._on_respawn:
72 self._on_respawn()
73
74 # -- Rendering --
75
76 def _screen_size(self) -> tuple[float, float]:
77 if self.tree is not None:
78 sw, sh = self.tree.screen_size
79 return float(sw), float(sh)
80 return 1280.0, 720.0
81
82 def on_draw(self, renderer):
83 if not self.visible:
84 return
85 sw, sh = self._screen_size()
86 alpha = min(1.0, self._timer / 1.0)
87
88 # Dark red background
89 renderer.rect((0, 0), (sw, sh), fill=(0.12, 0.0, 0.0, 0.85 * alpha))
90
91 # Red vignette edges
92 vig_a = 0.5 * alpha
93 vig_w = 80.0
94 renderer.rect((0, 0), (vig_w, sh), fill=(0.3, 0.0, 0.0, vig_a))
95 renderer.rect((sw - vig_w, 0), (vig_w, sh), fill=(0.3, 0.0, 0.0, vig_a))
96 renderer.rect((0, 0), (sw, vig_w), fill=(0.3, 0.0, 0.0, vig_a))
97 renderer.rect((0, sh - vig_w), (sw, vig_w), fill=(0.3, 0.0, 0.0, vig_a))
98
99 cx = sw / 2
100
101 if self._timer > 0.5:
102 text_a = min(1.0, (self._timer - 0.5) * 2.0)
103 renderer.text("YOU DIED", (cx - 60 + 2, 202), fill=(0.0, 0.0, 0.0, text_a * 0.5), scale=3.0)
104 renderer.text("YOU DIED", (cx - 60, 200), fill=(0.8, 0.1, 0.1, text_a), scale=3.0)
105
106 if self._timer > 1.5:
107 stat_a = min(1.0, (self._timer - 1.5) * 2.0)
108 sy = 300
109 renderer.text("-- Final Stats --", (cx - 60, sy), fill=(0.7, 0.5, 0.5, stat_a), scale=1.1)
110 sy += 28
111 if self._player:
112 stats = [
113 (f"Level: {self._player.level}", (0.8, 0.8, 0.8, stat_a)),
114 (f"Dungeon Floor: {self._dungeon_level}", (0.8, 0.8, 0.8, stat_a)),
115 (f"Gold: {self._player.gold}", (1.0, 0.85, 0.2, stat_a)),
116 (f"Enemies Slain: {getattr(self._player, '_total_kills', 0)}", (0.8, 0.5, 0.5, stat_a)),
117 ]
118 for text, colour in stats:
119 renderer.text(text, (cx - 70, sy), fill=colour, scale=1.0)
120 sy += 24
121
122 if self._timer > 2.0:
123 pulse = 0.5 + 0.5 * math.sin(self._timer * 3.0)
124 renderer.text(
125 "Press SPACE to respawn at last save point",
126 (cx - 150, sh - 120),
127 fill=(0.7, 0.7, 0.7, pulse),
128 scale=1.2,
129 )