crimsonland 0.1.0.dev14__py3-none-any.whl → 0.1.0.dev16__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/cli.py +63 -0
- crimson/creatures/damage.py +111 -36
- crimson/creatures/runtime.py +246 -156
- crimson/creatures/spawn.py +7 -3
- crimson/debug.py +9 -0
- crimson/demo.py +38 -45
- crimson/effects.py +7 -13
- crimson/frontend/high_scores_layout.py +81 -0
- crimson/frontend/panels/base.py +4 -1
- crimson/frontend/panels/controls.py +0 -15
- crimson/frontend/panels/databases.py +291 -3
- crimson/frontend/panels/mods.py +0 -15
- crimson/frontend/panels/play_game.py +0 -16
- crimson/game.py +689 -3
- crimson/gameplay.py +921 -569
- crimson/modes/base_gameplay_mode.py +33 -12
- crimson/modes/components/__init__.py +2 -0
- crimson/modes/components/highscore_record_builder.py +58 -0
- crimson/modes/components/perk_menu_controller.py +325 -0
- crimson/modes/quest_mode.py +94 -272
- crimson/modes/rush_mode.py +12 -43
- crimson/modes/survival_mode.py +109 -330
- crimson/modes/tutorial_mode.py +46 -247
- crimson/modes/typo_mode.py +11 -38
- crimson/oracle.py +396 -0
- crimson/perks.py +5 -2
- crimson/player_damage.py +95 -36
- crimson/projectiles.py +539 -320
- crimson/render/projectile_draw_registry.py +637 -0
- crimson/render/projectile_render_registry.py +110 -0
- crimson/render/secondary_projectile_draw_registry.py +206 -0
- crimson/render/world_renderer.py +58 -707
- crimson/sim/world_state.py +118 -61
- crimson/typo/spawns.py +5 -12
- crimson/ui/demo_trial_overlay.py +3 -11
- crimson/ui/formatting.py +24 -0
- crimson/ui/game_over.py +12 -58
- crimson/ui/hud.py +72 -39
- crimson/ui/layout.py +20 -0
- crimson/ui/perk_menu.py +9 -34
- crimson/ui/quest_results.py +28 -70
- crimson/ui/text_input.py +20 -0
- crimson/views/_ui_helpers.py +27 -0
- crimson/views/aim_debug.py +15 -32
- crimson/views/animations.py +18 -28
- crimson/views/arsenal_debug.py +22 -32
- crimson/views/bonuses.py +23 -36
- crimson/views/camera_debug.py +16 -29
- crimson/views/camera_shake.py +9 -33
- crimson/views/corpse_stamp_debug.py +13 -21
- crimson/views/decals_debug.py +36 -23
- crimson/views/fonts.py +8 -25
- crimson/views/ground.py +4 -21
- crimson/views/lighting_debug.py +42 -45
- crimson/views/particles.py +33 -42
- crimson/views/perk_menu_debug.py +3 -10
- crimson/views/player.py +50 -44
- crimson/views/player_sprite_debug.py +24 -31
- crimson/views/projectile_fx.py +57 -52
- crimson/views/projectile_render_debug.py +24 -33
- crimson/views/projectiles.py +24 -37
- crimson/views/spawn_plan.py +13 -29
- crimson/views/sprites.py +14 -29
- crimson/views/terrain.py +6 -23
- crimson/views/ui.py +7 -24
- crimson/views/wicons.py +28 -33
- {crimsonland-0.1.0.dev14.dist-info → crimsonland-0.1.0.dev16.dist-info}/METADATA +1 -1
- {crimsonland-0.1.0.dev14.dist-info → crimsonland-0.1.0.dev16.dist-info}/RECORD +73 -62
- {crimsonland-0.1.0.dev14.dist-info → crimsonland-0.1.0.dev16.dist-info}/WHEEL +1 -1
- grim/config.py +29 -1
- grim/console.py +7 -10
- grim/math.py +12 -0
- {crimsonland-0.1.0.dev14.dist-info → crimsonland-0.1.0.dev16.dist-info}/entry_points.txt +0 -0
crimson/ui/perk_menu.py
CHANGED
|
@@ -8,13 +8,11 @@ import pyray as rl
|
|
|
8
8
|
|
|
9
9
|
from grim.assets import TextureLoader
|
|
10
10
|
from grim.fonts.small import SmallFontData, draw_small_text, measure_small_text_width
|
|
11
|
+
from grim.math import clamp
|
|
11
12
|
|
|
13
|
+
from .layout import menu_widescreen_y_shift, ui_origin, ui_scale
|
|
12
14
|
from .menu_panel import draw_classic_menu_panel
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
UI_BASE_WIDTH = 640.0
|
|
16
|
-
UI_BASE_HEIGHT = 480.0
|
|
17
|
-
|
|
18
16
|
# Perk selection screen panel uses ui_element-style timeline animation:
|
|
19
17
|
# - fully hidden until end_ms
|
|
20
18
|
# - slides in over (end_ms..start_ms)
|
|
@@ -71,21 +69,6 @@ class PerkMenuComputedLayout:
|
|
|
71
69
|
cancel_x: float
|
|
72
70
|
cancel_y: float
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
def ui_scale(screen_w: float, screen_h: float) -> float:
|
|
76
|
-
# Classic UI renders in backbuffer pixels; keep menu scale fixed.
|
|
77
|
-
return 1.0
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def ui_origin(screen_w: float, screen_h: float, scale: float) -> tuple[float, float]:
|
|
81
|
-
return 0.0, 0.0
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _menu_widescreen_y_shift(layout_w: float) -> float:
|
|
85
|
-
# ui_menu_layout_init: pos_y += (screen_width / 640.0) * 150.0 - 150.0
|
|
86
|
-
return (layout_w / UI_BASE_WIDTH) * 150.0 - 150.0
|
|
87
|
-
|
|
88
|
-
|
|
89
72
|
def perk_menu_compute_layout(
|
|
90
73
|
layout: PerkMenuLayout,
|
|
91
74
|
*,
|
|
@@ -99,7 +82,7 @@ def perk_menu_compute_layout(
|
|
|
99
82
|
panel_slide_x: float = 0.0,
|
|
100
83
|
) -> PerkMenuComputedLayout:
|
|
101
84
|
layout_w = screen_w / scale if scale else screen_w
|
|
102
|
-
widescreen_shift_y =
|
|
85
|
+
widescreen_shift_y = menu_widescreen_y_shift(layout_w)
|
|
103
86
|
panel_x = layout.panel_x + panel_slide_x
|
|
104
87
|
panel_y = layout.panel_y + widescreen_shift_y
|
|
105
88
|
panel = rl.Rectangle(
|
|
@@ -358,10 +341,10 @@ def button_update(
|
|
|
358
341
|
state.hovered = rl.check_collision_point_rec(mouse, button_hit_rect(x=x, y=y, width=width))
|
|
359
342
|
|
|
360
343
|
delta = 6 if (state.enabled and state.hovered) else -4
|
|
361
|
-
state.hover_t = int(
|
|
344
|
+
state.hover_t = int(clamp(float(state.hover_t + int(dt_ms) * delta), 0.0, 1000.0))
|
|
362
345
|
|
|
363
346
|
if state.press_t > 0:
|
|
364
|
-
state.press_t = int(
|
|
347
|
+
state.press_t = int(clamp(float(state.press_t - int(dt_ms) * 6), 0.0, 1000.0))
|
|
365
348
|
|
|
366
349
|
state.activated = bool(state.enabled and state.hovered and click)
|
|
367
350
|
if state.activated:
|
|
@@ -369,14 +352,6 @@ def button_update(
|
|
|
369
352
|
return state.activated
|
|
370
353
|
|
|
371
354
|
|
|
372
|
-
def _clamp(value: float, lo: float, hi: float) -> float:
|
|
373
|
-
if value < lo:
|
|
374
|
-
return lo
|
|
375
|
-
if value > hi:
|
|
376
|
-
return hi
|
|
377
|
-
return value
|
|
378
|
-
|
|
379
|
-
|
|
380
355
|
def button_draw(
|
|
381
356
|
assets: UiButtonTextures,
|
|
382
357
|
font: SmallFontData | None,
|
|
@@ -409,7 +384,7 @@ def button_draw(
|
|
|
409
384
|
int(255 * r),
|
|
410
385
|
int(255 * g),
|
|
411
386
|
int(255 * b),
|
|
412
|
-
int(255 *
|
|
387
|
+
int(255 * clamp(a, 0.0, 1.0)),
|
|
413
388
|
)
|
|
414
389
|
rl.draw_rectangle(
|
|
415
390
|
int(x + 12.0 * scale),
|
|
@@ -419,14 +394,14 @@ def button_draw(
|
|
|
419
394
|
hl,
|
|
420
395
|
)
|
|
421
396
|
|
|
422
|
-
plate_tint = rl.Color(255, 255, 255, int(255 *
|
|
397
|
+
plate_tint = rl.Color(255, 255, 255, int(255 * clamp(state.alpha, 0.0, 1.0)))
|
|
423
398
|
|
|
424
399
|
src = rl.Rectangle(0.0, 0.0, float(texture.width), float(texture.height))
|
|
425
400
|
dst = rl.Rectangle(float(x), float(y), float(width), float(32.0 * scale))
|
|
426
401
|
rl.draw_texture_pro(texture, src, dst, rl.Vector2(0.0, 0.0), 0.0, plate_tint)
|
|
427
402
|
|
|
428
403
|
text_a = state.alpha if state.hovered else state.alpha * 0.7
|
|
429
|
-
text_tint = rl.Color(255, 255, 255, int(255 *
|
|
404
|
+
text_tint = rl.Color(255, 255, 255, int(255 * clamp(text_a, 0.0, 1.0)))
|
|
430
405
|
text_w = _ui_text_width(font, state.label, scale)
|
|
431
406
|
text_x = x + width * 0.5 - text_w * 0.5 + 1.0 * scale
|
|
432
407
|
text_y = y + 10.0 * scale
|
|
@@ -437,7 +412,7 @@ def cursor_draw(assets: PerkMenuAssets, *, mouse: rl.Vector2, scale: float, alph
|
|
|
437
412
|
tex = assets.cursor
|
|
438
413
|
if tex is None:
|
|
439
414
|
return
|
|
440
|
-
a = int(255 *
|
|
415
|
+
a = int(255 * clamp(alpha, 0.0, 1.0))
|
|
441
416
|
tint = rl.Color(255, 255, 255, a)
|
|
442
417
|
size = 32.0 * scale
|
|
443
418
|
src = rl.Rectangle(0.0, 0.0, float(tex.width), float(tex.height))
|
crimson/ui/quest_results.py
CHANGED
|
@@ -21,6 +21,8 @@ from ..persistence.highscores import (
|
|
|
21
21
|
upsert_highscore_record,
|
|
22
22
|
)
|
|
23
23
|
from ..quests.results import QuestFinalTime, QuestResultsBreakdownAnim, tick_quest_results_breakdown_anim
|
|
24
|
+
from .formatting import format_ordinal, format_time_mm_ss
|
|
25
|
+
from .layout import menu_widescreen_y_shift, ui_origin, ui_scale
|
|
24
26
|
from .menu_panel import draw_classic_menu_panel
|
|
25
27
|
from .perk_menu import (
|
|
26
28
|
PerkMenuAssets,
|
|
@@ -32,25 +34,7 @@ from .perk_menu import (
|
|
|
32
34
|
draw_ui_text,
|
|
33
35
|
load_perk_menu_assets,
|
|
34
36
|
)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
UI_BASE_WIDTH = 640.0
|
|
38
|
-
UI_BASE_HEIGHT = 480.0
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def ui_scale(screen_w: float, screen_h: float) -> float:
|
|
42
|
-
# Classic UI-space: draw in backbuffer pixels.
|
|
43
|
-
return 1.0
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def ui_origin(screen_w: float, screen_h: float, scale: float) -> tuple[float, float]:
|
|
47
|
-
return 0.0, 0.0
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _menu_widescreen_y_shift(layout_w: float) -> float:
|
|
51
|
-
# ui_menu_layout_init: pos_y += (screen_width / 640.0) * 150.0 - 150.0
|
|
52
|
-
return (layout_w / UI_BASE_WIDTH) * 150.0 - 150.0
|
|
53
|
-
|
|
37
|
+
from .text_input import poll_text_input
|
|
54
38
|
|
|
55
39
|
# `quest_results_screen_update` base layout (Crimsonland classic UI panel).
|
|
56
40
|
# Values are derived from `ui_menu_assets_init` + `ui_menu_layout_init` and how
|
|
@@ -73,6 +57,14 @@ QUEST_RESULTS_PANEL_H = 378.0
|
|
|
73
57
|
TEXTURE_TOP_BANNER_W = 256.0
|
|
74
58
|
TEXTURE_TOP_BANNER_H = 64.0
|
|
75
59
|
|
|
60
|
+
# `quest_results_screen_update` uses the classic UI element sums for positioning:
|
|
61
|
+
# content_x = (pos_x + offset_x + slide_x) + 180.0 + 40.0
|
|
62
|
+
# banner_x = content_x - 18.0
|
|
63
|
+
# score_x = content_x + 30.0
|
|
64
|
+
QUEST_RESULTS_CONTENT_X = 220.0
|
|
65
|
+
QUEST_RESULTS_BANNER_X_FROM_CONTENT = -18.0
|
|
66
|
+
QUEST_RESULTS_SCORE_CARD_X_FROM_CONTENT = 30.0
|
|
67
|
+
|
|
76
68
|
INPUT_BOX_W = 166.0
|
|
77
69
|
INPUT_BOX_H = 18.0
|
|
78
70
|
|
|
@@ -88,44 +80,6 @@ COLOR_TEXT_SUBTLE = rl.Color(255, 255, 255, int(255 * 0.7))
|
|
|
88
80
|
COLOR_GREEN = rl.Color(25, 200, 25, 255)
|
|
89
81
|
|
|
90
82
|
|
|
91
|
-
def _poll_text_input(max_len: int, *, allow_space: bool = True) -> str:
|
|
92
|
-
out = ""
|
|
93
|
-
while True:
|
|
94
|
-
value = rl.get_char_pressed()
|
|
95
|
-
if value == 0:
|
|
96
|
-
break
|
|
97
|
-
if value < 0x20 or value > 0xFF:
|
|
98
|
-
continue
|
|
99
|
-
if not allow_space and value == 0x20:
|
|
100
|
-
continue
|
|
101
|
-
if len(out) >= max_len:
|
|
102
|
-
continue
|
|
103
|
-
out += chr(int(value))
|
|
104
|
-
return out
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def _format_ordinal(value_1_based: int) -> str:
|
|
108
|
-
value = int(value_1_based)
|
|
109
|
-
if value % 100 in (11, 12, 13):
|
|
110
|
-
suffix = "th"
|
|
111
|
-
elif value % 10 == 1:
|
|
112
|
-
suffix = "st"
|
|
113
|
-
elif value % 10 == 2:
|
|
114
|
-
suffix = "nd"
|
|
115
|
-
elif value % 10 == 3:
|
|
116
|
-
suffix = "rd"
|
|
117
|
-
else:
|
|
118
|
-
suffix = "th"
|
|
119
|
-
return f"{value}{suffix}"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def _format_time_mm_ss(ms: int) -> str:
|
|
123
|
-
total_s = max(0, int(ms)) // 1000
|
|
124
|
-
minutes = total_s // 60
|
|
125
|
-
seconds = total_s % 60
|
|
126
|
-
return f"{minutes}:{seconds:02d}"
|
|
127
|
-
|
|
128
|
-
|
|
129
83
|
@dataclass(slots=True)
|
|
130
84
|
class QuestResultsAssets:
|
|
131
85
|
menu_panel: rl.Texture | None
|
|
@@ -300,7 +254,7 @@ class QuestResultsUi:
|
|
|
300
254
|
|
|
301
255
|
left = (QUEST_RESULTS_PANEL_GEOM_X0 + QUEST_RESULTS_PANEL_POS_X + panel_slide_x) * scale
|
|
302
256
|
layout_w = screen_w / scale if scale else screen_w
|
|
303
|
-
widescreen_shift_y =
|
|
257
|
+
widescreen_shift_y = menu_widescreen_y_shift(layout_w)
|
|
304
258
|
top = (QUEST_RESULTS_PANEL_GEOM_Y0 + QUEST_RESULTS_PANEL_POS_Y + widescreen_shift_y) * scale
|
|
305
259
|
panel = rl.Rectangle(float(left), float(top), QUEST_RESULTS_PANEL_W * scale, QUEST_RESULTS_PANEL_H * scale)
|
|
306
260
|
return panel, left, top
|
|
@@ -374,7 +328,7 @@ class QuestResultsUi:
|
|
|
374
328
|
|
|
375
329
|
if self.phase == 1:
|
|
376
330
|
click = rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT)
|
|
377
|
-
typed =
|
|
331
|
+
typed = poll_text_input(NAME_MAX_EDIT - len(self.input_text), allow_space=True)
|
|
378
332
|
if typed:
|
|
379
333
|
self.input_text = (self.input_text[: self.input_caret] + typed + self.input_text[self.input_caret :])[:NAME_MAX_EDIT]
|
|
380
334
|
self.input_caret = min(len(self.input_text), self.input_caret + len(typed))
|
|
@@ -399,7 +353,7 @@ class QuestResultsUi:
|
|
|
399
353
|
screen_h = float(rl.get_screen_height())
|
|
400
354
|
scale = ui_scale(screen_w, screen_h)
|
|
401
355
|
_panel, panel_left, panel_top = self._panel_layout(screen_w=screen_w, scale=scale)
|
|
402
|
-
anchor_x = panel_left +
|
|
356
|
+
anchor_x = panel_left + QUEST_RESULTS_CONTENT_X * scale
|
|
403
357
|
input_y = panel_top + 150.0 * scale
|
|
404
358
|
ok_x = anchor_x + 170.0 * scale
|
|
405
359
|
ok_y = input_y - 8.0 * scale
|
|
@@ -421,6 +375,8 @@ class QuestResultsUi:
|
|
|
421
375
|
except Exception:
|
|
422
376
|
self.highlight_rank = None
|
|
423
377
|
self._saved = True
|
|
378
|
+
self.config.set_player_name(self.input_text)
|
|
379
|
+
self.config.save()
|
|
424
380
|
self.phase = 2
|
|
425
381
|
return None
|
|
426
382
|
if play_sfx is not None:
|
|
@@ -451,7 +407,8 @@ class QuestResultsUi:
|
|
|
451
407
|
_origin_x, _origin_y = ui_origin(screen_w, screen_h, scale)
|
|
452
408
|
_panel, left, top = self._panel_layout(screen_w=screen_w, scale=scale)
|
|
453
409
|
qualifies = int(self.rank) < TABLE_MAX
|
|
454
|
-
|
|
410
|
+
content_x = left + QUEST_RESULTS_CONTENT_X * scale
|
|
411
|
+
score_card_x = content_x + QUEST_RESULTS_SCORE_CARD_X_FROM_CONTENT * scale
|
|
455
412
|
|
|
456
413
|
var_c_12 = top + (96.0 if qualifies else 108.0) * scale
|
|
457
414
|
var_c_14 = var_c_12 + 84.0 * scale
|
|
@@ -515,7 +472,8 @@ class QuestResultsUi:
|
|
|
515
472
|
fx_detail = bool(int(self.config.data.get("fx_detail_0", 0) or 0))
|
|
516
473
|
draw_classic_menu_panel(self.assets.menu_panel, dst=panel, tint=rl.WHITE, shadow=fx_detail)
|
|
517
474
|
|
|
518
|
-
|
|
475
|
+
content_x = left + QUEST_RESULTS_CONTENT_X * scale
|
|
476
|
+
banner_x = content_x + QUEST_RESULTS_BANNER_X_FROM_CONTENT * scale
|
|
519
477
|
banner_y = top + 36.0 * scale
|
|
520
478
|
if self.assets.text_well_done is not None:
|
|
521
479
|
src = rl.Rectangle(0.0, 0.0, float(self.assets.text_well_done.width), float(self.assets.text_well_done.height))
|
|
@@ -525,7 +483,7 @@ class QuestResultsUi:
|
|
|
525
483
|
qualifies = int(self.rank) < TABLE_MAX
|
|
526
484
|
|
|
527
485
|
if self.phase == 0:
|
|
528
|
-
anchor_x =
|
|
486
|
+
anchor_x = content_x
|
|
529
487
|
label_x = anchor_x + 32.0 * scale
|
|
530
488
|
value_x = label_x + 132.0 * scale
|
|
531
489
|
|
|
@@ -560,10 +518,10 @@ class QuestResultsUi:
|
|
|
560
518
|
return rl.Color(rgb[0], rgb[1], rgb[2], int(255 * max(0.0, min(1.0, alpha))))
|
|
561
519
|
|
|
562
520
|
y = top + 156.0 * scale
|
|
563
|
-
base_value =
|
|
564
|
-
life_value =
|
|
565
|
-
perk_value =
|
|
566
|
-
final_value =
|
|
521
|
+
base_value = format_time_mm_ss(base_time_ms)
|
|
522
|
+
life_value = format_time_mm_ss(life_bonus_ms)
|
|
523
|
+
perk_value = format_time_mm_ss(perk_bonus_ms)
|
|
524
|
+
final_value = format_time_mm_ss(final_time_ms)
|
|
567
525
|
|
|
568
526
|
self._draw_small("Base Time:", label_x, y, 1.0 * scale, _row_color(0))
|
|
569
527
|
self._draw_small(base_value, value_x, y, 1.0 * scale, _row_color(0))
|
|
@@ -587,7 +545,7 @@ class QuestResultsUi:
|
|
|
587
545
|
self._draw_small(final_value, value_x, y, 1.0 * scale, _row_color(3, final=True))
|
|
588
546
|
|
|
589
547
|
elif self.phase == 1:
|
|
590
|
-
anchor_x =
|
|
548
|
+
anchor_x = content_x
|
|
591
549
|
text_y = top + 118.0 * scale
|
|
592
550
|
self._draw_small("State your name trooper!", anchor_x + 42.0 * scale, text_y, 1.0 * scale, COLOR_TEXT)
|
|
593
551
|
|
|
@@ -615,7 +573,7 @@ class QuestResultsUi:
|
|
|
615
573
|
button_draw(self.assets.perk_menu_assets, self.font, self._ok_button, x=ok_x, y=ok_y, width=ok_w, scale=scale)
|
|
616
574
|
|
|
617
575
|
else:
|
|
618
|
-
score_card_x =
|
|
576
|
+
score_card_x = content_x + QUEST_RESULTS_SCORE_CARD_X_FROM_CONTENT * scale
|
|
619
577
|
var_c_12 = top + (96.0 if qualifies else 108.0) * scale
|
|
620
578
|
if (not qualifies) and self.font is not None:
|
|
621
579
|
self._draw_small(
|
|
@@ -629,7 +587,7 @@ class QuestResultsUi:
|
|
|
629
587
|
seconds = float(int(self.record.survival_elapsed_ms)) * 0.001
|
|
630
588
|
score_value = f"{seconds:.2f} secs"
|
|
631
589
|
xp_value = f"{int(self.record.score_xp)}"
|
|
632
|
-
rank_text =
|
|
590
|
+
rank_text = format_ordinal(int(self.rank) + 1) if qualifies else "--"
|
|
633
591
|
|
|
634
592
|
col_label = rl.Color(230, 230, 230, 255)
|
|
635
593
|
col_value = rl.Color(230, 230, 255, 255)
|
crimson/ui/text_input.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pyray as rl
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def poll_text_input(max_len: int, *, allow_space: bool = True) -> str:
|
|
7
|
+
out = ""
|
|
8
|
+
while True:
|
|
9
|
+
value = rl.get_char_pressed()
|
|
10
|
+
if value == 0:
|
|
11
|
+
break
|
|
12
|
+
if value < 0x20 or value > 0xFF:
|
|
13
|
+
continue
|
|
14
|
+
if not allow_space and value == 0x20:
|
|
15
|
+
continue
|
|
16
|
+
if len(out) >= max_len:
|
|
17
|
+
continue
|
|
18
|
+
out += chr(int(value))
|
|
19
|
+
return out
|
|
20
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pyray as rl
|
|
4
|
+
|
|
5
|
+
from grim.fonts.small import SmallFontData, draw_small_text
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def ui_line_height(font: SmallFontData | None, *, scale: float = 1.0) -> int:
|
|
9
|
+
if font is not None:
|
|
10
|
+
return int(font.cell_size * float(scale))
|
|
11
|
+
return int(20 * float(scale))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def draw_ui_text(
|
|
15
|
+
font: SmallFontData | None,
|
|
16
|
+
text: str,
|
|
17
|
+
x: float,
|
|
18
|
+
y: float,
|
|
19
|
+
*,
|
|
20
|
+
color: rl.Color,
|
|
21
|
+
scale: float = 1.0,
|
|
22
|
+
) -> None:
|
|
23
|
+
if font is not None:
|
|
24
|
+
draw_small_text(font, text, x, y, float(scale), color)
|
|
25
|
+
else:
|
|
26
|
+
rl.draw_text(text, int(x), int(y), int(20 * float(scale)), color)
|
|
27
|
+
|
crimson/views/aim_debug.py
CHANGED
|
@@ -5,13 +5,15 @@ import math
|
|
|
5
5
|
import pyray as rl
|
|
6
6
|
|
|
7
7
|
from grim.config import ensure_crimson_cfg
|
|
8
|
-
from grim.fonts.small import SmallFontData,
|
|
8
|
+
from grim.fonts.small import SmallFontData, load_small_font
|
|
9
|
+
from grim.math import clamp
|
|
9
10
|
from grim.view import ViewContext
|
|
10
11
|
|
|
11
12
|
from ..game_world import GameWorld
|
|
12
13
|
from ..gameplay import PlayerInput
|
|
13
14
|
from ..paths import default_runtime_dir
|
|
14
15
|
from ..ui.cursor import draw_cursor_glow
|
|
16
|
+
from ._ui_helpers import draw_ui_text, ui_line_height
|
|
15
17
|
from .registry import register_view
|
|
16
18
|
|
|
17
19
|
WORLD_SIZE = 1024.0
|
|
@@ -22,14 +24,6 @@ UI_HINT_COLOR = rl.Color(140, 140, 140, 255)
|
|
|
22
24
|
UI_ERROR_COLOR = rl.Color(240, 80, 80, 255)
|
|
23
25
|
|
|
24
26
|
|
|
25
|
-
def _clamp(value: float, lo: float, hi: float) -> float:
|
|
26
|
-
if value < lo:
|
|
27
|
-
return lo
|
|
28
|
-
if value > hi:
|
|
29
|
-
return hi
|
|
30
|
-
return value
|
|
31
|
-
|
|
32
|
-
|
|
33
27
|
class AimDebugView:
|
|
34
28
|
def __init__(self, ctx: ViewContext) -> None:
|
|
35
29
|
self._assets_root = ctx.assets_dir
|
|
@@ -61,30 +55,12 @@ class AimDebugView:
|
|
|
61
55
|
self._forced_heat = 0.18
|
|
62
56
|
self._test_circle_radius = 96.0
|
|
63
57
|
|
|
64
|
-
def _ui_line_height(self, scale: float = UI_TEXT_SCALE) -> int:
|
|
65
|
-
if self._small is not None:
|
|
66
|
-
return int(self._small.cell_size * scale)
|
|
67
|
-
return int(20 * scale)
|
|
68
|
-
|
|
69
|
-
def _draw_ui_text(
|
|
70
|
-
self,
|
|
71
|
-
text: str,
|
|
72
|
-
x: float,
|
|
73
|
-
y: float,
|
|
74
|
-
color: rl.Color,
|
|
75
|
-
scale: float = UI_TEXT_SCALE,
|
|
76
|
-
) -> None:
|
|
77
|
-
if self._small is not None:
|
|
78
|
-
draw_small_text(self._small, text, x, y, scale, color)
|
|
79
|
-
else:
|
|
80
|
-
rl.draw_text(text, int(x), int(y), int(20 * scale), color)
|
|
81
|
-
|
|
82
58
|
def _update_ui_mouse(self) -> None:
|
|
83
59
|
mouse = rl.get_mouse_position()
|
|
84
60
|
screen_w = float(rl.get_screen_width())
|
|
85
61
|
screen_h = float(rl.get_screen_height())
|
|
86
|
-
self._ui_mouse_x =
|
|
87
|
-
self._ui_mouse_y =
|
|
62
|
+
self._ui_mouse_x = clamp(float(mouse.x), 0.0, max(0.0, screen_w - 1.0))
|
|
63
|
+
self._ui_mouse_y = clamp(float(mouse.y), 0.0, max(0.0, screen_h - 1.0))
|
|
88
64
|
|
|
89
65
|
def _draw_cursor_glow(self, *, x: float, y: float) -> None:
|
|
90
66
|
draw_cursor_glow(self._world.particles_texture, x=x, y=y)
|
|
@@ -264,11 +240,18 @@ class AimDebugView:
|
|
|
264
240
|
]
|
|
265
241
|
x0 = 16.0
|
|
266
242
|
y0 = 16.0
|
|
267
|
-
lh = float(self.
|
|
243
|
+
lh = float(ui_line_height(self._small, scale=UI_TEXT_SCALE))
|
|
268
244
|
for idx, line in enumerate(lines):
|
|
269
|
-
|
|
245
|
+
draw_ui_text(
|
|
246
|
+
self._small,
|
|
247
|
+
line,
|
|
248
|
+
x0,
|
|
249
|
+
y0 + lh * float(idx),
|
|
250
|
+
scale=UI_TEXT_SCALE,
|
|
251
|
+
color=UI_TEXT_COLOR if idx < 6 else UI_HINT_COLOR,
|
|
252
|
+
)
|
|
270
253
|
elif self._draw_expected_overlay and self._player is None:
|
|
271
|
-
self.
|
|
254
|
+
draw_ui_text(self._small, "Aim debug view: missing player", 16.0, 16.0, scale=UI_TEXT_SCALE, color=UI_ERROR_COLOR)
|
|
272
255
|
|
|
273
256
|
|
|
274
257
|
@register_view("aim-debug", "Aim indicator debug")
|
crimson/views/animations.py
CHANGED
|
@@ -6,8 +6,9 @@ import pyray as rl
|
|
|
6
6
|
|
|
7
7
|
from ..creatures.anim import creature_anim_advance_phase, creature_anim_select_frame
|
|
8
8
|
from ..creatures.spawn import CreatureFlags, CreatureTypeId, SPAWN_TEMPLATES, SpawnTemplate, resolve_tint
|
|
9
|
+
from ._ui_helpers import draw_ui_text, ui_line_height
|
|
9
10
|
from .registry import register_view
|
|
10
|
-
from grim.fonts.small import SmallFontData,
|
|
11
|
+
from grim.fonts.small import SmallFontData, load_small_font
|
|
11
12
|
from grim.view import View, ViewContext
|
|
12
13
|
|
|
13
14
|
UI_TEXT_SCALE = 1.0
|
|
@@ -52,24 +53,6 @@ class CreatureAnimationView:
|
|
|
52
53
|
self._last_step = 0.0
|
|
53
54
|
self._apply_template_defaults()
|
|
54
55
|
|
|
55
|
-
def _ui_line_height(self, scale: float = UI_TEXT_SCALE) -> int:
|
|
56
|
-
if self._small is not None:
|
|
57
|
-
return int(self._small.cell_size * scale)
|
|
58
|
-
return int(20 * scale)
|
|
59
|
-
|
|
60
|
-
def _draw_ui_text(
|
|
61
|
-
self,
|
|
62
|
-
text: str,
|
|
63
|
-
x: float,
|
|
64
|
-
y: float,
|
|
65
|
-
color: rl.Color,
|
|
66
|
-
scale: float = UI_TEXT_SCALE,
|
|
67
|
-
) -> None:
|
|
68
|
-
if self._small is not None:
|
|
69
|
-
draw_small_text(self._small, text, x, y, scale, color)
|
|
70
|
-
else:
|
|
71
|
-
rl.draw_text(text, int(x), int(y), int(20 * scale), color)
|
|
72
|
-
|
|
73
56
|
def open(self) -> None:
|
|
74
57
|
self._missing_assets.clear()
|
|
75
58
|
self._textures.clear()
|
|
@@ -163,24 +146,24 @@ class CreatureAnimationView:
|
|
|
163
146
|
rl.clear_background(rl.Color(12, 12, 14, 255))
|
|
164
147
|
if self._missing_assets:
|
|
165
148
|
message = "Missing assets: " + ", ".join(self._missing_assets)
|
|
166
|
-
self.
|
|
149
|
+
draw_ui_text(self._small, message, 24, 24, scale=UI_TEXT_SCALE, color=UI_ERROR_COLOR)
|
|
167
150
|
return
|
|
168
151
|
if not self._templates:
|
|
169
|
-
self.
|
|
152
|
+
draw_ui_text(self._small, "No spawn templates loaded.", 24, 24, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
|
|
170
153
|
return
|
|
171
154
|
|
|
172
155
|
self._handle_input()
|
|
173
156
|
template = self._current_template()
|
|
174
157
|
if template is None or template.type_id is None or template.creature is None:
|
|
175
|
-
self.
|
|
158
|
+
draw_ui_text(self._small, "Invalid template.", 24, 24, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
|
|
176
159
|
return
|
|
177
160
|
texture = self._textures.get(template.creature)
|
|
178
161
|
if texture is None:
|
|
179
|
-
self.
|
|
162
|
+
draw_ui_text(self._small, "Missing texture for creature.", 24, 24, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
|
|
180
163
|
return
|
|
181
164
|
info = TYPE_ANIM.get(template.type_id)
|
|
182
165
|
if info is None:
|
|
183
|
-
self.
|
|
166
|
+
draw_ui_text(self._small, "Missing anim info.", 24, 24, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
|
|
184
167
|
return
|
|
185
168
|
|
|
186
169
|
frame, mirror_applied, mode = creature_anim_select_frame(
|
|
@@ -196,12 +179,19 @@ class CreatureAnimationView:
|
|
|
196
179
|
|
|
197
180
|
margin = 24
|
|
198
181
|
title = f"{template.creature} (spawn 0x{template.spawn_id:02x})"
|
|
199
|
-
self.
|
|
182
|
+
draw_ui_text(self._small, title, margin, margin, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
|
|
200
183
|
hint = (
|
|
201
184
|
"Left/Right: spawn template Up/Down: move_speed PgUp/PgDn: size "
|
|
202
185
|
"[/]: local scale Space: pause R: reset"
|
|
203
186
|
)
|
|
204
|
-
|
|
187
|
+
draw_ui_text(
|
|
188
|
+
self._small,
|
|
189
|
+
hint,
|
|
190
|
+
margin,
|
|
191
|
+
margin + ui_line_height(self._small, scale=UI_TEXT_SCALE) + 6,
|
|
192
|
+
scale=UI_TEXT_SCALE,
|
|
193
|
+
color=UI_HINT_COLOR,
|
|
194
|
+
)
|
|
205
195
|
|
|
206
196
|
sheet_scale = min(
|
|
207
197
|
1.0,
|
|
@@ -265,8 +255,8 @@ class CreatureAnimationView:
|
|
|
265
255
|
]
|
|
266
256
|
y = int(preview_y + preview_size + 16)
|
|
267
257
|
for line in info_lines:
|
|
268
|
-
self.
|
|
269
|
-
y += self.
|
|
258
|
+
draw_ui_text(self._small, line, preview_x, y, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
|
|
259
|
+
y += ui_line_height(self._small, scale=UI_TEXT_SCALE) + 4
|
|
270
260
|
|
|
271
261
|
|
|
272
262
|
@register_view("animations", "Creature animation preview")
|
crimson/views/arsenal_debug.py
CHANGED
|
@@ -7,7 +7,8 @@ import pyray as rl
|
|
|
7
7
|
|
|
8
8
|
from grim.audio import AudioState, shutdown_audio, update_audio
|
|
9
9
|
from grim.console import ConsoleState
|
|
10
|
-
from grim.fonts.small import SmallFontData,
|
|
10
|
+
from grim.fonts.small import SmallFontData, load_small_font
|
|
11
|
+
from grim.math import clamp
|
|
11
12
|
from grim.view import View, ViewContext
|
|
12
13
|
|
|
13
14
|
from ..bonuses import BONUS_TABLE, BonusId
|
|
@@ -24,6 +25,7 @@ from ..weapons import (
|
|
|
24
25
|
Weapon,
|
|
25
26
|
projectile_type_id_from_weapon_id,
|
|
26
27
|
)
|
|
28
|
+
from ._ui_helpers import draw_ui_text, ui_line_height
|
|
27
29
|
from .audio_bootstrap import init_view_audio
|
|
28
30
|
from .registry import register_view
|
|
29
31
|
|
|
@@ -65,14 +67,6 @@ SPECIAL_PROJECTILES: dict[int, str] = {
|
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
|
|
68
|
-
def _clamp(value: float, lo: float, hi: float) -> float:
|
|
69
|
-
if value < lo:
|
|
70
|
-
return lo
|
|
71
|
-
if value > hi:
|
|
72
|
-
return hi
|
|
73
|
-
return value
|
|
74
|
-
|
|
75
|
-
|
|
76
70
|
def _fmt_float(value: float | None, *, digits: int = 3) -> str:
|
|
77
71
|
if value is None:
|
|
78
72
|
return "—"
|
|
@@ -135,17 +129,6 @@ class ArsenalDebugView:
|
|
|
135
129
|
player.speed_multiplier = float(ARSENAL_PLAYER_MOVE_SPEED_MULTIPLIER)
|
|
136
130
|
player.shield_timer = float(ARSENAL_PLAYER_INVULNERABLE_SHIELD_TIMER)
|
|
137
131
|
|
|
138
|
-
def _ui_line_height(self, scale: float = 1.0) -> int:
|
|
139
|
-
if self._small is not None:
|
|
140
|
-
return int(self._small.cell_size * scale)
|
|
141
|
-
return int(20 * scale)
|
|
142
|
-
|
|
143
|
-
def _draw_ui_text(self, text: str, x: float, y: float, color: rl.Color, scale: float = 1.0) -> None:
|
|
144
|
-
if self._small is not None:
|
|
145
|
-
draw_small_text(self._small, text, x, y, scale, color)
|
|
146
|
-
else:
|
|
147
|
-
rl.draw_text(text, int(x), int(y), int(20 * scale), color)
|
|
148
|
-
|
|
149
132
|
def _selected_weapon_id(self) -> int:
|
|
150
133
|
if not self._weapon_ids:
|
|
151
134
|
return 0
|
|
@@ -181,11 +164,14 @@ class ArsenalDebugView:
|
|
|
181
164
|
count = max(1, len(self._spawn_ids))
|
|
182
165
|
base_x = float(player.pos_x)
|
|
183
166
|
base_y = float(player.pos_y)
|
|
167
|
+
detail_preset = 5
|
|
168
|
+
if self._world.config is not None:
|
|
169
|
+
detail_preset = int(self._world.config.data.get("detail_preset", 5) or 5)
|
|
184
170
|
for idx in range(count):
|
|
185
171
|
spawn_id = int(self._spawn_ids[idx % len(self._spawn_ids)])
|
|
186
172
|
angle = float(idx) / float(count) * math.tau
|
|
187
|
-
x =
|
|
188
|
-
y =
|
|
173
|
+
x = clamp(base_x + math.cos(angle) * self._spawn_ring_radius, 48.0, WORLD_SIZE - 48.0)
|
|
174
|
+
y = clamp(base_y + math.sin(angle) * self._spawn_ring_radius, 48.0, WORLD_SIZE - 48.0)
|
|
189
175
|
heading = angle + math.pi
|
|
190
176
|
self._world.creatures.spawn_template(
|
|
191
177
|
spawn_id,
|
|
@@ -193,6 +179,8 @@ class ArsenalDebugView:
|
|
|
193
179
|
heading,
|
|
194
180
|
self._world.state.rng,
|
|
195
181
|
rand=self._world.state.rng.rand,
|
|
182
|
+
state=self._world.state,
|
|
183
|
+
detail_preset=detail_preset,
|
|
196
184
|
)
|
|
197
185
|
|
|
198
186
|
def _spawn_all_bonuses(self) -> None:
|
|
@@ -402,43 +390,45 @@ class ArsenalDebugView:
|
|
|
402
390
|
|
|
403
391
|
warn_x = 24.0
|
|
404
392
|
warn_y = 24.0
|
|
405
|
-
warn_line = float(self.
|
|
393
|
+
warn_line = float(ui_line_height(self._small))
|
|
406
394
|
if self._missing_assets:
|
|
407
|
-
self.
|
|
395
|
+
draw_ui_text(self._small, "Missing assets (ui): " + ", ".join(self._missing_assets), warn_x, warn_y, color=UI_ERROR)
|
|
408
396
|
warn_y += warn_line
|
|
409
397
|
if self._world.missing_assets:
|
|
410
|
-
|
|
398
|
+
draw_ui_text(
|
|
399
|
+
self._small,
|
|
411
400
|
"Missing assets (world): " + ", ".join(self._world.missing_assets),
|
|
412
401
|
warn_x,
|
|
413
402
|
warn_y,
|
|
414
|
-
UI_ERROR,
|
|
403
|
+
color=UI_ERROR,
|
|
415
404
|
)
|
|
416
405
|
warn_y += warn_line
|
|
417
406
|
|
|
418
407
|
x = 16.0
|
|
419
408
|
y = 12.0
|
|
420
|
-
line = float(self.
|
|
409
|
+
line = float(ui_line_height(self._small))
|
|
421
410
|
|
|
422
411
|
weapon = WEAPON_BY_ID.get(int(self._player.weapon_id)) if self._player is not None else None
|
|
423
412
|
for text in self._weapon_debug_lines(weapon):
|
|
424
|
-
self.
|
|
413
|
+
draw_ui_text(self._small, text, x, y, color=UI_TEXT)
|
|
425
414
|
y += line
|
|
426
415
|
|
|
427
416
|
if self._player is not None:
|
|
428
417
|
alive = sum(1 for c in self._world.creatures.entries if c.active and c.hp > 0.0)
|
|
429
418
|
total = sum(1 for c in self._world.creatures.entries if c.active)
|
|
430
|
-
self.
|
|
419
|
+
draw_ui_text(self._small, f"creatures alive {alive}/{total}", x, y, color=UI_TEXT)
|
|
431
420
|
y += line
|
|
432
421
|
|
|
433
422
|
y += 6.0
|
|
434
|
-
|
|
423
|
+
draw_ui_text(
|
|
424
|
+
self._small,
|
|
435
425
|
"WASD move LMB fire R reload [/] cycle weapons Space pause T respawn B spawn all bonuses Backspace reset Esc quit",
|
|
436
426
|
x,
|
|
437
427
|
y,
|
|
438
|
-
UI_HINT,
|
|
428
|
+
color=UI_HINT,
|
|
439
429
|
)
|
|
440
430
|
y += line
|
|
441
|
-
self.
|
|
431
|
+
draw_ui_text(self._small, "P screenshot", x, y, color=UI_HINT)
|
|
442
432
|
|
|
443
433
|
mouse = rl.get_mouse_position()
|
|
444
434
|
draw_aim_cursor(self._world.particles_texture, self._aim_texture, x=float(mouse.x), y=float(mouse.y))
|