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.
- crimson/assets_fetch.py +23 -8
- crimson/creatures/runtime.py +15 -0
- crimson/demo.py +47 -38
- crimson/effects.py +46 -1
- crimson/frontend/boot.py +2 -1
- crimson/frontend/high_scores_layout.py +26 -0
- crimson/frontend/menu.py +24 -43
- crimson/frontend/panels/base.py +27 -29
- crimson/frontend/panels/controls.py +152 -65
- crimson/frontend/panels/credits.py +221 -0
- crimson/frontend/panels/databases.py +307 -0
- crimson/frontend/panels/mods.py +1 -3
- crimson/frontend/panels/options.py +36 -42
- crimson/frontend/panels/play_game.py +82 -74
- crimson/frontend/panels/stats.py +255 -298
- crimson/frontend/pause_menu.py +425 -0
- crimson/game.py +512 -505
- crimson/gameplay.py +35 -6
- crimson/modes/base_gameplay_mode.py +3 -0
- crimson/modes/quest_mode.py +54 -44
- crimson/modes/rush_mode.py +4 -1
- crimson/modes/survival_mode.py +15 -10
- crimson/modes/tutorial_mode.py +15 -5
- crimson/modes/typo_mode.py +4 -1
- crimson/persistence/highscores.py +6 -2
- crimson/render/world_renderer.py +1 -1
- crimson/sim/world_state.py +8 -1
- crimson/typo/spawns.py +3 -4
- crimson/ui/demo_trial_overlay.py +3 -3
- crimson/ui/game_over.py +18 -2
- crimson/ui/menu_panel.py +127 -0
- crimson/ui/perk_menu.py +101 -44
- crimson/ui/quest_results.py +669 -0
- crimson/ui/shadow.py +39 -0
- crimson/views/particles.py +1 -1
- crimson/views/perk_menu_debug.py +2 -2
- crimson/views/perks.py +2 -2
- crimson/weapons.py +110 -110
- {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev13.dist-info}/METADATA +1 -1
- {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev13.dist-info}/RECORD +43 -36
- grim/app.py +3 -0
- {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev13.dist-info}/WHEEL +0 -0
- {crimsonland-0.1.0.dev11.dist-info → crimsonland-0.1.0.dev13.dist-info}/entry_points.txt +0 -0
crimson/ui/menu_panel.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pyray as rl
|
|
4
|
+
|
|
5
|
+
from .shadow import UI_SHADOW_OFFSET, draw_ui_quad_shadow
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Classic menu panel is rendered from the *inset* inner region of ui_menuPanel:
|
|
9
|
+
# - X inset: 1px on each side (uv 1/512 .. 511/512) => 510px wide
|
|
10
|
+
# - Y inset: 1px on each side (uv 1/256 .. 255/256) => 254px tall
|
|
11
|
+
#
|
|
12
|
+
# When a panel is taller than the base height, the original stretches it using a
|
|
13
|
+
# 3-slice: [top][mid][bottom]. The source slice boundaries are at y=130 and y=150
|
|
14
|
+
# in the texture (see grim UVs in ui_render_trace).
|
|
15
|
+
MENU_PANEL_INSET = 1.0
|
|
16
|
+
MENU_PANEL_SRC_SLICE_Y1 = 130.0
|
|
17
|
+
MENU_PANEL_SRC_SLICE_Y2 = 150.0
|
|
18
|
+
|
|
19
|
+
# Destination slice heights observed in the original at scale=1.0 (1024x768).
|
|
20
|
+
MENU_PANEL_DST_TOP_H = 138.0
|
|
21
|
+
MENU_PANEL_DST_BOTTOM_H = 116.0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def draw_classic_menu_panel(
|
|
25
|
+
texture: rl.Texture2D,
|
|
26
|
+
*,
|
|
27
|
+
dst: rl.Rectangle,
|
|
28
|
+
tint: rl.Color = rl.WHITE,
|
|
29
|
+
shadow: bool = False,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Draw a classic menu panel (ui_menuPanel) with the same slicing behavior as the original.
|
|
33
|
+
|
|
34
|
+
- Uses inset source rect (1px border skipped) to match the vertex/UV inset.
|
|
35
|
+
- Uses 3-slice only when dst is taller than (top + bottom); otherwise draws a single quad.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
tex_w = float(texture.width)
|
|
39
|
+
tex_h = float(texture.height)
|
|
40
|
+
if tex_w <= 0.0 or tex_h <= 0.0:
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
inset = MENU_PANEL_INSET
|
|
44
|
+
src_x = inset
|
|
45
|
+
src_y = inset
|
|
46
|
+
src_w = max(0.0, tex_w - inset * 2.0)
|
|
47
|
+
src_h = max(0.0, tex_h - inset * 2.0)
|
|
48
|
+
|
|
49
|
+
# Scale slice heights with the panel width (menu panel uses the same scale factor).
|
|
50
|
+
# dst.width is already in our "inset" width space (510 at scale=1.0).
|
|
51
|
+
scale = (float(dst.width) / 510.0) if float(dst.width) != 0.0 else 1.0
|
|
52
|
+
top_h = MENU_PANEL_DST_TOP_H * scale
|
|
53
|
+
bottom_h = MENU_PANEL_DST_BOTTOM_H * scale
|
|
54
|
+
mid_h = float(dst.height) - top_h - bottom_h
|
|
55
|
+
|
|
56
|
+
origin = rl.Vector2(0.0, 0.0)
|
|
57
|
+
|
|
58
|
+
if mid_h <= 0.0:
|
|
59
|
+
src = rl.Rectangle(src_x, src_y, src_w, src_h)
|
|
60
|
+
if shadow:
|
|
61
|
+
draw_ui_quad_shadow(
|
|
62
|
+
texture=texture,
|
|
63
|
+
src=src,
|
|
64
|
+
dst=rl.Rectangle(
|
|
65
|
+
float(dst.x + UI_SHADOW_OFFSET),
|
|
66
|
+
float(dst.y + UI_SHADOW_OFFSET),
|
|
67
|
+
float(dst.width),
|
|
68
|
+
float(dst.height),
|
|
69
|
+
),
|
|
70
|
+
origin=origin,
|
|
71
|
+
rotation_deg=0.0,
|
|
72
|
+
)
|
|
73
|
+
rl.draw_texture_pro(texture, src, dst, origin, 0.0, tint)
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
# Source slice rects (in texture pixels, with 1px inset).
|
|
77
|
+
src_top = rl.Rectangle(src_x, src_y, src_w, max(0.0, MENU_PANEL_SRC_SLICE_Y1 - inset))
|
|
78
|
+
src_mid = rl.Rectangle(src_x, MENU_PANEL_SRC_SLICE_Y1, src_w, max(0.0, MENU_PANEL_SRC_SLICE_Y2 - MENU_PANEL_SRC_SLICE_Y1))
|
|
79
|
+
src_bot = rl.Rectangle(src_x, MENU_PANEL_SRC_SLICE_Y2, src_w, max(0.0, (tex_h - inset) - MENU_PANEL_SRC_SLICE_Y2))
|
|
80
|
+
|
|
81
|
+
# Destination slices.
|
|
82
|
+
dst_top = rl.Rectangle(float(dst.x), float(dst.y), float(dst.width), float(top_h))
|
|
83
|
+
dst_mid = rl.Rectangle(float(dst.x), float(dst.y) + float(top_h), float(dst.width), float(mid_h))
|
|
84
|
+
dst_bot = rl.Rectangle(float(dst.x), float(dst.y) + float(top_h) + float(mid_h), float(dst.width), float(bottom_h))
|
|
85
|
+
|
|
86
|
+
if shadow:
|
|
87
|
+
draw_ui_quad_shadow(
|
|
88
|
+
texture=texture,
|
|
89
|
+
src=src_top,
|
|
90
|
+
dst=rl.Rectangle(
|
|
91
|
+
float(dst_top.x + UI_SHADOW_OFFSET),
|
|
92
|
+
float(dst_top.y + UI_SHADOW_OFFSET),
|
|
93
|
+
float(dst_top.width),
|
|
94
|
+
float(dst_top.height),
|
|
95
|
+
),
|
|
96
|
+
origin=origin,
|
|
97
|
+
rotation_deg=0.0,
|
|
98
|
+
)
|
|
99
|
+
draw_ui_quad_shadow(
|
|
100
|
+
texture=texture,
|
|
101
|
+
src=src_mid,
|
|
102
|
+
dst=rl.Rectangle(
|
|
103
|
+
float(dst_mid.x + UI_SHADOW_OFFSET),
|
|
104
|
+
float(dst_mid.y + UI_SHADOW_OFFSET),
|
|
105
|
+
float(dst_mid.width),
|
|
106
|
+
float(dst_mid.height),
|
|
107
|
+
),
|
|
108
|
+
origin=origin,
|
|
109
|
+
rotation_deg=0.0,
|
|
110
|
+
)
|
|
111
|
+
draw_ui_quad_shadow(
|
|
112
|
+
texture=texture,
|
|
113
|
+
src=src_bot,
|
|
114
|
+
dst=rl.Rectangle(
|
|
115
|
+
float(dst_bot.x + UI_SHADOW_OFFSET),
|
|
116
|
+
float(dst_bot.y + UI_SHADOW_OFFSET),
|
|
117
|
+
float(dst_bot.width),
|
|
118
|
+
float(dst_bot.height),
|
|
119
|
+
),
|
|
120
|
+
origin=origin,
|
|
121
|
+
rotation_deg=0.0,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
rl.draw_texture_pro(texture, src_top, dst_top, origin, 0.0, tint)
|
|
125
|
+
rl.draw_texture_pro(texture, src_mid, dst_mid, origin, 0.0, tint)
|
|
126
|
+
rl.draw_texture_pro(texture, src_bot, dst_bot, origin, 0.0, tint)
|
|
127
|
+
|
crimson/ui/perk_menu.py
CHANGED
|
@@ -2,19 +2,26 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import Protocol
|
|
5
6
|
|
|
6
7
|
import pyray as rl
|
|
7
8
|
|
|
8
9
|
from grim.assets import TextureLoader
|
|
9
10
|
from grim.fonts.small import SmallFontData, draw_small_text, measure_small_text_width
|
|
10
11
|
|
|
12
|
+
from .menu_panel import draw_classic_menu_panel
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
UI_BASE_WIDTH = 640.0
|
|
13
16
|
UI_BASE_HEIGHT = 480.0
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
# Perk selection screen panel uses ui_element-style timeline animation:
|
|
19
|
+
# - fully hidden until end_ms
|
|
20
|
+
# - slides in over (end_ms..start_ms)
|
|
21
|
+
# - fully visible at start_ms
|
|
22
|
+
PERK_MENU_ANIM_START_MS = 400.0
|
|
23
|
+
PERK_MENU_ANIM_END_MS = 100.0
|
|
24
|
+
PERK_MENU_TRANSITION_MS = PERK_MENU_ANIM_START_MS
|
|
18
25
|
|
|
19
26
|
# Layout offsets from the classic game (perk selection screen), derived from
|
|
20
27
|
# `perk_selection_screen_update` (see analysis/ghidra + BN).
|
|
@@ -42,11 +49,13 @@ MENU_DESC_RIGHT_X = 480.0
|
|
|
42
49
|
@dataclass(slots=True)
|
|
43
50
|
class PerkMenuLayout:
|
|
44
51
|
# Coordinates live in the original 640x480 UI space.
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
# Capture (1024x768) shows the perk menu panel uses the 3-slice variant:
|
|
53
|
+
# open bbox (-108,119) -> (402,497)
|
|
54
|
+
# which corresponds to ui_element pos (-45,110) + geom (-63,-81) and size 510x378.
|
|
55
|
+
panel_x: float = -108.0
|
|
56
|
+
panel_y: float = 29.0
|
|
57
|
+
panel_w: float = 510.0
|
|
58
|
+
panel_h: float = 378.0
|
|
50
59
|
|
|
51
60
|
|
|
52
61
|
@dataclass(slots=True)
|
|
@@ -142,32 +151,47 @@ def perk_menu_compute_layout(
|
|
|
142
151
|
cancel_y=float(cancel_y),
|
|
143
152
|
)
|
|
144
153
|
|
|
154
|
+
def ui_element_slide_x(
|
|
155
|
+
t_ms: float,
|
|
156
|
+
*,
|
|
157
|
+
start_ms: float,
|
|
158
|
+
end_ms: float,
|
|
159
|
+
width: float,
|
|
160
|
+
direction_flag: int = 0,
|
|
161
|
+
) -> float:
|
|
162
|
+
"""
|
|
163
|
+
Slide offset helper matching ui_element_update semantics (see MenuView._ui_element_anim).
|
|
164
|
+
|
|
165
|
+
direction_flag=0: slide from left (-width -> 0)
|
|
166
|
+
direction_flag=1: slide from right (+width -> 0)
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
if start_ms <= end_ms or width <= 0.0:
|
|
170
|
+
return 0.0
|
|
171
|
+
|
|
172
|
+
width = abs(float(width))
|
|
173
|
+
t = float(t_ms)
|
|
174
|
+
if t < float(end_ms):
|
|
175
|
+
slide = width
|
|
176
|
+
elif t < float(start_ms):
|
|
177
|
+
elapsed = t - float(end_ms)
|
|
178
|
+
span = float(start_ms) - float(end_ms)
|
|
179
|
+
p = elapsed / span if span > 1e-6 else 1.0
|
|
180
|
+
slide = (1.0 - p) * width
|
|
181
|
+
else:
|
|
182
|
+
slide = 0.0
|
|
145
183
|
|
|
146
|
-
|
|
147
|
-
scale = float(dst.width) / float(texture.width)
|
|
148
|
-
top_h = MENU_PANEL_SLICE_Y1 * scale
|
|
149
|
-
bottom_h = (float(texture.height) - MENU_PANEL_SLICE_Y2) * scale
|
|
150
|
-
mid_h = float(dst.height) - top_h - bottom_h
|
|
151
|
-
if mid_h < 0.0:
|
|
152
|
-
src = rl.Rectangle(0.0, 0.0, float(texture.width), float(texture.height))
|
|
153
|
-
rl.draw_texture_pro(texture, src, dst, rl.Vector2(0.0, 0.0), 0.0, tint)
|
|
154
|
-
return
|
|
155
|
-
|
|
156
|
-
src_w = float(texture.width)
|
|
157
|
-
src_h = float(texture.height)
|
|
158
|
-
|
|
159
|
-
src_top = rl.Rectangle(0.0, 0.0, src_w, MENU_PANEL_SLICE_Y1)
|
|
160
|
-
src_mid = rl.Rectangle(0.0, MENU_PANEL_SLICE_Y1, src_w, MENU_PANEL_SLICE_Y2 - MENU_PANEL_SLICE_Y1)
|
|
161
|
-
src_bot = rl.Rectangle(0.0, MENU_PANEL_SLICE_Y2, src_w, src_h - MENU_PANEL_SLICE_Y2)
|
|
184
|
+
return slide if int(direction_flag) else -slide
|
|
162
185
|
|
|
163
|
-
dst_top = rl.Rectangle(float(dst.x), float(dst.y), float(dst.width), top_h)
|
|
164
|
-
dst_mid = rl.Rectangle(float(dst.x), float(dst.y) + top_h, float(dst.width), mid_h)
|
|
165
|
-
dst_bot = rl.Rectangle(float(dst.x), float(dst.y) + top_h + mid_h, float(dst.width), bottom_h)
|
|
166
186
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
187
|
+
def perk_menu_panel_slide_x(t_ms: float, *, width: float) -> float:
|
|
188
|
+
return ui_element_slide_x(
|
|
189
|
+
t_ms,
|
|
190
|
+
start_ms=PERK_MENU_ANIM_START_MS,
|
|
191
|
+
end_ms=PERK_MENU_ANIM_END_MS,
|
|
192
|
+
width=width,
|
|
193
|
+
direction_flag=0,
|
|
194
|
+
)
|
|
171
195
|
|
|
172
196
|
|
|
173
197
|
@dataclass(slots=True)
|
|
@@ -198,11 +222,11 @@ def load_perk_menu_assets(assets_root: Path) -> PerkMenuAssets:
|
|
|
198
222
|
fs_rel="ui/ui_textLevelUp.png",
|
|
199
223
|
),
|
|
200
224
|
menu_item=loader.get(name="ui_menuItem", paq_rel="ui/ui_menuItem.jaz", fs_rel="ui/ui_menuItem.png"),
|
|
201
|
-
button_sm=loader.get(name="ui_buttonSm", paq_rel="ui/
|
|
225
|
+
button_sm=loader.get(name="ui_buttonSm", paq_rel="ui/ui_button_64x32.jaz", fs_rel="ui/ui_button_64x32.png"),
|
|
202
226
|
button_md=loader.get(
|
|
203
227
|
name="ui_buttonMd",
|
|
204
|
-
paq_rel="ui/
|
|
205
|
-
fs_rel="ui/
|
|
228
|
+
paq_rel="ui/ui_button_128x32.jaz",
|
|
229
|
+
fs_rel="ui/ui_button_128x32.png",
|
|
206
230
|
),
|
|
207
231
|
cursor=loader.get(name="ui_cursor", paq_rel="ui/ui_cursor.jaz", fs_rel="ui/ui_cursor.png"),
|
|
208
232
|
aim=loader.get(name="ui_aim", paq_rel="ui/ui_aim.jaz", fs_rel="ui/ui_aim.png"),
|
|
@@ -281,6 +305,17 @@ def draw_menu_item(
|
|
|
281
305
|
return float(width)
|
|
282
306
|
|
|
283
307
|
|
|
308
|
+
class UiButtonTextures(Protocol):
|
|
309
|
+
button_sm: rl.Texture | None
|
|
310
|
+
button_md: rl.Texture | None
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@dataclass(slots=True)
|
|
314
|
+
class UiButtonTextureSet:
|
|
315
|
+
button_sm: rl.Texture | None
|
|
316
|
+
button_md: rl.Texture | None
|
|
317
|
+
|
|
318
|
+
|
|
284
319
|
@dataclass(slots=True)
|
|
285
320
|
class UiButtonState:
|
|
286
321
|
label: str
|
|
@@ -343,7 +378,7 @@ def _clamp(value: float, lo: float, hi: float) -> float:
|
|
|
343
378
|
|
|
344
379
|
|
|
345
380
|
def button_draw(
|
|
346
|
-
assets:
|
|
381
|
+
assets: UiButtonTextures,
|
|
347
382
|
font: SmallFontData | None,
|
|
348
383
|
state: UiButtonState,
|
|
349
384
|
*,
|
|
@@ -357,23 +392,45 @@ def button_draw(
|
|
|
357
392
|
return
|
|
358
393
|
|
|
359
394
|
if state.hover_t > 0:
|
|
360
|
-
alpha
|
|
395
|
+
# ui_button_update: highlight fill uses a hover-scaled alpha and click-biased blue tint.
|
|
396
|
+
# - base: (0.5, 0.5, 0.7)
|
|
397
|
+
# - click_anim: +0.0005 / +0.0007, clamped to 1.0 (towards white)
|
|
398
|
+
# - alpha: hover_anim * 0.001 * button.alpha
|
|
399
|
+
r = 0.5
|
|
400
|
+
g = 0.5
|
|
401
|
+
b = 0.7
|
|
361
402
|
if state.press_t > 0:
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
403
|
+
click_t = float(state.press_t)
|
|
404
|
+
g = min(1.0, 0.5 + click_t * 0.0005)
|
|
405
|
+
r = g
|
|
406
|
+
b = min(1.0, 0.7 + click_t * 0.0007)
|
|
407
|
+
a = float(state.hover_t) * 0.001 * state.alpha
|
|
408
|
+
hl = rl.Color(
|
|
409
|
+
int(255 * r),
|
|
410
|
+
int(255 * g),
|
|
411
|
+
int(255 * b),
|
|
412
|
+
int(255 * _clamp(a, 0.0, 1.0)),
|
|
413
|
+
)
|
|
414
|
+
rl.draw_rectangle(
|
|
415
|
+
int(x + 12.0 * scale),
|
|
416
|
+
int(y + 5.0 * scale),
|
|
417
|
+
int(width - 24.0 * scale),
|
|
418
|
+
int(22.0 * scale),
|
|
419
|
+
hl,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
plate_tint = rl.Color(255, 255, 255, int(255 * _clamp(state.alpha, 0.0, 1.0)))
|
|
368
423
|
|
|
369
424
|
src = rl.Rectangle(0.0, 0.0, float(texture.width), float(texture.height))
|
|
370
425
|
dst = rl.Rectangle(float(x), float(y), float(width), float(32.0 * scale))
|
|
371
|
-
rl.draw_texture_pro(texture, src, dst, rl.Vector2(0.0, 0.0), 0.0,
|
|
426
|
+
rl.draw_texture_pro(texture, src, dst, rl.Vector2(0.0, 0.0), 0.0, plate_tint)
|
|
372
427
|
|
|
428
|
+
text_a = state.alpha if state.hovered else state.alpha * 0.7
|
|
429
|
+
text_tint = rl.Color(255, 255, 255, int(255 * _clamp(text_a, 0.0, 1.0)))
|
|
373
430
|
text_w = _ui_text_width(font, state.label, scale)
|
|
374
431
|
text_x = x + width * 0.5 - text_w * 0.5 + 1.0 * scale
|
|
375
432
|
text_y = y + 10.0 * scale
|
|
376
|
-
draw_ui_text(font, state.label, text_x, text_y, scale=scale, color=
|
|
433
|
+
draw_ui_text(font, state.label, text_x, text_y, scale=scale, color=text_tint)
|
|
377
434
|
|
|
378
435
|
|
|
379
436
|
def cursor_draw(assets: PerkMenuAssets, *, mouse: rl.Vector2, scale: float, alpha: float = 1.0) -> None:
|