crimsonland 0.1.0.dev12__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.
@@ -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
+ )
@@ -0,0 +1,307 @@
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
+ from .base import PANEL_TIMELINE_END_MS, PANEL_TIMELINE_START_MS
32
+
33
+ if TYPE_CHECKING:
34
+ from ...game import GameState
35
+ from grim.terrain_render import GroundRenderer
36
+
37
+
38
+ # Shared panel layout (state_14/15/16 in the oracle): tall left panel + short right panel.
39
+ LEFT_PANEL_POS_X = -119.0
40
+ LEFT_PANEL_POS_Y = 185.0
41
+ LEFT_PANEL_HEIGHT = 378.0
42
+
43
+ RIGHT_PANEL_POS_X = 609.0
44
+ RIGHT_PANEL_POS_Y = 200.0
45
+ RIGHT_PANEL_HEIGHT = 254.0
46
+
47
+
48
+ class _DatabaseBaseView:
49
+ def __init__(self, state: GameState) -> None:
50
+ self._state = state
51
+ self._assets: MenuAssets | None = None
52
+ self._ground: GroundRenderer | None = None
53
+ self._small_font: SmallFontData | None = None
54
+ self._button_textures: UiButtonTextureSet | None = None
55
+
56
+ self._cursor_pulse_time = 0.0
57
+ self._widescreen_y_shift = 0.0
58
+ self._timeline_ms = 0
59
+ self._timeline_max_ms = PANEL_TIMELINE_START_MS
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._timeline_ms = 0
72
+ self._timeline_max_ms = PANEL_TIMELINE_START_MS
73
+ self._action = None
74
+
75
+ cache = _ensure_texture_cache(self._state)
76
+ button_md = cache.get_or_load("ui_buttonMd", "ui/ui_button_128x32.jaz").texture
77
+ button_sm = cache.get_or_load("ui_buttonSm", "ui/ui_button_64x32.jaz").texture
78
+ self._button_textures = UiButtonTextureSet(button_sm=button_sm, button_md=button_md)
79
+ self._back_button = UiButtonState("Back", force_wide=False)
80
+
81
+ if self._state.audio is not None:
82
+ play_sfx(self._state.audio, "sfx_ui_panelclick", rng=self._state.rng)
83
+
84
+ def close(self) -> None:
85
+ if self._small_font is not None:
86
+ rl.unload_texture(self._small_font.texture)
87
+ self._small_font = None
88
+ self._button_textures = None
89
+ self._assets = None
90
+ self._ground = None
91
+ self._action = None
92
+
93
+ def take_action(self) -> str | None:
94
+ action = self._action
95
+ self._action = None
96
+ return action
97
+
98
+ def _ensure_small_font(self) -> SmallFontData:
99
+ if self._small_font is not None:
100
+ return self._small_font
101
+ missing_assets: list[str] = []
102
+ self._small_font = load_small_font(self._state.assets_dir, missing_assets)
103
+ return self._small_font
104
+
105
+ def _panel_top_left(self, *, pos_x: float, pos_y: float, scale: float) -> tuple[float, float]:
106
+ x0 = pos_x + MENU_PANEL_OFFSET_X * scale
107
+ y0 = pos_y + self._widescreen_y_shift + MENU_PANEL_OFFSET_Y * scale
108
+ return float(x0), float(y0)
109
+
110
+ def _draw_sign(self) -> None:
111
+ assets = self._assets
112
+ if assets is None or assets.sign is None:
113
+ return
114
+ sign = assets.sign
115
+ screen_w = float(self._state.config.screen_width)
116
+ sign_scale, shift_x = MenuView._sign_layout_scale(int(screen_w))
117
+ pos_x = screen_w + MENU_SIGN_POS_X_PAD
118
+ pos_y = MENU_SIGN_POS_Y if screen_w > MENU_SCALE_SMALL_THRESHOLD else MENU_SIGN_POS_Y_SMALL
119
+ sign_w = MENU_SIGN_WIDTH * sign_scale
120
+ sign_h = MENU_SIGN_HEIGHT * sign_scale
121
+ offset_x = MENU_SIGN_OFFSET_X * sign_scale + shift_x
122
+ offset_y = MENU_SIGN_OFFSET_Y * sign_scale
123
+ rotation_deg = 0.0
124
+ fx_detail = bool(self._state.config.data.get("fx_detail_0", 0))
125
+ if fx_detail:
126
+ MenuView._draw_ui_quad_shadow(
127
+ texture=sign,
128
+ src=rl.Rectangle(0.0, 0.0, float(sign.width), float(sign.height)),
129
+ dst=rl.Rectangle(pos_x + UI_SHADOW_OFFSET, pos_y + UI_SHADOW_OFFSET, sign_w, sign_h),
130
+ origin=rl.Vector2(-offset_x, -offset_y),
131
+ rotation_deg=rotation_deg,
132
+ )
133
+ MenuView._draw_ui_quad(
134
+ texture=sign,
135
+ src=rl.Rectangle(0.0, 0.0, float(sign.width), float(sign.height)),
136
+ dst=rl.Rectangle(pos_x, pos_y, sign_w, sign_h),
137
+ origin=rl.Vector2(-offset_x, -offset_y),
138
+ rotation_deg=rotation_deg,
139
+ tint=rl.WHITE,
140
+ )
141
+
142
+ def update(self, dt: float) -> None:
143
+ if self._state.audio is not None:
144
+ update_audio(self._state.audio, dt)
145
+ if self._ground is not None:
146
+ self._ground.process_pending()
147
+ self._cursor_pulse_time += min(float(dt), 0.1) * 1.1
148
+
149
+ dt_ms = int(min(float(dt), 0.1) * 1000.0)
150
+ if dt_ms > 0:
151
+ self._timeline_ms = min(self._timeline_max_ms, int(self._timeline_ms + dt_ms))
152
+
153
+ enabled = self._timeline_ms >= self._timeline_max_ms
154
+
155
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE) and enabled:
156
+ if self._state.audio is not None:
157
+ play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
158
+ self._action = "back_to_previous"
159
+ return
160
+
161
+ textures = self._button_textures
162
+ if textures is None or (textures.button_md is None and textures.button_sm is None):
163
+ return
164
+ if not enabled:
165
+ return
166
+
167
+ scale = 0.9 if float(self._state.config.screen_width) < 641.0 else 1.0
168
+ left_x0, left_y0 = self._panel_top_left(pos_x=LEFT_PANEL_POS_X, pos_y=LEFT_PANEL_POS_Y, scale=scale)
169
+
170
+ mouse = rl.get_mouse_position()
171
+ click = rl.is_mouse_button_pressed(rl.MOUSE_BUTTON_LEFT)
172
+
173
+ bx, by = self._back_button_pos()
174
+ back_w = button_width(None, self._back_button.label, scale=scale, force_wide=self._back_button.force_wide)
175
+ if button_update(
176
+ self._back_button,
177
+ x=left_x0 + float(bx) * scale,
178
+ y=left_y0 + float(by) * scale,
179
+ width=back_w,
180
+ dt_ms=dt_ms,
181
+ mouse=mouse,
182
+ click=click,
183
+ ):
184
+ if self._state.audio is not None:
185
+ play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
186
+ self._action = "back_to_previous"
187
+
188
+ def draw(self) -> None:
189
+ rl.clear_background(rl.BLACK)
190
+ pause_background = self._state.pause_background
191
+ if pause_background is not None:
192
+ pause_background.draw_pause_background()
193
+ elif self._ground is not None:
194
+ self._ground.draw(0.0, 0.0)
195
+ _draw_screen_fade(self._state)
196
+
197
+ assets = self._assets
198
+ if assets is None or assets.panel is None:
199
+ return
200
+
201
+ scale = 0.9 if float(self._state.config.screen_width) < 641.0 else 1.0
202
+ fx_detail = bool(self._state.config.data.get("fx_detail_0", 0))
203
+
204
+ panel_w = MENU_PANEL_WIDTH * scale
205
+ _angle_rad, left_slide_x = MenuView._ui_element_anim(
206
+ self,
207
+ index=1,
208
+ start_ms=PANEL_TIMELINE_START_MS,
209
+ end_ms=PANEL_TIMELINE_END_MS,
210
+ width=panel_w,
211
+ direction_flag=0,
212
+ )
213
+ _angle_rad, right_slide_x = MenuView._ui_element_anim(
214
+ self,
215
+ index=2,
216
+ start_ms=PANEL_TIMELINE_START_MS,
217
+ end_ms=PANEL_TIMELINE_END_MS,
218
+ width=panel_w,
219
+ direction_flag=1,
220
+ )
221
+
222
+ left_x0, left_y0 = self._panel_top_left(pos_x=LEFT_PANEL_POS_X, pos_y=LEFT_PANEL_POS_Y, scale=scale)
223
+ right_x0, right_y0 = self._panel_top_left(pos_x=RIGHT_PANEL_POS_X, pos_y=RIGHT_PANEL_POS_Y, scale=scale)
224
+ left_x0 += float(left_slide_x)
225
+ right_x0 += float(right_slide_x)
226
+
227
+ draw_classic_menu_panel(
228
+ assets.panel,
229
+ dst=rl.Rectangle(left_x0, left_y0, panel_w, LEFT_PANEL_HEIGHT * scale),
230
+ tint=rl.WHITE,
231
+ shadow=fx_detail,
232
+ )
233
+ draw_classic_menu_panel(
234
+ assets.panel,
235
+ dst=rl.Rectangle(right_x0, right_y0, panel_w, RIGHT_PANEL_HEIGHT * scale),
236
+ tint=rl.WHITE,
237
+ shadow=fx_detail,
238
+ )
239
+
240
+ font = self._ensure_small_font()
241
+ self._draw_contents(left_x0, left_y0, right_x0, right_y0, scale=scale, font=font)
242
+
243
+ textures = self._button_textures
244
+ if textures is not None and (textures.button_md is not None or textures.button_sm is not None):
245
+ bx, by = self._back_button_pos()
246
+ back_w = button_width(None, self._back_button.label, scale=scale, force_wide=self._back_button.force_wide)
247
+ button_draw(
248
+ textures,
249
+ font,
250
+ self._back_button,
251
+ x=left_x0 + float(bx) * scale,
252
+ y=left_y0 + float(by) * scale,
253
+ width=back_w,
254
+ scale=scale,
255
+ )
256
+
257
+ self._draw_sign()
258
+ _draw_menu_cursor(self._state, pulse_time=self._cursor_pulse_time)
259
+
260
+ def _back_button_pos(self) -> tuple[float, float]:
261
+ raise NotImplementedError
262
+
263
+ def _draw_contents(
264
+ self,
265
+ left_x0: float,
266
+ left_y0: float,
267
+ right_x0: float,
268
+ right_y0: float,
269
+ *,
270
+ scale: float,
271
+ font: SmallFontData,
272
+ ) -> None:
273
+ raise NotImplementedError
274
+
275
+
276
+ class UnlockedWeaponsDatabaseView(_DatabaseBaseView):
277
+ def _back_button_pos(self) -> tuple[float, float]:
278
+ # state_15: ui_buttonSm bbox [270,507]..[352,539] => relative to left panel (-98,194): (368, 313)
279
+ return (368.0, 313.0)
280
+
281
+ def _draw_contents(self, left_x0: float, left_y0: float, right_x0: float, right_y0: float, *, scale: float, font: SmallFontData) -> None:
282
+ # state_15 title at (153,244) => relative to left panel (-98,194): (251,50)
283
+ draw_small_text(
284
+ font,
285
+ "Unlocked Weapons Database",
286
+ left_x0 + 251.0 * scale,
287
+ left_y0 + 50.0 * scale,
288
+ 1.0 * scale,
289
+ rl.Color(255, 255, 255, 255),
290
+ )
291
+
292
+
293
+ class UnlockedPerksDatabaseView(_DatabaseBaseView):
294
+ def _back_button_pos(self) -> tuple[float, float]:
295
+ # state_16: ui_buttonSm bbox [258,509]..[340,541] => relative to left panel (-98,194): (356, 315)
296
+ return (356.0, 315.0)
297
+
298
+ def _draw_contents(self, left_x0: float, left_y0: float, right_x0: float, right_y0: float, *, scale: float, font: SmallFontData) -> None:
299
+ # state_16 title at (163,244) => relative to left panel (-98,194): (261,50)
300
+ draw_small_text(
301
+ font,
302
+ "Unlocked Perks Database",
303
+ left_x0 + 261.0 * scale,
304
+ left_y0 + 50.0 * scale,
305
+ 1.0 * scale,
306
+ rl.Color(255, 255, 255, 255),
307
+ )
@@ -85,6 +85,7 @@ class OptionsMenuView(PanelMenuView):
85
85
 
86
86
  config = self._state.config
87
87
  layout = self._content_layout()
88
+ base_x = layout["base_x"]
88
89
  label_x = layout["label_x"]
89
90
  base_y = layout["base_y"]
90
91
  scale = layout["scale"]
@@ -126,8 +127,8 @@ class OptionsMenuView(PanelMenuView):
126
127
 
127
128
  textures = self._button_textures
128
129
  if textures is not None and textures.button_md is not None:
129
- # `sub_4475d0`: controls button shares the slider column.
130
- x = slider_x
130
+ # `sub_4475d0`: controls button is aligned with the panel content base.
131
+ x = base_x
131
132
  y = base_y + 155.0 * scale
132
133
  dt_ms = min(float(dt), 0.1) * 1000.0
133
134
  mouse = rl.get_mouse_position()
@@ -373,7 +374,7 @@ class OptionsMenuView(PanelMenuView):
373
374
  button = self._button_tex
374
375
  textures = self._button_textures
375
376
  if button is not None and textures is not None:
376
- button_x = slider_x
377
+ button_x = base_x
377
378
  button_y = base_y + 155.0 * scale
378
379
  button_w = button_width(font, self._controls_button.label, scale=scale, force_wide=self._controls_button.force_wide)
379
380
  button_draw(textures, font, self._controls_button, x=button_x, y=button_y, width=button_w, scale=scale)
@@ -15,8 +15,6 @@ from ..menu import (
15
15
  MENU_LABEL_ROW_HEIGHT,
16
16
  MENU_LABEL_ROW_PLAY_GAME,
17
17
  MENU_LABEL_WIDTH,
18
- MENU_PANEL_OFFSET_X,
19
- MENU_PANEL_OFFSET_Y,
20
18
  MENU_PANEL_WIDTH,
21
19
  MenuView,
22
20
  _draw_menu_cursor,
@@ -50,6 +48,8 @@ class PlayGameMenuView(PanelMenuView):
50
48
  super().__init__(
51
49
  state,
52
50
  title="Play Game",
51
+ panel_offset_x=-63.0,
52
+ panel_height=278.0,
53
53
  back_pos_y=462.0,
54
54
  )
55
55
  self._small_font: SmallFontData | None = None
@@ -211,8 +211,8 @@ class PlayGameMenuView(PanelMenuView):
211
211
  )
212
212
  panel_x = self._panel_pos_x + slide_x
213
213
  panel_y = self._panel_pos_y + self._widescreen_y_shift
214
- origin_x = -(MENU_PANEL_OFFSET_X * panel_scale)
215
- origin_y = -(MENU_PANEL_OFFSET_Y * panel_scale)
214
+ origin_x = -(self._panel_offset_x * panel_scale)
215
+ origin_y = -(self._panel_offset_y * panel_scale)
216
216
  panel_left = panel_x - origin_x
217
217
  panel_top = panel_y - origin_y
218
218