crimsonland 0.1.0.dev5__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 (139) hide show
  1. crimson/__init__.py +24 -0
  2. crimson/assets_fetch.py +60 -0
  3. crimson/atlas.py +92 -0
  4. crimson/audio_router.py +155 -0
  5. crimson/bonuses.py +167 -0
  6. crimson/camera.py +75 -0
  7. crimson/cli.py +380 -0
  8. crimson/creatures/__init__.py +8 -0
  9. crimson/creatures/ai.py +186 -0
  10. crimson/creatures/anim.py +173 -0
  11. crimson/creatures/damage.py +103 -0
  12. crimson/creatures/runtime.py +1019 -0
  13. crimson/creatures/spawn.py +2871 -0
  14. crimson/debug.py +7 -0
  15. crimson/demo.py +1360 -0
  16. crimson/demo_trial.py +140 -0
  17. crimson/effects.py +1086 -0
  18. crimson/effects_atlas.py +73 -0
  19. crimson/frontend/__init__.py +1 -0
  20. crimson/frontend/assets.py +43 -0
  21. crimson/frontend/boot.py +424 -0
  22. crimson/frontend/menu.py +700 -0
  23. crimson/frontend/panels/__init__.py +1 -0
  24. crimson/frontend/panels/base.py +410 -0
  25. crimson/frontend/panels/controls.py +132 -0
  26. crimson/frontend/panels/mods.py +128 -0
  27. crimson/frontend/panels/options.py +409 -0
  28. crimson/frontend/panels/play_game.py +627 -0
  29. crimson/frontend/panels/stats.py +351 -0
  30. crimson/frontend/transitions.py +31 -0
  31. crimson/game.py +2533 -0
  32. crimson/game_modes.py +15 -0
  33. crimson/game_world.py +652 -0
  34. crimson/gameplay.py +2467 -0
  35. crimson/input_codes.py +176 -0
  36. crimson/modes/__init__.py +1 -0
  37. crimson/modes/base_gameplay_mode.py +219 -0
  38. crimson/modes/quest_mode.py +502 -0
  39. crimson/modes/rush_mode.py +300 -0
  40. crimson/modes/survival_mode.py +792 -0
  41. crimson/modes/tutorial_mode.py +648 -0
  42. crimson/modes/typo_mode.py +472 -0
  43. crimson/paths.py +23 -0
  44. crimson/perks.py +828 -0
  45. crimson/persistence/__init__.py +1 -0
  46. crimson/persistence/highscores.py +385 -0
  47. crimson/persistence/save_status.py +245 -0
  48. crimson/player_damage.py +77 -0
  49. crimson/projectiles.py +1133 -0
  50. crimson/quests/__init__.py +18 -0
  51. crimson/quests/helpers.py +147 -0
  52. crimson/quests/registry.py +49 -0
  53. crimson/quests/results.py +164 -0
  54. crimson/quests/runtime.py +91 -0
  55. crimson/quests/tier1.py +620 -0
  56. crimson/quests/tier2.py +652 -0
  57. crimson/quests/tier3.py +579 -0
  58. crimson/quests/tier4.py +721 -0
  59. crimson/quests/tier5.py +886 -0
  60. crimson/quests/timeline.py +115 -0
  61. crimson/quests/types.py +70 -0
  62. crimson/render/__init__.py +1 -0
  63. crimson/render/terrain_fx.py +88 -0
  64. crimson/render/world_renderer.py +1941 -0
  65. crimson/sim/__init__.py +1 -0
  66. crimson/sim/world_defs.py +67 -0
  67. crimson/sim/world_state.py +422 -0
  68. crimson/terrain_assets.py +19 -0
  69. crimson/tutorial/__init__.py +12 -0
  70. crimson/tutorial/timeline.py +291 -0
  71. crimson/typo/__init__.py +2 -0
  72. crimson/typo/names.py +233 -0
  73. crimson/typo/player.py +43 -0
  74. crimson/typo/spawns.py +73 -0
  75. crimson/typo/typing.py +52 -0
  76. crimson/ui/__init__.py +3 -0
  77. crimson/ui/cursor.py +95 -0
  78. crimson/ui/demo_trial_overlay.py +235 -0
  79. crimson/ui/game_over.py +660 -0
  80. crimson/ui/hud.py +601 -0
  81. crimson/ui/perk_menu.py +388 -0
  82. crimson/views/__init__.py +40 -0
  83. crimson/views/aim_debug.py +276 -0
  84. crimson/views/animations.py +274 -0
  85. crimson/views/arsenal_debug.py +404 -0
  86. crimson/views/audio_bootstrap.py +47 -0
  87. crimson/views/bonuses.py +201 -0
  88. crimson/views/camera_debug.py +359 -0
  89. crimson/views/camera_shake.py +229 -0
  90. crimson/views/corpse_stamp_debug.py +324 -0
  91. crimson/views/decals_debug.py +739 -0
  92. crimson/views/empty.py +19 -0
  93. crimson/views/fonts.py +114 -0
  94. crimson/views/game_over.py +117 -0
  95. crimson/views/ground.py +259 -0
  96. crimson/views/lighting_debug.py +1166 -0
  97. crimson/views/particles.py +293 -0
  98. crimson/views/perk_menu_debug.py +430 -0
  99. crimson/views/perks.py +398 -0
  100. crimson/views/player.py +434 -0
  101. crimson/views/player_sprite_debug.py +314 -0
  102. crimson/views/projectile_fx.py +609 -0
  103. crimson/views/projectile_render_debug.py +393 -0
  104. crimson/views/projectiles.py +221 -0
  105. crimson/views/quest_title_overlay.py +108 -0
  106. crimson/views/registry.py +34 -0
  107. crimson/views/rush.py +16 -0
  108. crimson/views/small_font_debug.py +204 -0
  109. crimson/views/spawn_plan.py +363 -0
  110. crimson/views/sprites.py +214 -0
  111. crimson/views/survival.py +15 -0
  112. crimson/views/terrain.py +132 -0
  113. crimson/views/ui.py +123 -0
  114. crimson/views/wicons.py +166 -0
  115. crimson/weapon_sfx.py +63 -0
  116. crimson/weapons.py +860 -0
  117. crimsonland-0.1.0.dev5.dist-info/METADATA +9 -0
  118. crimsonland-0.1.0.dev5.dist-info/RECORD +139 -0
  119. crimsonland-0.1.0.dev5.dist-info/WHEEL +4 -0
  120. crimsonland-0.1.0.dev5.dist-info/entry_points.txt +4 -0
  121. grim/__init__.py +20 -0
  122. grim/app.py +92 -0
  123. grim/assets.py +231 -0
  124. grim/audio.py +106 -0
  125. grim/config.py +294 -0
  126. grim/console.py +737 -0
  127. grim/fonts/__init__.py +7 -0
  128. grim/fonts/grim_mono.py +111 -0
  129. grim/fonts/small.py +120 -0
  130. grim/input.py +44 -0
  131. grim/jaz.py +103 -0
  132. grim/math.py +17 -0
  133. grim/music.py +403 -0
  134. grim/paq.py +76 -0
  135. grim/rand.py +37 -0
  136. grim/sfx.py +276 -0
  137. grim/sfx_map.py +103 -0
  138. grim/terrain_render.py +840 -0
  139. grim/view.py +16 -0
@@ -0,0 +1,293 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ import pyray as rl
6
+
7
+ from .registry import register_view
8
+ from ..effects_atlas import EFFECT_ID_ATLAS_TABLE, SIZE_CODE_GRID
9
+ from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
10
+ from grim.view import View, ViewContext
11
+
12
+ UI_TEXT_SCALE = 1.0
13
+ UI_TEXT_COLOR = rl.Color(220, 220, 220, 255)
14
+ UI_HINT_COLOR = rl.Color(140, 140, 140, 255)
15
+ UI_ERROR_COLOR = rl.Color(240, 80, 80, 255)
16
+ UI_KNOWN_COLOR = rl.Color(80, 160, 240, 255)
17
+ UI_HOVER_COLOR = rl.Color(240, 200, 80, 255)
18
+ UI_CLAMP_COLOR = rl.Color(120, 120, 220, 255)
19
+
20
+ EFFECT_UV_STEP = {
21
+ 2: 0.4921875,
22
+ 4: 0.2421875,
23
+ 8: 0.1171875,
24
+ 16: 0.0546875,
25
+ }
26
+
27
+
28
+ @dataclass(frozen=True, slots=True)
29
+ class EffectEntry:
30
+ effect_id: int
31
+ size_code: int
32
+ frame: int
33
+ label: str | None = None
34
+
35
+ @property
36
+ def grid(self) -> int:
37
+ return SIZE_CODE_GRID[self.size_code]
38
+
39
+
40
+ EFFECT_LABELS = {
41
+ 0x00: "burst",
42
+ 0x01: "ring",
43
+ 0x07: "blood",
44
+ 0x08: "freeze",
45
+ 0x09: "freeze",
46
+ 0x0A: "freeze",
47
+ 0x0C: "explosion",
48
+ 0x0E: "shatter",
49
+ 0x11: "explosion",
50
+ 0x12: "muzzle",
51
+ }
52
+
53
+ EFFECT_ENTRIES = [
54
+ EffectEntry(entry.effect_id, entry.size_code, entry.frame, EFFECT_LABELS.get(entry.effect_id))
55
+ for entry in EFFECT_ID_ATLAS_TABLE
56
+ ]
57
+
58
+
59
+ def _build_effect_map() -> dict[int, dict[int, list[EffectEntry]]]:
60
+ mapping: dict[int, dict[int, list[EffectEntry]]] = {}
61
+ for entry in EFFECT_ENTRIES:
62
+ grid_map = mapping.setdefault(entry.grid, {})
63
+ grid_map.setdefault(entry.frame, []).append(entry)
64
+ return mapping
65
+
66
+
67
+ EFFECT_BY_GRID = _build_effect_map()
68
+ GRID_CHOICES = (2, 4, 8, 16)
69
+
70
+
71
+ class ParticleView:
72
+ def __init__(self, ctx: ViewContext) -> None:
73
+ self._assets_root = ctx.assets_dir
74
+ self._missing_assets: list[str] = []
75
+ self._texture: rl.Texture | None = None
76
+ self._small: SmallFontData | None = None
77
+ self._grid = 8
78
+ self._show_uv_clamp = False
79
+
80
+ def _ui_line_height(self, scale: float = UI_TEXT_SCALE) -> int:
81
+ if self._small is not None:
82
+ return int(self._small.cell_size * scale)
83
+ return int(20 * scale)
84
+
85
+ def _draw_ui_text(
86
+ self,
87
+ text: str,
88
+ x: float,
89
+ y: float,
90
+ color: rl.Color,
91
+ scale: float = UI_TEXT_SCALE,
92
+ ) -> None:
93
+ if self._small is not None:
94
+ draw_small_text(self._small, text, x, y, scale, color)
95
+ else:
96
+ rl.draw_text(text, int(x), int(y), int(20 * scale), color)
97
+
98
+ def open(self) -> None:
99
+ self._missing_assets.clear()
100
+ self._small = load_small_font(self._assets_root, self._missing_assets)
101
+ path = self._assets_root / "crimson" / "game" / "particles.png"
102
+ if not path.is_file():
103
+ self._missing_assets.append("game/particles.png")
104
+ raise FileNotFoundError(f"Missing asset: {path}")
105
+ self._texture = rl.load_texture(str(path))
106
+
107
+ def close(self) -> None:
108
+ if self._texture is not None:
109
+ rl.unload_texture(self._texture)
110
+ self._texture = None
111
+ if self._small is not None:
112
+ rl.unload_texture(self._small.texture)
113
+ self._small = None
114
+
115
+ def update(self, dt: float) -> None:
116
+ del dt
117
+
118
+ def _cycle_grid(self, delta: int) -> None:
119
+ idx = GRID_CHOICES.index(self._grid)
120
+ self._grid = GRID_CHOICES[(idx + delta) % len(GRID_CHOICES)]
121
+
122
+ def _handle_input(self) -> None:
123
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_UP):
124
+ self._cycle_grid(1)
125
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_DOWN):
126
+ self._cycle_grid(-1)
127
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_TWO):
128
+ self._grid = 2
129
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_FOUR):
130
+ self._grid = 4
131
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_EIGHT):
132
+ self._grid = 8
133
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_ONE):
134
+ self._grid = 16
135
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_U):
136
+ self._show_uv_clamp = not self._show_uv_clamp
137
+
138
+ def draw(self) -> None:
139
+ rl.clear_background(rl.Color(12, 12, 14, 255))
140
+ if self._missing_assets:
141
+ message = "Missing assets: " + ", ".join(self._missing_assets)
142
+ self._draw_ui_text(message, 24, 24, UI_ERROR_COLOR)
143
+ return
144
+ if self._texture is None:
145
+ self._draw_ui_text("No particles texture loaded.", 24, 24, UI_TEXT_COLOR)
146
+ return
147
+
148
+ self._handle_input()
149
+
150
+ margin = 24
151
+ panel_gap = 32
152
+ panel_width = min(440, int(rl.get_screen_width() * 0.4))
153
+ available_width = rl.get_screen_width() - margin * 2 - panel_gap - panel_width
154
+ available_height = rl.get_screen_height() - margin * 2 - 60
155
+ scale = min(
156
+ 3.0,
157
+ available_width / self._texture.width,
158
+ available_height / self._texture.height,
159
+ )
160
+ draw_w = self._texture.width * scale
161
+ draw_h = self._texture.height * scale
162
+ x = margin
163
+ y = margin + 60
164
+
165
+ src = rl.Rectangle(0.0, 0.0, float(self._texture.width), float(self._texture.height))
166
+ dst = rl.Rectangle(float(x), float(y), float(draw_w), float(draw_h))
167
+ rl.draw_texture_pro(self._texture, src, dst, rl.Vector2(0.0, 0.0), 0.0, rl.WHITE)
168
+
169
+ grid = self._grid
170
+ cell_w = draw_w / grid
171
+ cell_h = draw_h / grid
172
+ step = EFFECT_UV_STEP.get(grid, 1.0 / grid)
173
+ sample_w = self._texture.width * step * scale
174
+ sample_h = self._texture.height * step * scale
175
+ for i in range(1, grid):
176
+ rl.draw_line(
177
+ int(x + i * cell_w),
178
+ int(y),
179
+ int(x + i * cell_w),
180
+ int(y + draw_h),
181
+ rl.Color(60, 60, 70, 255),
182
+ )
183
+ rl.draw_line(
184
+ int(x),
185
+ int(y + i * cell_h),
186
+ int(x + draw_w),
187
+ int(y + i * cell_h),
188
+ rl.Color(60, 60, 70, 255),
189
+ )
190
+
191
+ known_frames = EFFECT_BY_GRID.get(grid, {})
192
+ for frame_index in known_frames:
193
+ row = frame_index // grid
194
+ col = frame_index % grid
195
+ hl = rl.Rectangle(
196
+ float(x + col * cell_w),
197
+ float(y + row * cell_h),
198
+ float(cell_w),
199
+ float(cell_h),
200
+ )
201
+ rl.draw_rectangle_lines_ex(hl, 2, UI_KNOWN_COLOR)
202
+ if self._show_uv_clamp:
203
+ inset = rl.Rectangle(
204
+ float(x + col * cell_w),
205
+ float(y + row * cell_h),
206
+ float(sample_w),
207
+ float(sample_h),
208
+ )
209
+ rl.draw_rectangle_lines_ex(inset, 1, UI_CLAMP_COLOR)
210
+
211
+ hovered_index = None
212
+ mouse = rl.get_mouse_position()
213
+ if x <= mouse.x <= x + draw_w and y <= mouse.y <= y + draw_h:
214
+ col = int((mouse.x - x) // cell_w)
215
+ row = int((mouse.y - y) // cell_h)
216
+ if 0 <= col < grid and 0 <= row < grid:
217
+ hovered_index = row * grid + col
218
+ hl = rl.Rectangle(
219
+ float(x + col * cell_w),
220
+ float(y + row * cell_h),
221
+ float(cell_w),
222
+ float(cell_h),
223
+ )
224
+ rl.draw_rectangle_lines_ex(hl, 3, UI_HOVER_COLOR)
225
+ if self._show_uv_clamp:
226
+ inset = rl.Rectangle(
227
+ float(x + col * cell_w),
228
+ float(y + row * cell_h),
229
+ float(sample_w),
230
+ float(sample_h),
231
+ )
232
+ rl.draw_rectangle_lines_ex(inset, 1, UI_CLAMP_COLOR)
233
+
234
+ info_x = x + draw_w + panel_gap
235
+ info_y = margin
236
+ self._draw_ui_text(
237
+ f"particles.png (grid {grid}x{grid})",
238
+ info_x,
239
+ info_y,
240
+ UI_TEXT_COLOR,
241
+ )
242
+ info_y += self._ui_line_height() + 6
243
+ self._draw_ui_text(
244
+ "Up/Down: grid 2/4/8: direct 1: grid16 U: UV clamp",
245
+ info_x,
246
+ info_y,
247
+ UI_HINT_COLOR,
248
+ )
249
+ info_y += self._ui_line_height() + 12
250
+ if self._show_uv_clamp:
251
+ step_px = int(round(self._texture.width * step))
252
+ self._draw_ui_text(
253
+ f"UV clamp: {step_px}px of {int(self._texture.width / grid)}px",
254
+ info_x,
255
+ info_y,
256
+ UI_HINT_COLOR,
257
+ )
258
+ info_y += self._ui_line_height() + 12
259
+
260
+ if hovered_index is not None:
261
+ self._draw_ui_text(f"frame {hovered_index:02d}", info_x, info_y, UI_TEXT_COLOR)
262
+ info_y += self._ui_line_height() + 6
263
+ entries = known_frames.get(hovered_index, [])
264
+ if entries:
265
+ for entry in entries:
266
+ label = f" {entry.label}" if entry.label else ""
267
+ self._draw_ui_text(
268
+ f"0x{entry.effect_id:02x}{label}",
269
+ info_x,
270
+ info_y,
271
+ UI_TEXT_COLOR,
272
+ )
273
+ info_y += self._ui_line_height() + 4
274
+ else:
275
+ self._draw_ui_text("no known mapping", info_x, info_y, UI_HINT_COLOR)
276
+ info_y += self._ui_line_height() + 4
277
+ info_y += 8
278
+
279
+ self._draw_ui_text("Effect table", info_x, info_y, UI_TEXT_COLOR)
280
+ info_y += self._ui_line_height() + 6
281
+ for entry in EFFECT_ENTRIES:
282
+ grid_label = entry.grid
283
+ line = f"0x{entry.effect_id:02x} grid{grid_label} frame 0x{entry.frame:02x}"
284
+ if entry.label:
285
+ line += f" {entry.label}"
286
+ color = UI_TEXT_COLOR if entry.grid == grid else UI_HINT_COLOR
287
+ self._draw_ui_text(line, info_x, info_y, color)
288
+ info_y += self._ui_line_height() + 3
289
+
290
+
291
+ @register_view("particles", "Particle atlas preview")
292
+ def build_particle_view(ctx: ViewContext) -> View:
293
+ return ParticleView(ctx)