crimsonland 0.1.0.dev11__py3-none-any.whl → 0.1.0.dev13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. crimson/assets_fetch.py +23 -8
  2. crimson/creatures/runtime.py +15 -0
  3. crimson/demo.py +47 -38
  4. crimson/effects.py +46 -1
  5. crimson/frontend/boot.py +2 -1
  6. crimson/frontend/high_scores_layout.py +26 -0
  7. crimson/frontend/menu.py +24 -43
  8. crimson/frontend/panels/base.py +27 -29
  9. crimson/frontend/panels/controls.py +152 -65
  10. crimson/frontend/panels/credits.py +221 -0
  11. crimson/frontend/panels/databases.py +307 -0
  12. crimson/frontend/panels/mods.py +1 -3
  13. crimson/frontend/panels/options.py +36 -42
  14. crimson/frontend/panels/play_game.py +82 -74
  15. crimson/frontend/panels/stats.py +255 -298
  16. crimson/frontend/pause_menu.py +425 -0
  17. crimson/game.py +512 -505
  18. crimson/gameplay.py +35 -6
  19. crimson/modes/base_gameplay_mode.py +3 -0
  20. crimson/modes/quest_mode.py +54 -44
  21. crimson/modes/rush_mode.py +4 -1
  22. crimson/modes/survival_mode.py +15 -10
  23. crimson/modes/tutorial_mode.py +15 -5
  24. crimson/modes/typo_mode.py +4 -1
  25. crimson/persistence/highscores.py +6 -2
  26. crimson/render/world_renderer.py +1 -1
  27. crimson/sim/world_state.py +8 -1
  28. crimson/typo/spawns.py +3 -4
  29. crimson/ui/demo_trial_overlay.py +3 -3
  30. crimson/ui/game_over.py +18 -2
  31. crimson/ui/menu_panel.py +127 -0
  32. crimson/ui/perk_menu.py +101 -44
  33. crimson/ui/quest_results.py +669 -0
  34. crimson/ui/shadow.py +39 -0
  35. crimson/views/particles.py +1 -1
  36. crimson/views/perk_menu_debug.py +2 -2
  37. crimson/views/perks.py +2 -2
  38. crimson/weapons.py +110 -110
  39. {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev13.dist-info}/METADATA +1 -1
  40. {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev13.dist-info}/RECORD +43 -36
  41. grim/app.py +3 -0
  42. {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev13.dist-info}/WHEEL +0 -0
  43. {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev13.dist-info}/entry_points.txt +0 -0
@@ -9,11 +9,13 @@ from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
9
9
  from ..menu import (
10
10
  MENU_PANEL_OFFSET_X,
11
11
  MENU_PANEL_OFFSET_Y,
12
+ MENU_PANEL_HEIGHT,
12
13
  MENU_PANEL_WIDTH,
13
14
  MenuView,
14
15
  _draw_menu_cursor,
15
16
  )
16
17
  from ..transitions import _draw_screen_fade
18
+ from ...ui.menu_panel import draw_classic_menu_panel
17
19
  from .base import PANEL_TIMELINE_END_MS, PANEL_TIMELINE_START_MS, PanelMenuView
18
20
  from ...input_codes import config_keybinds, input_code_name, player_move_fire_binds
19
21
 
@@ -21,20 +23,46 @@ if TYPE_CHECKING:
21
23
  from ...game import GameState
22
24
 
23
25
 
26
+ # Measured from ui_render_trace_oracle_1024x768.json (state_3:Configure for:, timeline=300).
27
+ CONTROLS_LEFT_PANEL_POS_X = -165.0
28
+ CONTROLS_LEFT_PANEL_POS_Y = 200.0
29
+ CONTROLS_RIGHT_PANEL_POS_X = 590.0
30
+ CONTROLS_RIGHT_PANEL_POS_Y = 110.0
31
+ CONTROLS_RIGHT_PANEL_HEIGHT = 378.0
32
+ CONTROLS_BACK_POS_X = -155.0
33
+ CONTROLS_BACK_POS_Y = 420.0
34
+
35
+
24
36
  class ControlsMenuView(PanelMenuView):
25
37
  def __init__(self, state: GameState) -> None:
26
- super().__init__(state, title="Controls", back_action="open_options")
38
+ super().__init__(
39
+ state,
40
+ title="Controls",
41
+ back_action="open_options",
42
+ panel_pos_x=CONTROLS_LEFT_PANEL_POS_X,
43
+ panel_pos_y=CONTROLS_LEFT_PANEL_POS_Y,
44
+ back_pos_x=CONTROLS_BACK_POS_X,
45
+ back_pos_y=CONTROLS_BACK_POS_Y,
46
+ )
27
47
  self._small_font: SmallFontData | None = None
28
- self._lines: list[str] = []
48
+ self._text_controls: rl.Texture2D | None = None
49
+ self._drop_off: rl.Texture2D | None = None
50
+ self._check_on: rl.Texture2D | None = None
51
+ self._check_off: rl.Texture2D | None = None
52
+
53
+ self._config_player = 1
29
54
 
30
55
  def open(self) -> None:
31
56
  super().open()
32
- self._lines = self._build_lines()
57
+ cache = self._ensure_cache()
58
+ # UI elements used by the classic controls screen.
59
+ self._text_controls = cache.get_or_load("ui_textControls", "ui/ui_textControls.jaz").texture
60
+ self._drop_off = cache.get_or_load("ui_dropOff", "ui/ui_dropDownOff.jaz").texture
61
+ self._check_on = cache.get_or_load("ui_checkOn", "ui/ui_checkOn.jaz").texture
62
+ self._check_off = cache.get_or_load("ui_checkOff", "ui/ui_checkOff.jaz").texture
33
63
 
34
64
  def draw(self) -> None:
35
- rl.clear_background(rl.BLACK)
36
- if self._ground is not None:
37
- self._ground.draw(0.0, 0.0)
65
+ self._draw_background()
38
66
  _draw_screen_fade(self._state)
39
67
  assets = self._assets
40
68
  entry = self._entry
@@ -53,9 +81,17 @@ class ControlsMenuView(PanelMenuView):
53
81
  self._small_font = load_small_font(self._state.assets_dir, missing_assets)
54
82
  return self._small_font
55
83
 
56
- def _content_layout(self) -> dict[str, float]:
57
- panel_scale, _local_shift = self._menu_item_scale(0)
84
+ def _draw_panel(self) -> None:
85
+ assets = self._assets
86
+ if assets is None or assets.panel is None:
87
+ return
88
+ panel = assets.panel
89
+
90
+ fx_detail = bool(self._state.config.data.get("fx_detail_0", 0))
91
+ panel_scale, _local_y_shift = self._menu_item_scale(0)
58
92
  panel_w = MENU_PANEL_WIDTH * panel_scale
93
+
94
+ # Left (controls options) panel: standard 254px height => a single quad.
59
95
  _angle_rad, slide_x = MenuView._ui_element_anim(
60
96
  self,
61
97
  index=1,
@@ -63,23 +99,93 @@ class ControlsMenuView(PanelMenuView):
63
99
  end_ms=PANEL_TIMELINE_END_MS,
64
100
  width=panel_w,
65
101
  )
66
- panel_x = self._panel_pos_x + slide_x
67
- panel_y = self._panel_pos_y + self._widescreen_y_shift
68
- origin_x = -(MENU_PANEL_OFFSET_X * panel_scale)
69
- origin_y = -(MENU_PANEL_OFFSET_Y * panel_scale)
70
- panel_left = panel_x - origin_x
71
- panel_top = panel_y - origin_y
72
- base_x = panel_left + 212.0 * panel_scale
73
- base_y = panel_top + 32.0 * panel_scale
74
- label_x = base_x + 8.0 * panel_scale
75
- return {
76
- "base_x": base_x,
77
- "base_y": base_y,
78
- "label_x": label_x,
79
- "scale": panel_scale,
80
- }
81
-
82
- def _build_lines(self) -> list[str]:
102
+ left_x0 = self._panel_pos_x + slide_x + MENU_PANEL_OFFSET_X * panel_scale
103
+ left_y0 = self._panel_pos_y + self._widescreen_y_shift + MENU_PANEL_OFFSET_Y * panel_scale
104
+ left_h = MENU_PANEL_HEIGHT * panel_scale
105
+ draw_classic_menu_panel(panel, dst=rl.Rectangle(left_x0, left_y0, panel_w, left_h), tint=rl.WHITE, shadow=fx_detail)
106
+
107
+ # Right (configured bindings) panel: tall 378px panel rendered as 3 vertical slices.
108
+ _angle_rad, slide_x = MenuView._ui_element_anim(
109
+ self,
110
+ index=3,
111
+ start_ms=PANEL_TIMELINE_START_MS,
112
+ end_ms=PANEL_TIMELINE_END_MS,
113
+ width=panel_w,
114
+ direction_flag=1,
115
+ )
116
+ right_x0 = CONTROLS_RIGHT_PANEL_POS_X + slide_x + MENU_PANEL_OFFSET_X * panel_scale
117
+ right_y0 = CONTROLS_RIGHT_PANEL_POS_Y + self._widescreen_y_shift + MENU_PANEL_OFFSET_Y * panel_scale
118
+ right_h = float(CONTROLS_RIGHT_PANEL_HEIGHT) * panel_scale
119
+ draw_classic_menu_panel(panel, dst=rl.Rectangle(right_x0, right_y0, panel_w, right_h), tint=rl.WHITE, shadow=fx_detail)
120
+
121
+ def _draw_contents(self) -> None:
122
+ # Positions are expressed relative to the panel top-left corners and scaled with the panel scale.
123
+ panel_scale, _local_y_shift = self._menu_item_scale(0)
124
+
125
+ left_x0 = self._panel_pos_x + MENU_PANEL_OFFSET_X * panel_scale
126
+ left_y0 = self._panel_pos_y + self._widescreen_y_shift + MENU_PANEL_OFFSET_Y * panel_scale
127
+ right_x0 = CONTROLS_RIGHT_PANEL_POS_X + MENU_PANEL_OFFSET_X * panel_scale
128
+ right_y0 = CONTROLS_RIGHT_PANEL_POS_Y + self._widescreen_y_shift + MENU_PANEL_OFFSET_Y * panel_scale
129
+
130
+ font = self._ensure_small_font()
131
+ text_color = rl.Color(255, 255, 255, 255)
132
+
133
+ # --- Left panel: "Configure for" + method selectors (state_3 in trace) ---
134
+ if self._text_controls is not None:
135
+ MenuView._draw_ui_quad(
136
+ texture=self._text_controls,
137
+ src=rl.Rectangle(0.0, 0.0, float(self._text_controls.width), float(self._text_controls.height)),
138
+ dst=rl.Rectangle(left_x0 + 206.0 * panel_scale, left_y0 + 44.0 * panel_scale, 128.0 * panel_scale, 32.0 * panel_scale),
139
+ origin=rl.Vector2(0.0, 0.0),
140
+ rotation_deg=0.0,
141
+ tint=rl.WHITE,
142
+ )
143
+
144
+ draw_small_text(font, "Configure for:", left_x0 + 339.0 * panel_scale, left_y0 + 41.0 * panel_scale, 1.0 * panel_scale, text_color)
145
+ draw_small_text(font, f"Player {int(self._config_player)}", left_x0 + 344.0 * panel_scale, left_y0 + 57.0 * panel_scale, 1.0 * panel_scale, text_color)
146
+
147
+ draw_small_text(font, "Aiming method:", left_x0 + 213.0 * panel_scale, left_y0 + 86.0 * panel_scale, 1.0 * panel_scale, text_color)
148
+ draw_small_text(font, "Mouse", left_x0 + 218.0 * panel_scale, left_y0 + 103.0 * panel_scale, 1.0 * panel_scale, text_color)
149
+
150
+ draw_small_text(font, "Moving method:", left_x0 + 213.0 * panel_scale, left_y0 + 128.0 * panel_scale, 1.0 * panel_scale, text_color)
151
+ draw_small_text(font, "Static", left_x0 + 218.0 * panel_scale, left_y0 + 145.0 * panel_scale, 1.0 * panel_scale, text_color)
152
+
153
+ check_tex = self._check_on
154
+ if check_tex is not None:
155
+ MenuView._draw_ui_quad(
156
+ texture=check_tex,
157
+ src=rl.Rectangle(0.0, 0.0, float(check_tex.width), float(check_tex.height)),
158
+ dst=rl.Rectangle(left_x0 + 213.0 * panel_scale, left_y0 + 174.0 * panel_scale, 16.0 * panel_scale, 16.0 * panel_scale),
159
+ origin=rl.Vector2(0.0, 0.0),
160
+ rotation_deg=0.0,
161
+ tint=rl.WHITE,
162
+ )
163
+ draw_small_text(
164
+ font,
165
+ "Show direction arrow",
166
+ left_x0 + 235.0 * panel_scale,
167
+ left_y0 + 175.0 * panel_scale,
168
+ 1.0 * panel_scale,
169
+ text_color,
170
+ )
171
+
172
+ drop_tex = self._drop_off
173
+ if drop_tex is not None:
174
+ for ox, oy in (
175
+ (418.0, 56.0), # player dropdown
176
+ (336.0, 102.0), # aiming dropdown
177
+ (336.0, 144.0), # moving dropdown
178
+ ):
179
+ MenuView._draw_ui_quad(
180
+ texture=drop_tex,
181
+ src=rl.Rectangle(0.0, 0.0, float(drop_tex.width), float(drop_tex.height)),
182
+ dst=rl.Rectangle(left_x0 + ox * panel_scale, left_y0 + oy * panel_scale, 16.0 * panel_scale, 16.0 * panel_scale),
183
+ origin=rl.Vector2(0.0, 0.0),
184
+ rotation_deg=0.0,
185
+ tint=rl.WHITE,
186
+ )
187
+
188
+ # --- Right panel: configured bindings list ---
83
189
  config = self._state.config
84
190
  pick_perk_key = int(config.data.get("keybind_pick_perk", 0x101) or 0x101)
85
191
  reload_key = int(config.data.get("keybind_reload", 0x102) or 0x102)
@@ -89,44 +195,25 @@ class ControlsMenuView(PanelMenuView):
89
195
  keybinds = (0x11, 0x1F, 0x1E, 0x20, 0x100) + (0x17E,) * 11 + (0xC8, 0xD0, 0xCB, 0xCD, 0x9D)
90
196
 
91
197
  p1_up, p1_down, p1_left, p1_right, p1_fire = player_move_fire_binds(keybinds, 0)
92
- p2_up, p2_down, p2_left, p2_right, p2_fire = player_move_fire_binds(keybinds, 1)
93
-
94
- return [
95
- f"Level up: {input_code_name(pick_perk_key)} / Space / KeyPad+",
96
- f"Reload: {input_code_name(reload_key)}",
97
- "",
98
- "Player 1:",
99
- f" Up: {input_code_name(p1_up)}",
100
- f" Down: {input_code_name(p1_down)}",
101
- f" Left: {input_code_name(p1_left)}",
102
- f" Right: {input_code_name(p1_right)}",
103
- f" Fire: {input_code_name(p1_fire)}",
104
- "",
105
- "Player 2:",
106
- f" Up: {input_code_name(p2_up)}",
107
- f" Down: {input_code_name(p2_down)}",
108
- f" Left: {input_code_name(p2_left)}",
109
- f" Right: {input_code_name(p2_right)}",
110
- f" Fire: {input_code_name(p2_fire)}",
111
- ]
112
198
 
113
- def _draw_contents(self) -> None:
114
- layout = self._content_layout()
115
- base_x = layout["base_x"]
116
- base_y = layout["base_y"]
117
- label_x = layout["label_x"]
118
- scale = layout["scale"]
119
-
120
- font = self._ensure_small_font()
121
- title_scale = 1.2 * scale
122
- text_scale = 1.0 * scale
123
-
124
- title_color = rl.Color(255, 255, 255, 255)
125
- text_color = rl.Color(255, 255, 255, int(255 * 0.8))
126
-
127
- draw_small_text(font, "CONTROLS", base_x, base_y, title_scale, title_color)
128
- line_y = base_y + 44.0 * scale
129
- line_step = (font.cell_size + 4.0) * scale
130
- for line in self._lines:
131
- draw_small_text(font, line, label_x, line_y, text_scale, text_color)
132
- line_y += line_step
199
+ draw_small_text(font, "Configured controls", right_x0 + 120.0 * panel_scale, right_y0 + 38.0 * panel_scale, 1.0 * panel_scale, text_color)
200
+
201
+ draw_small_text(font, "Aiming", right_x0 + 44.0 * panel_scale, right_y0 + 64.0 * panel_scale, 1.0 * panel_scale, text_color)
202
+ draw_small_text(font, "Fire:", right_x0 + 52.0 * panel_scale, right_y0 + 82.0 * panel_scale, 1.0 * panel_scale, text_color)
203
+ draw_small_text(font, input_code_name(p1_fire), right_x0 + 180.0 * panel_scale, right_y0 + 82.0 * panel_scale, 1.0 * panel_scale, text_color)
204
+
205
+ draw_small_text(font, "Moving", right_x0 + 44.0 * panel_scale, right_y0 + 106.0 * panel_scale, 1.0 * panel_scale, text_color)
206
+ draw_small_text(font, "Move Up:", right_x0 + 52.0 * panel_scale, right_y0 + 124.0 * panel_scale, 1.0 * panel_scale, text_color)
207
+ draw_small_text(font, input_code_name(p1_up), right_x0 + 180.0 * panel_scale, right_y0 + 124.0 * panel_scale, 1.0 * panel_scale, text_color)
208
+ draw_small_text(font, "Move Down:", right_x0 + 52.0 * panel_scale, right_y0 + 140.0 * panel_scale, 1.0 * panel_scale, text_color)
209
+ draw_small_text(font, input_code_name(p1_down), right_x0 + 180.0 * panel_scale, right_y0 + 140.0 * panel_scale, 1.0 * panel_scale, text_color)
210
+ draw_small_text(font, "Move Left:", right_x0 + 52.0 * panel_scale, right_y0 + 156.0 * panel_scale, 1.0 * panel_scale, text_color)
211
+ draw_small_text(font, input_code_name(p1_left), right_x0 + 180.0 * panel_scale, right_y0 + 156.0 * panel_scale, 1.0 * panel_scale, text_color)
212
+ draw_small_text(font, "Move Right:", right_x0 + 52.0 * panel_scale, right_y0 + 172.0 * panel_scale, 1.0 * panel_scale, text_color)
213
+ draw_small_text(font, input_code_name(p1_right), right_x0 + 180.0 * panel_scale, right_y0 + 172.0 * panel_scale, 1.0 * panel_scale, text_color)
214
+
215
+ draw_small_text(font, "Misc", right_x0 + 44.0 * panel_scale, right_y0 + 196.0 * panel_scale, 1.0 * panel_scale, text_color)
216
+ draw_small_text(font, "Level Up:", right_x0 + 52.0 * panel_scale, right_y0 + 214.0 * panel_scale, 1.0 * panel_scale, text_color)
217
+ draw_small_text(font, input_code_name(pick_perk_key), right_x0 + 180.0 * panel_scale, right_y0 + 214.0 * panel_scale, 1.0 * panel_scale, text_color)
218
+ draw_small_text(font, "Reload:", right_x0 + 52.0 * panel_scale, right_y0 + 230.0 * panel_scale, 1.0 * panel_scale, text_color)
219
+ draw_small_text(font, input_code_name(reload_key), right_x0 + 180.0 * panel_scale, right_y0 + 230.0 * panel_scale, 1.0 * panel_scale, text_color)
@@ -0,0 +1,221 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import pyray as rl
6
+
7
+ from grim.audio import play_sfx, update_audio
8
+ from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
9
+
10
+ from ...ui.menu_panel import draw_classic_menu_panel
11
+ from ...ui.perk_menu import UiButtonState, UiButtonTextureSet, button_draw, button_update, button_width
12
+ from ..assets import MenuAssets, _ensure_texture_cache, load_menu_assets
13
+ from ..menu import (
14
+ MENU_PANEL_OFFSET_X,
15
+ MENU_PANEL_OFFSET_Y,
16
+ MENU_PANEL_WIDTH,
17
+ MENU_SCALE_SMALL_THRESHOLD,
18
+ MENU_SIGN_HEIGHT,
19
+ MENU_SIGN_OFFSET_X,
20
+ MENU_SIGN_OFFSET_Y,
21
+ MENU_SIGN_POS_X_PAD,
22
+ MENU_SIGN_POS_Y,
23
+ MENU_SIGN_POS_Y_SMALL,
24
+ MENU_SIGN_WIDTH,
25
+ UI_SHADOW_OFFSET,
26
+ MenuView,
27
+ _draw_menu_cursor,
28
+ ensure_menu_ground,
29
+ )
30
+ from ..transitions import _draw_screen_fade
31
+
32
+ if TYPE_CHECKING:
33
+ from ...game import GameState
34
+ from grim.terrain_render import GroundRenderer
35
+
36
+
37
+ # Measured from ui_render_trace_oracle_1024x768.json (state_17:credits, timeline=300).
38
+ CREDITS_PANEL_POS_X = -119.0
39
+ CREDITS_PANEL_POS_Y = 185.0
40
+ CREDITS_PANEL_HEIGHT = 378.0
41
+
42
+ # Child layout inside the panel (relative to panel top-left).
43
+ _TITLE_X = 202.0
44
+ _TITLE_Y = 46.0
45
+
46
+ _BACK_BUTTON_X = 298.0
47
+ _BACK_BUTTON_Y = 310.0
48
+
49
+
50
+ class CreditsView:
51
+ def __init__(self, state: GameState) -> None:
52
+ self._state = state
53
+ self._assets: MenuAssets | None = None
54
+ self._ground: GroundRenderer | None = None
55
+ self._small_font: SmallFontData | None = None
56
+ self._button_textures: UiButtonTextureSet | None = None
57
+
58
+ self._cursor_pulse_time = 0.0
59
+ self._widescreen_y_shift = 0.0
60
+ self._action: str | None = None
61
+
62
+ self._back_button = UiButtonState("Back", force_wide=False)
63
+
64
+ def open(self) -> None:
65
+ layout_w = float(self._state.config.screen_width)
66
+ self._widescreen_y_shift = MenuView._menu_widescreen_y_shift(layout_w)
67
+ self._assets = load_menu_assets(self._state)
68
+ self._ground = None if self._state.pause_background is not None else ensure_menu_ground(self._state)
69
+ self._small_font = None
70
+ self._cursor_pulse_time = 0.0
71
+ self._action = None
72
+
73
+ cache = _ensure_texture_cache(self._state)
74
+ button_md = cache.get_or_load("ui_buttonMd", "ui/ui_button_128x32.jaz").texture
75
+ button_sm = cache.get_or_load("ui_buttonSm", "ui/ui_button_64x32.jaz").texture
76
+ self._button_textures = UiButtonTextureSet(button_sm=button_sm, button_md=button_md)
77
+ self._back_button = UiButtonState("Back", force_wide=False)
78
+
79
+ if self._state.audio is not None:
80
+ play_sfx(self._state.audio, "sfx_ui_panelclick", rng=self._state.rng)
81
+
82
+ def close(self) -> None:
83
+ if self._small_font is not None:
84
+ rl.unload_texture(self._small_font.texture)
85
+ self._small_font = None
86
+ self._button_textures = None
87
+ self._assets = None
88
+ self._ground = None
89
+ self._action = None
90
+
91
+ def take_action(self) -> str | None:
92
+ action = self._action
93
+ self._action = None
94
+ return action
95
+
96
+ def _ensure_small_font(self) -> SmallFontData:
97
+ if self._small_font is not None:
98
+ return self._small_font
99
+ missing_assets: list[str] = []
100
+ self._small_font = load_small_font(self._state.assets_dir, missing_assets)
101
+ return self._small_font
102
+
103
+ def _panel_top_left(self, *, scale: float) -> tuple[float, float]:
104
+ x0 = CREDITS_PANEL_POS_X + MENU_PANEL_OFFSET_X * scale
105
+ y0 = CREDITS_PANEL_POS_Y + self._widescreen_y_shift + MENU_PANEL_OFFSET_Y * scale
106
+ return float(x0), float(y0)
107
+
108
+ def update(self, dt: float) -> None:
109
+ if self._state.audio is not None:
110
+ update_audio(self._state.audio, dt)
111
+ if self._ground is not None:
112
+ self._ground.process_pending()
113
+ self._cursor_pulse_time += min(float(dt), 0.1) * 1.1
114
+
115
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
116
+ if self._state.audio is not None:
117
+ play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
118
+ self._action = "back_to_previous"
119
+ return
120
+
121
+ textures = self._button_textures
122
+ if textures is None or (textures.button_md is None and textures.button_sm is None):
123
+ return
124
+
125
+ scale = 0.9 if float(self._state.config.screen_width) < 641.0 else 1.0
126
+ panel_x0, panel_y0 = self._panel_top_left(scale=scale)
127
+
128
+ mouse = rl.get_mouse_position()
129
+ click = rl.is_mouse_button_pressed(rl.MOUSE_BUTTON_LEFT)
130
+ dt_ms = min(float(dt), 0.1) * 1000.0
131
+
132
+ w = button_width(None, self._back_button.label, scale=scale, force_wide=self._back_button.force_wide)
133
+ if button_update(
134
+ self._back_button,
135
+ x=panel_x0 + _BACK_BUTTON_X * scale,
136
+ y=panel_y0 + _BACK_BUTTON_Y * scale,
137
+ width=w,
138
+ dt_ms=dt_ms,
139
+ mouse=mouse,
140
+ click=click,
141
+ ):
142
+ if self._state.audio is not None:
143
+ play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
144
+ self._action = "back_to_previous"
145
+
146
+ def draw(self) -> None:
147
+ rl.clear_background(rl.BLACK)
148
+ pause_background = self._state.pause_background
149
+ if pause_background is not None:
150
+ pause_background.draw_pause_background()
151
+ elif self._ground is not None:
152
+ self._ground.draw(0.0, 0.0)
153
+ _draw_screen_fade(self._state)
154
+
155
+ assets = self._assets
156
+ if assets is None or assets.panel is None:
157
+ return
158
+
159
+ scale = 0.9 if float(self._state.config.screen_width) < 641.0 else 1.0
160
+ panel_x0, panel_y0 = self._panel_top_left(scale=scale)
161
+ dst = rl.Rectangle(panel_x0, panel_y0, MENU_PANEL_WIDTH * scale, CREDITS_PANEL_HEIGHT * scale)
162
+ fx_detail = bool(self._state.config.data.get("fx_detail_0", 0))
163
+ draw_classic_menu_panel(assets.panel, dst=dst, tint=rl.WHITE, shadow=fx_detail)
164
+
165
+ font = self._ensure_small_font()
166
+ draw_small_text(
167
+ font,
168
+ "credits",
169
+ panel_x0 + _TITLE_X * scale,
170
+ panel_y0 + _TITLE_Y * scale,
171
+ 1.0 * scale,
172
+ rl.Color(255, 255, 255, 255),
173
+ )
174
+
175
+ textures = self._button_textures
176
+ if textures is not None and (textures.button_md is not None or textures.button_sm is not None):
177
+ back_w = button_width(None, self._back_button.label, scale=scale, force_wide=self._back_button.force_wide)
178
+ button_draw(
179
+ textures,
180
+ font,
181
+ self._back_button,
182
+ x=panel_x0 + _BACK_BUTTON_X * scale,
183
+ y=panel_y0 + _BACK_BUTTON_Y * scale,
184
+ width=back_w,
185
+ scale=scale,
186
+ )
187
+
188
+ self._draw_sign()
189
+ _draw_menu_cursor(self._state, pulse_time=self._cursor_pulse_time)
190
+
191
+ def _draw_sign(self) -> None:
192
+ assets = self._assets
193
+ if assets is None or assets.sign is None:
194
+ return
195
+ sign = assets.sign
196
+ screen_w = float(self._state.config.screen_width)
197
+ sign_scale, shift_x = MenuView._sign_layout_scale(int(screen_w))
198
+ pos_x = screen_w + MENU_SIGN_POS_X_PAD
199
+ pos_y = MENU_SIGN_POS_Y if screen_w > MENU_SCALE_SMALL_THRESHOLD else MENU_SIGN_POS_Y_SMALL
200
+ sign_w = MENU_SIGN_WIDTH * sign_scale
201
+ sign_h = MENU_SIGN_HEIGHT * sign_scale
202
+ offset_x = MENU_SIGN_OFFSET_X * sign_scale + shift_x
203
+ offset_y = MENU_SIGN_OFFSET_Y * sign_scale
204
+ rotation_deg = 0.0
205
+ fx_detail = bool(self._state.config.data.get("fx_detail_0", 0))
206
+ if fx_detail:
207
+ MenuView._draw_ui_quad_shadow(
208
+ texture=sign,
209
+ src=rl.Rectangle(0.0, 0.0, float(sign.width), float(sign.height)),
210
+ dst=rl.Rectangle(pos_x + UI_SHADOW_OFFSET, pos_y + UI_SHADOW_OFFSET, sign_w, sign_h),
211
+ origin=rl.Vector2(-offset_x, -offset_y),
212
+ rotation_deg=rotation_deg,
213
+ )
214
+ MenuView._draw_ui_quad(
215
+ texture=sign,
216
+ src=rl.Rectangle(0.0, 0.0, float(sign.width), float(sign.height)),
217
+ dst=rl.Rectangle(pos_x, pos_y, sign_w, sign_h),
218
+ origin=rl.Vector2(-offset_x, -offset_y),
219
+ rotation_deg=rotation_deg,
220
+ tint=rl.WHITE,
221
+ )