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.
Files changed (73) hide show
  1. crimson/cli.py +63 -0
  2. crimson/creatures/damage.py +111 -36
  3. crimson/creatures/runtime.py +246 -156
  4. crimson/creatures/spawn.py +7 -3
  5. crimson/debug.py +9 -0
  6. crimson/demo.py +38 -45
  7. crimson/effects.py +7 -13
  8. crimson/frontend/high_scores_layout.py +81 -0
  9. crimson/frontend/panels/base.py +4 -1
  10. crimson/frontend/panels/controls.py +0 -15
  11. crimson/frontend/panels/databases.py +291 -3
  12. crimson/frontend/panels/mods.py +0 -15
  13. crimson/frontend/panels/play_game.py +0 -16
  14. crimson/game.py +689 -3
  15. crimson/gameplay.py +921 -569
  16. crimson/modes/base_gameplay_mode.py +33 -12
  17. crimson/modes/components/__init__.py +2 -0
  18. crimson/modes/components/highscore_record_builder.py +58 -0
  19. crimson/modes/components/perk_menu_controller.py +325 -0
  20. crimson/modes/quest_mode.py +94 -272
  21. crimson/modes/rush_mode.py +12 -43
  22. crimson/modes/survival_mode.py +109 -330
  23. crimson/modes/tutorial_mode.py +46 -247
  24. crimson/modes/typo_mode.py +11 -38
  25. crimson/oracle.py +396 -0
  26. crimson/perks.py +5 -2
  27. crimson/player_damage.py +95 -36
  28. crimson/projectiles.py +539 -320
  29. crimson/render/projectile_draw_registry.py +637 -0
  30. crimson/render/projectile_render_registry.py +110 -0
  31. crimson/render/secondary_projectile_draw_registry.py +206 -0
  32. crimson/render/world_renderer.py +58 -707
  33. crimson/sim/world_state.py +118 -61
  34. crimson/typo/spawns.py +5 -12
  35. crimson/ui/demo_trial_overlay.py +3 -11
  36. crimson/ui/formatting.py +24 -0
  37. crimson/ui/game_over.py +12 -58
  38. crimson/ui/hud.py +72 -39
  39. crimson/ui/layout.py +20 -0
  40. crimson/ui/perk_menu.py +9 -34
  41. crimson/ui/quest_results.py +28 -70
  42. crimson/ui/text_input.py +20 -0
  43. crimson/views/_ui_helpers.py +27 -0
  44. crimson/views/aim_debug.py +15 -32
  45. crimson/views/animations.py +18 -28
  46. crimson/views/arsenal_debug.py +22 -32
  47. crimson/views/bonuses.py +23 -36
  48. crimson/views/camera_debug.py +16 -29
  49. crimson/views/camera_shake.py +9 -33
  50. crimson/views/corpse_stamp_debug.py +13 -21
  51. crimson/views/decals_debug.py +36 -23
  52. crimson/views/fonts.py +8 -25
  53. crimson/views/ground.py +4 -21
  54. crimson/views/lighting_debug.py +42 -45
  55. crimson/views/particles.py +33 -42
  56. crimson/views/perk_menu_debug.py +3 -10
  57. crimson/views/player.py +50 -44
  58. crimson/views/player_sprite_debug.py +24 -31
  59. crimson/views/projectile_fx.py +57 -52
  60. crimson/views/projectile_render_debug.py +24 -33
  61. crimson/views/projectiles.py +24 -37
  62. crimson/views/spawn_plan.py +13 -29
  63. crimson/views/sprites.py +14 -29
  64. crimson/views/terrain.py +6 -23
  65. crimson/views/ui.py +7 -24
  66. crimson/views/wicons.py +28 -33
  67. {crimsonland-0.1.0.dev14.dist-info → crimsonland-0.1.0.dev16.dist-info}/METADATA +1 -1
  68. {crimsonland-0.1.0.dev14.dist-info → crimsonland-0.1.0.dev16.dist-info}/RECORD +73 -62
  69. {crimsonland-0.1.0.dev14.dist-info → crimsonland-0.1.0.dev16.dist-info}/WHEEL +1 -1
  70. grim/config.py +29 -1
  71. grim/console.py +7 -10
  72. grim/math.py +12 -0
  73. {crimsonland-0.1.0.dev14.dist-info → crimsonland-0.1.0.dev16.dist-info}/entry_points.txt +0 -0
crimson/views/bonuses.py CHANGED
@@ -6,8 +6,9 @@ import pyray as rl
6
6
 
7
7
  from ..bonuses import BONUS_TABLE, BonusMeta
8
8
  from ..weapons import WEAPON_TABLE
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, draw_small_text, load_small_font
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
@@ -46,24 +47,6 @@ class BonusIconView:
46
47
  self._texture: rl.Texture | None = None
47
48
  self._small: SmallFontData | None = None
48
49
 
49
- def _ui_line_height(self, scale: float = UI_TEXT_SCALE) -> int:
50
- if self._small is not None:
51
- return int(self._small.cell_size * scale)
52
- return int(20 * scale)
53
-
54
- def _draw_ui_text(
55
- self,
56
- text: str,
57
- x: float,
58
- y: float,
59
- color: rl.Color,
60
- scale: float = UI_TEXT_SCALE,
61
- ) -> None:
62
- if self._small is not None:
63
- draw_small_text(self._small, text, x, y, scale, color)
64
- else:
65
- rl.draw_text(text, int(x), int(y), int(20 * scale), color)
66
-
67
50
  def open(self) -> None:
68
51
  self._missing_assets.clear()
69
52
  self._small = load_small_font(self._assets_root, self._missing_assets)
@@ -88,10 +71,10 @@ class BonusIconView:
88
71
  rl.clear_background(rl.Color(12, 12, 14, 255))
89
72
  if self._missing_assets:
90
73
  message = "Missing assets: " + ", ".join(self._missing_assets)
91
- self._draw_ui_text(message, 24, 24, UI_ERROR_COLOR)
74
+ draw_ui_text(self._small, message, 24, 24, scale=UI_TEXT_SCALE, color=UI_ERROR_COLOR)
92
75
  return
93
76
  if self._texture is None:
94
- self._draw_ui_text("No bonuses texture loaded.", 24, 24, UI_TEXT_COLOR)
77
+ draw_ui_text(self._small, "No bonuses texture loaded.", 24, 24, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
95
78
  return
96
79
 
97
80
  margin = 24
@@ -149,41 +132,45 @@ class BonusIconView:
149
132
 
150
133
  info_x = x + draw_w + panel_gap
151
134
  info_y = margin
152
- self._draw_ui_text("bonuses.png (grid 4x4)", info_x, info_y, UI_TEXT_COLOR)
153
- info_y += self._ui_line_height() + 12
135
+ draw_ui_text(self._small, "bonuses.png (grid 4x4)", info_x, info_y, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
136
+ info_y += ui_line_height(self._small, scale=UI_TEXT_SCALE) + 12
154
137
 
155
138
  if hovered_index is not None:
156
139
  group = BONUS_ICON_GROUPS.get(hovered_index)
157
- self._draw_ui_text(f"icon_id {hovered_index}", info_x, info_y, UI_TEXT_COLOR)
158
- info_y += self._ui_line_height() + 6
140
+ draw_ui_text(self._small, f"icon_id {hovered_index}", info_x, info_y, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
141
+ info_y += ui_line_height(self._small, scale=UI_TEXT_SCALE) + 6
159
142
  if group is None:
160
- self._draw_ui_text("no bonus mapping", info_x, info_y, UI_HINT_COLOR)
161
- info_y += self._ui_line_height() + 6
143
+ draw_ui_text(self._small, "no bonus mapping", info_x, info_y, scale=UI_TEXT_SCALE, color=UI_HINT_COLOR)
144
+ info_y += ui_line_height(self._small, scale=UI_TEXT_SCALE) + 6
162
145
  else:
163
146
  for entry in group.bonuses:
164
147
  bonus_id = int(entry.bonus_id)
165
148
  amount = entry.default_amount
166
149
  amount_label = f" default={amount}" if amount is not None else ""
167
- self._draw_ui_text(
150
+ draw_ui_text(
151
+ self._small,
168
152
  f"id {bonus_id:02d} {entry.name}{amount_label}",
169
153
  info_x,
170
154
  info_y,
171
- UI_TEXT_COLOR,
155
+ scale=UI_TEXT_SCALE,
156
+ color=UI_TEXT_COLOR,
172
157
  )
173
- info_y += self._ui_line_height() + 4
158
+ info_y += ui_line_height(self._small, scale=UI_TEXT_SCALE) + 4
174
159
  if entry.description:
175
- self._draw_ui_text(
160
+ draw_ui_text(
161
+ self._small,
176
162
  entry.description,
177
163
  info_x,
178
164
  info_y,
179
- UI_HINT_COLOR,
165
+ scale=UI_TEXT_SCALE,
166
+ color=UI_HINT_COLOR,
180
167
  )
181
- info_y += self._ui_line_height() + 4
168
+ info_y += ui_line_height(self._small, scale=UI_TEXT_SCALE) + 4
182
169
  info_y += 8
183
170
 
184
171
  if WEAPON_BONUS is not None:
185
- self._draw_ui_text("Weapon bonus icon", info_x, info_y, UI_TEXT_COLOR)
186
- info_y += self._ui_line_height() + 4
172
+ draw_ui_text(self._small, "Weapon bonus icon", info_x, info_y, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
173
+ info_y += ui_line_height(self._small, scale=UI_TEXT_SCALE) + 4
187
174
  weapon_id = WEAPON_BONUS.default_amount
188
175
  weapon_name = None
189
176
  if weapon_id is not None:
@@ -193,7 +180,7 @@ class BonusIconView:
193
180
  break
194
181
  name_label = f" ({weapon_name})" if weapon_name else ""
195
182
  weapon_label = f"icon_id = -1 → ui_wicons (default weapon {weapon_id}{name_label})"
196
- self._draw_ui_text(weapon_label, info_x, info_y, UI_HINT_COLOR)
183
+ draw_ui_text(self._small, weapon_label, info_x, info_y, scale=UI_TEXT_SCALE, color=UI_HINT_COLOR)
197
184
 
198
185
 
199
186
  @register_view("bonuses", "Bonus icon preview")
@@ -11,10 +11,11 @@ import pyray as rl
11
11
  from grim.assets import resolve_asset_path
12
12
  from grim.config import ensure_crimson_cfg
13
13
  from grim.terrain_render import GroundRenderer
14
- from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
14
+ from grim.fonts.small import SmallFontData, load_small_font
15
15
  from grim.view import View, ViewContext
16
16
 
17
17
  from ..paths import default_runtime_dir
18
+ from ._ui_helpers import draw_ui_text, ui_line_height
18
19
  from .registry import register_view
19
20
 
20
21
 
@@ -58,24 +59,6 @@ class CameraDebugView:
58
59
  self._log_path: Path | None = None
59
60
  self._log_file = None
60
61
 
61
- def _ui_line_height(self, scale: float = UI_TEXT_SCALE) -> int:
62
- if self._small is not None:
63
- return int(self._small.cell_size * scale)
64
- return int(20 * scale)
65
-
66
- def _draw_ui_text(
67
- self,
68
- text: str,
69
- x: float,
70
- y: float,
71
- color: rl.Color,
72
- scale: float = UI_TEXT_SCALE,
73
- ) -> None:
74
- if self._small is not None:
75
- draw_small_text(self._small, text, x, y, scale, color)
76
- else:
77
- rl.draw_text(text, int(x), int(y), int(20 * scale), color)
78
-
79
62
  def _load_runtime_config(self) -> None:
80
63
  runtime_dir = default_runtime_dir()
81
64
  if not runtime_dir.is_dir():
@@ -276,7 +259,7 @@ class CameraDebugView:
276
259
  rl.clear_background(clear_color)
277
260
 
278
261
  if self._renderer is None:
279
- self._draw_ui_text("Ground renderer not initialized.", 16, 16, UI_ERROR_COLOR)
262
+ draw_ui_text(self._small, "Ground renderer not initialized.", 16, 16, scale=UI_TEXT_SCALE, color=UI_ERROR_COLOR)
280
263
  return
281
264
 
282
265
  cam_x, cam_y, scale_x, scale_y, screen_w, screen_h = self._world_params()
@@ -327,31 +310,35 @@ class CameraDebugView:
327
310
  # HUD
328
311
  x = 16.0
329
312
  y = 16.0
330
- line = self._ui_line_height()
313
+ line = ui_line_height(self._small, scale=UI_TEXT_SCALE)
331
314
  mode = "config" if self._use_config_screen else "window"
332
- self._draw_ui_text(
315
+ draw_ui_text(
316
+ self._small,
333
317
  f"window={int(out_w)}x{int(rl.get_screen_height())} camera={int(screen_w)}x{int(screen_h)} ({mode})",
334
318
  x,
335
319
  y,
336
- UI_TEXT_COLOR,
320
+ scale=UI_TEXT_SCALE,
321
+ color=UI_TEXT_COLOR,
337
322
  )
338
323
  y += line
339
- self._draw_ui_text(
324
+ draw_ui_text(
325
+ self._small,
340
326
  f"config={int(self._config_screen_w)}x{int(self._config_screen_h)} "
341
327
  f"scale={scale_x:.3f},{scale_y:.3f} tex={self._texture_scale:.2f}",
342
328
  x,
343
329
  y,
344
- UI_TEXT_COLOR,
330
+ scale=UI_TEXT_SCALE,
331
+ color=UI_TEXT_COLOR,
345
332
  )
346
333
  y += line
347
- self._draw_ui_text(f"player={self._player_x:.1f},{self._player_y:.1f}", x, y, UI_TEXT_COLOR)
334
+ draw_ui_text(self._small, f"player={self._player_x:.1f},{self._player_y:.1f}", x, y, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
348
335
  y += line
349
- self._draw_ui_text(f"camera={cam_x:.1f},{cam_y:.1f}", x, y, UI_TEXT_COLOR)
336
+ draw_ui_text(self._small, f"camera={cam_x:.1f},{cam_y:.1f}", x, y, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
350
337
  y += line
351
338
  if self._log_path is not None:
352
- self._draw_ui_text(f"log: {self._log_path}", x, y, UI_HINT_COLOR, scale=0.9)
339
+ draw_ui_text(self._small, f"log: {self._log_path}", x, y, scale=0.9, color=UI_HINT_COLOR)
353
340
  y += line
354
- self._draw_ui_text("F1: toggle camera size (config/window)", x, y, UI_HINT_COLOR)
341
+ draw_ui_text(self._small, "F1: toggle camera size (config/window)", x, y, scale=UI_TEXT_SCALE, color=UI_HINT_COLOR)
355
342
 
356
343
 
357
344
  @register_view("camera-debug", "Camera debug")
@@ -6,7 +6,8 @@ import math
6
6
  import pyray as rl
7
7
 
8
8
  from grim.config import ensure_crimson_cfg
9
- from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
9
+ from grim.fonts.small import SmallFontData, load_small_font
10
+ from grim.math import clamp
10
11
  from grim.view import View, ViewContext
11
12
 
12
13
  from ..bonuses import BonusId
@@ -14,6 +15,7 @@ from ..creatures.spawn import CreatureInit, CreatureTypeId
14
15
  from ..game_world import GameWorld
15
16
  from ..gameplay import PlayerInput, bonus_apply
16
17
  from ..paths import default_runtime_dir
18
+ from ._ui_helpers import draw_ui_text, ui_line_height
17
19
  from .registry import register_view
18
20
 
19
21
 
@@ -25,14 +27,6 @@ UI_HINT_COLOR = rl.Color(140, 140, 140, 255)
25
27
  UI_ERROR_COLOR = rl.Color(240, 80, 80, 255)
26
28
 
27
29
 
28
- def _clamp(value: float, lo: float, hi: float) -> float:
29
- if value < lo:
30
- return lo
31
- if value > hi:
32
- return hi
33
- return value
34
-
35
-
36
30
  @dataclass(frozen=True, slots=True)
37
31
  class _SpawnSpec:
38
32
  r: float
@@ -70,29 +64,11 @@ class CameraShakeView:
70
64
  self._reflex_boost_locked = False
71
65
  self._reset_scene()
72
66
 
73
- def _ui_line_height(self, scale: float = UI_TEXT_SCALE) -> int:
74
- if self._small is not None:
75
- return int(self._small.cell_size * scale)
76
- return int(20 * scale)
77
-
78
- def _draw_ui_text(
79
- self,
80
- text: str,
81
- x: float,
82
- y: float,
83
- color: rl.Color,
84
- scale: float = UI_TEXT_SCALE,
85
- ) -> None:
86
- if self._small is not None:
87
- draw_small_text(self._small, text, x, y, scale, color)
88
- else:
89
- rl.draw_text(text, int(x), int(y), int(20 * scale), color)
90
-
91
67
  def _spawn_creature(self, *, world_x: float, world_y: float, type_id: CreatureTypeId, hp: float) -> None:
92
68
  init = CreatureInit(
93
69
  origin_template_id=0,
94
- pos_x=_clamp(world_x, 64.0, WORLD_SIZE - 64.0),
95
- pos_y=_clamp(world_y, 64.0, WORLD_SIZE - 64.0),
70
+ pos_x=clamp(world_x, 64.0, WORLD_SIZE - 64.0),
71
+ pos_y=clamp(world_y, 64.0, WORLD_SIZE - 64.0),
96
72
  heading=math.pi,
97
73
  phase_seed=0.0,
98
74
  type_id=type_id,
@@ -205,7 +181,7 @@ class CameraShakeView:
205
181
 
206
182
  if self._missing_assets:
207
183
  message = "Missing assets: " + ", ".join(self._missing_assets)
208
- self._draw_ui_text(message, 24, 24, UI_ERROR_COLOR)
184
+ draw_ui_text(self._small, message, 24, 24, scale=UI_TEXT_SCALE, color=UI_ERROR_COLOR)
209
185
 
210
186
  state = self._world.state
211
187
  cam_x, cam_y, _sx, _sy = self._world._world_params()
@@ -217,11 +193,11 @@ class CameraShakeView:
217
193
  f"reflex_boost={state.bonuses.reflex_boost:.2f} creatures_alive={len(self._world.creatures.iter_active())}",
218
194
  ]
219
195
  x = 24.0
220
- y = 24.0 + float(self._ui_line_height()) + 12.0
196
+ y = 24.0 + float(ui_line_height(self._small, scale=UI_TEXT_SCALE)) + 12.0
221
197
  for idx, line in enumerate(lines):
222
198
  color = UI_HINT_COLOR if idx == 0 else UI_TEXT_COLOR
223
- self._draw_ui_text(line, x, y, color)
224
- y += float(self._ui_line_height())
199
+ draw_ui_text(self._small, line, x, y, scale=UI_TEXT_SCALE, color=color)
200
+ y += float(ui_line_height(self._small, scale=UI_TEXT_SCALE))
225
201
 
226
202
 
227
203
  @register_view("camera-shake", "Camera shake")
@@ -9,11 +9,12 @@ from crimson.creatures.anim import creature_corpse_frame_for_type
9
9
  from crimson.creatures.spawn import CreatureTypeId
10
10
  from grim.assets import resolve_asset_path
11
11
  from grim.config import ensure_crimson_cfg
12
- from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
12
+ from grim.fonts.small import SmallFontData, load_small_font
13
13
  from grim.terrain_render import GroundCorpseDecal, GroundRenderer, _maybe_alpha_test
14
14
  from grim.view import View, ViewContext
15
15
 
16
16
  from ..paths import default_runtime_dir
17
+ from ._ui_helpers import draw_ui_text, ui_line_height
17
18
  from .registry import register_view
18
19
 
19
20
 
@@ -61,17 +62,6 @@ class CorpseStampDebugView:
61
62
  self._dump_requested = False
62
63
  self._dump_index = 0
63
64
 
64
- def _ui_line_height(self) -> int:
65
- if self._small is not None:
66
- return int(self._small.cell_size)
67
- return 20
68
-
69
- def _draw_ui_text(self, text: str, x: float, y: float, color: rl.Color) -> None:
70
- if self._small is not None:
71
- draw_small_text(self._small, text, x, y, 1.0, color)
72
- else:
73
- rl.draw_text(text, int(x), int(y), 20, color)
74
-
75
65
  def _load_runtime_config(self) -> tuple[float, float | None, float | None]:
76
66
  runtime_dir = default_runtime_dir()
77
67
  if runtime_dir.is_dir():
@@ -266,12 +256,12 @@ class CorpseStampDebugView:
266
256
  rl.clear_background(BG)
267
257
 
268
258
  if self._missing_assets:
269
- self._draw_ui_text("Missing assets: " + ", ".join(self._missing_assets), 24, 24, UI_ERROR)
259
+ draw_ui_text(self._small, "Missing assets: " + ", ".join(self._missing_assets), 24, 24, color=UI_ERROR)
270
260
  return
271
261
 
272
262
  ground = self._ground
273
263
  if ground is None:
274
- self._draw_ui_text("Ground renderer not initialized.", 24, 24, UI_ERROR)
264
+ draw_ui_text(self._small, "Ground renderer not initialized.", 24, 24, color=UI_ERROR)
275
265
  return
276
266
 
277
267
  if self._dump_requested:
@@ -287,25 +277,27 @@ class CorpseStampDebugView:
287
277
  # UI
288
278
  x = 24.0
289
279
  y = 20.0
290
- line = float(self._ui_line_height())
280
+ line = float(ui_line_height(self._small))
291
281
  step = _STEPS[self._step_index]
292
282
  alpha_test = bool(getattr(ground, "alpha_test", True))
293
- self._draw_ui_text("Corpse stamp debug (SPIDER)", x, y, UI_TEXT)
283
+ draw_ui_text(self._small, "Corpse stamp debug (SPIDER)", x, y, color=UI_TEXT)
294
284
  y += line
295
- self._draw_ui_text(
285
+ draw_ui_text(
286
+ self._small,
296
287
  "N/Space: next step R: reset A: toggle alpha test Q/E: rotate P: screenshot D: dump RT",
297
288
  x,
298
289
  y,
299
- UI_HINT,
290
+ color=UI_HINT,
300
291
  )
301
292
  y += line
302
- self._draw_ui_text(f"step {self._step_index + 1}/{len(_STEPS)}: {step.description}", x, y, UI_HINT)
293
+ draw_ui_text(self._small, f"step {self._step_index + 1}/{len(_STEPS)}: {step.description}", x, y, color=UI_HINT)
303
294
  y += line
304
- self._draw_ui_text(
295
+ draw_ui_text(
296
+ self._small,
305
297
  f"alpha_test={'on' if alpha_test else 'off'} size={self._corpse_size:.1f} dump_index={self._dump_index}",
306
298
  x,
307
299
  y,
308
- UI_HINT,
300
+ color=UI_HINT,
309
301
  )
310
302
 
311
303
  # Source preview (bodyset frame) in the corner for inspection.
@@ -16,11 +16,12 @@ from crimson.gameplay import GameplayState, PlayerState
16
16
  from crimson.render.terrain_fx import FxQueueTextures, bake_fx_queues
17
17
  from grim.assets import resolve_asset_path
18
18
  from grim.config import ensure_crimson_cfg
19
- from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
19
+ from grim.fonts.small import SmallFontData, load_small_font
20
20
  from grim.terrain_render import GroundRenderer
21
21
  from grim.view import View, ViewContext
22
22
 
23
23
  from ..paths import default_runtime_dir
24
+ from ._ui_helpers import draw_ui_text, ui_line_height
24
25
  from .registry import register_view
25
26
 
26
27
 
@@ -113,17 +114,6 @@ class DecalsDebugView:
113
114
  self._fx_queue = FxQueue()
114
115
  self._fx_queue_rotated = FxQueueRotated()
115
116
 
116
- def _ui_line_height(self, scale: float = UI_TEXT_SCALE) -> int:
117
- if self._small is not None:
118
- return int(self._small.cell_size * scale)
119
- return int(20 * scale)
120
-
121
- def _draw_ui_text(self, text: str, x: float, y: float, color: rl.Color, scale: float = UI_TEXT_SCALE) -> None:
122
- if self._small is not None:
123
- draw_small_text(self._small, text, x, y, scale, color)
124
- else:
125
- rl.draw_text(text, int(x), int(y), int(20 * scale), color)
126
-
127
117
  def _write_stamp_log(self, payload: dict) -> None:
128
118
  if self._stamp_log_file is None:
129
119
  return
@@ -631,11 +621,18 @@ class DecalsDebugView:
631
621
  rl.clear_background(BG_LIGHT if self._light_mode else BG_DARK)
632
622
 
633
623
  if self._missing_assets:
634
- self._draw_ui_text("Missing assets: " + ", ".join(self._missing_assets), 24, 24, UI_ERROR_COLOR)
624
+ draw_ui_text(
625
+ self._small,
626
+ "Missing assets: " + ", ".join(self._missing_assets),
627
+ 24,
628
+ 24,
629
+ scale=UI_TEXT_SCALE,
630
+ color=UI_ERROR_COLOR,
631
+ )
635
632
  return
636
633
 
637
634
  if self._ground is None:
638
- self._draw_ui_text("Ground renderer not initialized.", 24, 24, UI_ERROR_COLOR)
635
+ draw_ui_text(self._small, "Ground renderer not initialized.", 24, 24, scale=UI_TEXT_SCALE, color=UI_ERROR_COLOR)
639
636
  return
640
637
 
641
638
  self._ground.draw(self._camera_x, self._camera_y)
@@ -697,28 +694,44 @@ class DecalsDebugView:
697
694
  hint_color = UI_HINT_DARK if self._light_mode else UI_HINT_LIGHT
698
695
  x = 16
699
696
  y = 12
700
- line = self._ui_line_height()
701
- self._draw_ui_text("Decals debug", x, y, text_color)
697
+ line = ui_line_height(self._small, scale=UI_TEXT_SCALE)
698
+ draw_ui_text(self._small, "Decals debug", x, y, scale=UI_TEXT_SCALE, color=text_color)
702
699
  y += line
703
- self._draw_ui_text("LMB: blood / damage enemy RMB: spawn enemy", x, y, hint_color)
700
+ draw_ui_text(self._small, "LMB: blood / damage enemy RMB: spawn enemy", x, y, scale=UI_TEXT_SCALE, color=hint_color)
704
701
  y += line
705
- self._draw_ui_text(
702
+ draw_ui_text(
703
+ self._small,
706
704
  "WASD: pan R: random seed T: random terrain G: toggle light grid C: clear L: stamp log",
707
705
  x,
708
706
  y,
709
- hint_color,
707
+ scale=UI_TEXT_SCALE,
708
+ color=hint_color,
710
709
  )
711
710
  y += line
712
- self._draw_ui_text(f"enemies={len([c for c in self._creatures.entries if c.active])}", x, y, hint_color)
711
+ draw_ui_text(
712
+ self._small,
713
+ f"enemies={len([c for c in self._creatures.entries if c.active])}",
714
+ x,
715
+ y,
716
+ scale=UI_TEXT_SCALE,
717
+ color=hint_color,
718
+ )
713
719
  y += line
714
720
  if self._stamp_log_path is not None:
715
721
  status = "on" if self._show_stamp_log else "off"
716
- self._draw_ui_text(f"stamp log ({status}): {self._stamp_log_path}", x, y, hint_color)
722
+ draw_ui_text(
723
+ self._small,
724
+ f"stamp log ({status}): {self._stamp_log_path}",
725
+ x,
726
+ y,
727
+ scale=UI_TEXT_SCALE,
728
+ color=hint_color,
729
+ )
717
730
  y += line
718
731
  if self._ground is not None and self._show_stamp_log:
719
732
  stamp_log = self._ground.debug_stamp_log()
720
733
  if stamp_log:
721
- self._draw_ui_text("stamp order:", x, y, hint_color)
734
+ draw_ui_text(self._small, "stamp order:", x, y, scale=UI_TEXT_SCALE, color=hint_color)
722
735
  y += line
723
736
  for event in stamp_log[-6:]:
724
737
  kind = str(event.get("kind", "?"))
@@ -730,7 +743,7 @@ class DecalsDebugView:
730
743
  msg = f"{kind} draws={event.get('draws')}"
731
744
  else:
732
745
  msg = kind
733
- self._draw_ui_text(msg, x, y, hint_color)
746
+ draw_ui_text(self._small, msg, x, y, scale=UI_TEXT_SCALE, color=hint_color)
734
747
  y += line
735
748
 
736
749
 
crimson/views/fonts.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import pyray as rl
4
+ from ._ui_helpers import draw_ui_text, ui_line_height
4
5
  from .quest_title_overlay import (
5
6
  draw_quest_title_overlay,
6
7
  quest_title_base_scale,
@@ -41,24 +42,6 @@ class FontView:
41
42
  self._grim_mono: GrimMonoFont | None = None
42
43
  self._sample = DEFAULT_SAMPLE
43
44
 
44
- def _ui_line_height(self, scale: float = UI_TEXT_SCALE) -> int:
45
- if self._small is not None:
46
- return int(self._small.cell_size * scale)
47
- return int(20 * scale)
48
-
49
- def _draw_ui_text(
50
- self,
51
- text: str,
52
- x: float,
53
- y: float,
54
- color: rl.Color,
55
- scale: float = UI_TEXT_SCALE,
56
- ) -> None:
57
- if self._small is not None:
58
- draw_small_text(self._small, text, x, y, scale, color)
59
- else:
60
- rl.draw_text(text, int(x), int(y), int(20 * scale), color)
61
-
62
45
  def close(self) -> None:
63
46
  if self._small is not None:
64
47
  rl.unload_texture(self._small.texture)
@@ -83,20 +66,20 @@ class FontView:
83
66
  rl.clear_background(rl.Color(12, 12, 14, 255))
84
67
  if self._missing_assets:
85
68
  message = "Missing assets: " + ", ".join(self._missing_assets)
86
- self._draw_ui_text(message, 24, 24, UI_ERROR_COLOR)
69
+ draw_ui_text(self._small, message, 24, 24, scale=UI_TEXT_SCALE, color=UI_ERROR_COLOR)
87
70
  return
88
71
  y = 24
89
- self._draw_ui_text("Small font", 24, y, UI_TEXT_COLOR)
90
- y += self._ui_line_height() + 12
72
+ draw_ui_text(self._small, "Small font", 24, y, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
73
+ y += ui_line_height(self._small, scale=UI_TEXT_SCALE) + 12
91
74
  if self._small is not None:
92
75
  draw_small_text(self._small, self._sample, 24, y, SMALL_SAMPLE_SCALE, rl.WHITE)
93
76
  y += int(measure_small_text_height(self._small, self._sample, SMALL_SAMPLE_SCALE)) + 40
94
77
 
95
- self._draw_ui_text("Grim2D mono font", 24, y, UI_TEXT_COLOR)
96
- y += self._ui_line_height() + 12
78
+ draw_ui_text(self._small, "Grim2D mono font", 24, y, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
79
+ y += ui_line_height(self._small, scale=UI_TEXT_SCALE) + 12
97
80
  if self._grim_mono is not None:
98
- self._draw_ui_text(f"Filter: {GRIM_MONO_FILTER_NAME}", 24, y, UI_TEXT_COLOR)
99
- y += self._ui_line_height(0.9) + 6
81
+ draw_ui_text(self._small, f"Filter: {GRIM_MONO_FILTER_NAME}", 24, y, scale=UI_TEXT_SCALE, color=UI_TEXT_COLOR)
82
+ y += ui_line_height(self._small, scale=0.9) + 6
100
83
  mono_scale = quest_title_base_scale(rl.get_screen_width())
101
84
  draw_grim_mono_text(self._grim_mono, self._sample, 24, y, mono_scale, rl.WHITE)
102
85
  y += int(measure_grim_mono_text_height(self._grim_mono, self._sample, mono_scale)) + 20
crimson/views/ground.py CHANGED
@@ -13,10 +13,11 @@ from grim.terrain_render import GroundRenderer
13
13
  from ..paths import default_runtime_dir
14
14
  from ..quests import all_quests
15
15
  from ..quests.types import QuestDefinition
16
+ from ._ui_helpers import draw_ui_text
16
17
  from .quest_title_overlay import draw_quest_title_overlay
17
18
  from .registry import register_view
18
19
  from grim.fonts.grim_mono import GrimMonoFont, load_grim_mono_font
19
- from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
20
+ from grim.fonts.small import SmallFontData, load_small_font
20
21
  from grim.view import View, ViewContext
21
22
 
22
23
 
@@ -60,24 +61,6 @@ class GroundView:
60
61
  self._fx_queue_rotated = FxQueueRotated()
61
62
  self._fx_textures: FxQueueTextures | None = None
62
63
 
63
- def _ui_line_height(self, scale: float = UI_TEXT_SCALE) -> int:
64
- if self._small is not None:
65
- return int(self._small.cell_size * scale)
66
- return int(20 * scale)
67
-
68
- def _draw_ui_text(
69
- self,
70
- text: str,
71
- x: float,
72
- y: float,
73
- color: rl.Color,
74
- scale: float = UI_TEXT_SCALE,
75
- ) -> None:
76
- if self._small is not None:
77
- draw_small_text(self._small, text, x, y, scale, color)
78
- else:
79
- rl.draw_text(text, int(x), int(y), int(20 * scale), color)
80
-
81
64
  def open(self) -> None:
82
65
  self._missing_assets.clear()
83
66
  self._small = load_small_font(self._assets_root, self._missing_assets)
@@ -179,10 +162,10 @@ class GroundView:
179
162
  rl.clear_background(rl.Color(12, 12, 14, 255))
180
163
  if self._missing_assets:
181
164
  message = "Missing assets: " + ", ".join(self._missing_assets)
182
- self._draw_ui_text(message, 24, 24, UI_ERROR_COLOR)
165
+ draw_ui_text(self._small, message, 24, 24, scale=UI_TEXT_SCALE, color=UI_ERROR_COLOR)
183
166
  return
184
167
  if self._renderer is None:
185
- self._draw_ui_text("Ground renderer not initialized.", 24, 24, UI_ERROR_COLOR)
168
+ draw_ui_text(self._small, "Ground renderer not initialized.", 24, 24, scale=UI_TEXT_SCALE, color=UI_ERROR_COLOR)
186
169
  return
187
170
  self._renderer.draw(self._camera_x, self._camera_y)
188
171
  self._draw_quest_title_overlay()