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,314 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ import math
5
+ from pathlib import Path
6
+
7
+ import pyray as rl
8
+
9
+ from grim.assets import resolve_asset_path
10
+ from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
11
+ from grim.view import View, ViewContext
12
+ from .registry import register_view
13
+
14
+
15
+ WORLD_SIZE = 1024.0
16
+ WINDOW_W = 640
17
+ WINDOW_H = 480
18
+ GRID_STEP = 64.0
19
+
20
+ SPRITE_GRID = 8
21
+ SPRITE_PAD_PX = 2.0
22
+ LEG_FRAME_COUNT = 15
23
+
24
+ UI_TEXT_SCALE = 1.0
25
+ UI_TEXT_COLOR = rl.Color(220, 220, 220, 255)
26
+ UI_HINT_COLOR = rl.Color(140, 140, 140, 255)
27
+ UI_ERROR_COLOR = rl.Color(240, 80, 80, 255)
28
+
29
+
30
+ @dataclass(slots=True)
31
+ class PlayerSpriteAssets:
32
+ trooper: rl.Texture
33
+
34
+
35
+ class PlayerSpriteDebugView:
36
+ def __init__(self, ctx: ViewContext) -> None:
37
+ self._assets_root = ctx.assets_dir
38
+ self._missing_assets: list[str] = []
39
+ self._small: SmallFontData | None = None
40
+ self._assets: PlayerSpriteAssets | None = None
41
+
42
+ self._player_x = WORLD_SIZE * 0.5
43
+ self._player_y = WORLD_SIZE * 0.5
44
+ self._player_size = 50.0
45
+ self._move_phase = 0.0
46
+ self._move_heading = 0.0
47
+ self._aim_heading = 0.0
48
+ self._muzzle_flash = 0.0
49
+
50
+ self._leg_base = 0
51
+ self._torso_base = 16
52
+ self._frame_count = LEG_FRAME_COUNT
53
+ self._show_grid = True
54
+ self._show_shadow = True
55
+ self._use_torso_offset = True
56
+
57
+ def _ui_line_height(self, scale: float = UI_TEXT_SCALE) -> int:
58
+ if self._small is not None:
59
+ return int(self._small.cell_size * scale)
60
+ return int(20 * scale)
61
+
62
+ def _draw_ui_text(
63
+ self,
64
+ text: str,
65
+ x: float,
66
+ y: float,
67
+ color: rl.Color,
68
+ scale: float = UI_TEXT_SCALE,
69
+ ) -> None:
70
+ if self._small is not None:
71
+ draw_small_text(self._small, text, x, y, scale, color)
72
+ else:
73
+ rl.draw_text(text, int(x), int(y), int(20 * scale), color)
74
+
75
+ def _frame_src(self, texture: rl.Texture, frame_index: int) -> rl.Rectangle:
76
+ cell = float(texture.width) / float(SPRITE_GRID)
77
+ pad = SPRITE_PAD_PX
78
+ frame = max(0, int(frame_index)) % (SPRITE_GRID * SPRITE_GRID)
79
+ col = frame % SPRITE_GRID
80
+ row = frame // SPRITE_GRID
81
+ u0 = col * cell + pad
82
+ v0 = row * cell + pad
83
+ size = cell - pad * 2.0
84
+ return rl.Rectangle(u0, v0, size, size)
85
+
86
+ def _draw_sprite(
87
+ self,
88
+ texture: rl.Texture,
89
+ frame_index: int,
90
+ *,
91
+ x: float,
92
+ y: float,
93
+ size: float,
94
+ rotation_rad: float,
95
+ tint: rl.Color,
96
+ shadow: bool,
97
+ offset_x: float = 0.0,
98
+ offset_y: float = 0.0,
99
+ ) -> None:
100
+ src = self._frame_src(texture, frame_index)
101
+ origin = rl.Vector2(size * 0.5, size * 0.5)
102
+ rotation_deg = float(rotation_rad * 57.29577951308232)
103
+
104
+ if shadow:
105
+ shadow_color = rl.Color(0, 0, 0, 90)
106
+ dst = rl.Rectangle(x + offset_x + 1.0, y + offset_y + 1.0, size, size)
107
+ rl.draw_texture_pro(texture, src, dst, origin, rotation_deg, shadow_color)
108
+
109
+ dst = rl.Rectangle(x + offset_x, y + offset_y, size, size)
110
+ rl.draw_texture_pro(texture, src, dst, origin, rotation_deg, tint)
111
+
112
+ def open(self) -> None:
113
+ rl.set_window_size(WINDOW_W, WINDOW_H)
114
+ self._missing_assets.clear()
115
+ self._small = load_small_font(self._assets_root, self._missing_assets)
116
+ trooper_path = resolve_asset_path(self._assets_root, "game/trooper.png")
117
+ if trooper_path is None:
118
+ self._missing_assets.append("game/trooper.png")
119
+ if self._missing_assets:
120
+ raise FileNotFoundError("Missing assets: " + ", ".join(self._missing_assets))
121
+ trooper = rl.load_texture(str(trooper_path))
122
+ self._assets = PlayerSpriteAssets(trooper=trooper)
123
+
124
+ def close(self) -> None:
125
+ if self._assets is not None:
126
+ rl.unload_texture(self._assets.trooper)
127
+ self._assets = None
128
+ if self._small is not None:
129
+ rl.unload_texture(self._small.texture)
130
+ self._small = None
131
+
132
+ def update(self, dt: float) -> None:
133
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_F1):
134
+ self._show_grid = not self._show_grid
135
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_F2):
136
+ self._show_shadow = not self._show_shadow
137
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_F3):
138
+ self._use_torso_offset = not self._use_torso_offset
139
+
140
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_LEFT_BRACKET):
141
+ self._torso_base = max(0, self._torso_base - 1)
142
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_RIGHT_BRACKET):
143
+ self._torso_base = min(63, self._torso_base + 1)
144
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_SEMICOLON):
145
+ self._leg_base = max(0, self._leg_base - 1)
146
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_APOSTROPHE):
147
+ self._leg_base = min(63, self._leg_base + 1)
148
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_COMMA):
149
+ self._frame_count = max(1, self._frame_count - 1)
150
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_PERIOD):
151
+ self._frame_count = min(64, self._frame_count + 1)
152
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_R):
153
+ self._player_x = WORLD_SIZE * 0.5
154
+ self._player_y = WORLD_SIZE * 0.5
155
+ self._move_phase = 0.0
156
+ self._move_heading = 0.0
157
+
158
+ move_x = 0.0
159
+ move_y = 0.0
160
+ if rl.is_key_down(rl.KeyboardKey.KEY_A):
161
+ move_x -= 1.0
162
+ if rl.is_key_down(rl.KeyboardKey.KEY_D):
163
+ move_x += 1.0
164
+ if rl.is_key_down(rl.KeyboardKey.KEY_W):
165
+ move_y -= 1.0
166
+ if rl.is_key_down(rl.KeyboardKey.KEY_S):
167
+ move_y += 1.0
168
+
169
+ moving = move_x != 0.0 or move_y != 0.0
170
+ if moving:
171
+ length = math.hypot(move_x, move_y)
172
+ if length > 0.0:
173
+ move_x /= length
174
+ move_y /= length
175
+ speed = 120.0
176
+ if rl.is_key_down(rl.KeyboardKey.KEY_LEFT_SHIFT) or rl.is_key_down(rl.KeyboardKey.KEY_RIGHT_SHIFT):
177
+ speed *= 2.0
178
+ self._player_x += move_x * speed * dt
179
+ self._player_y += move_y * speed * dt
180
+ self._player_x = max(0.0, min(WORLD_SIZE, self._player_x))
181
+ self._player_y = max(0.0, min(WORLD_SIZE, self._player_y))
182
+ self._move_heading = math.atan2(move_y, move_x) + math.pi / 2.0
183
+
184
+ move_speed = 2.0
185
+ self._move_phase += dt * move_speed * 19.0
186
+ while self._move_phase > 14.0:
187
+ self._move_phase -= 14.0
188
+
189
+ cam_x = float(rl.get_screen_width()) * 0.5 - self._player_x
190
+ cam_y = float(rl.get_screen_height()) * 0.5 - self._player_y
191
+ mouse = rl.get_mouse_position()
192
+ aim_world_x = float(mouse.x) - cam_x
193
+ aim_world_y = float(mouse.y) - cam_y
194
+ aim_dx = aim_world_x - self._player_x
195
+ aim_dy = aim_world_y - self._player_y
196
+ if abs(aim_dx) > 1e-3 or abs(aim_dy) > 1e-3:
197
+ self._aim_heading = math.atan2(aim_dy, aim_dx) + math.pi / 2.0
198
+
199
+ if rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT):
200
+ self._muzzle_flash = 0.8
201
+ else:
202
+ self._muzzle_flash = max(0.0, self._muzzle_flash - dt * 2.5)
203
+
204
+ def draw(self) -> None:
205
+ rl.clear_background(rl.Color(10, 10, 12, 255))
206
+ if self._assets is None:
207
+ self._draw_ui_text("Trooper sprite not loaded.", 16, 16, UI_ERROR_COLOR)
208
+ return
209
+
210
+ cam_x = float(rl.get_screen_width()) * 0.5 - self._player_x
211
+ cam_y = float(rl.get_screen_height()) * 0.5 - self._player_y
212
+
213
+ if self._show_grid:
214
+ grid_major = rl.Color(70, 80, 95, 180)
215
+ grid_minor = rl.Color(40, 50, 65, 140)
216
+ for i in range(0, int(WORLD_SIZE) + 1, int(GRID_STEP)):
217
+ color = grid_major if i % 256 == 0 else grid_minor
218
+ sx = float(i) + cam_x
219
+ sy0 = cam_y
220
+ sy1 = WORLD_SIZE + cam_y
221
+ rl.draw_line(int(sx), int(sy0), int(sx), int(sy1), color)
222
+ sy = float(i) + cam_y
223
+ sx0 = cam_x
224
+ sx1 = WORLD_SIZE + cam_x
225
+ rl.draw_line(int(sx0), int(sy), int(sx1), int(sy), color)
226
+
227
+ px = self._player_x + cam_x
228
+ py = self._player_y + cam_y
229
+
230
+ frame = int(self._move_phase + 0.5)
231
+ if self._frame_count > 0:
232
+ frame %= self._frame_count
233
+ leg_frame = self._leg_base + frame
234
+ torso_frame = leg_frame
235
+ if self._use_torso_offset:
236
+ torso_frame = self._torso_base + frame
237
+
238
+ recoil_dir = self._aim_heading + math.pi / 2.0
239
+ recoil_offset = self._muzzle_flash * 12.0
240
+ torso_offset_x = math.cos(recoil_dir) * recoil_offset
241
+ torso_offset_y = math.sin(recoil_dir) * recoil_offset
242
+
243
+ tint = rl.Color(240, 240, 255, 255)
244
+ self._draw_sprite(
245
+ self._assets.trooper,
246
+ leg_frame,
247
+ x=px,
248
+ y=py,
249
+ size=self._player_size,
250
+ rotation_rad=self._move_heading,
251
+ tint=tint,
252
+ shadow=self._show_shadow,
253
+ )
254
+ self._draw_sprite(
255
+ self._assets.trooper,
256
+ torso_frame,
257
+ x=px,
258
+ y=py,
259
+ size=self._player_size,
260
+ rotation_rad=self._aim_heading,
261
+ tint=tint,
262
+ shadow=self._show_shadow,
263
+ offset_x=torso_offset_x,
264
+ offset_y=torso_offset_y,
265
+ )
266
+
267
+ # Aim/debug helpers.
268
+ mouse = rl.get_mouse_position()
269
+ rl.draw_circle(int(mouse.x), int(mouse.y), 3.0, rl.Color(120, 200, 255, 255))
270
+ rl.draw_line(int(px), int(py), int(mouse.x), int(mouse.y), rl.Color(120, 200, 255, 180))
271
+
272
+ hud_x = 16.0
273
+ hud_y = 16.0
274
+ line = self._ui_line_height()
275
+ self._draw_ui_text(
276
+ f"legs frame={leg_frame} (base {self._leg_base}, count {self._frame_count})",
277
+ hud_x,
278
+ hud_y,
279
+ UI_TEXT_COLOR,
280
+ )
281
+ hud_y += line
282
+ torso_label = "offset" if self._use_torso_offset else "match"
283
+ self._draw_ui_text(
284
+ f"torso frame={torso_frame} (base {self._torso_base}, mode {torso_label})",
285
+ hud_x,
286
+ hud_y,
287
+ UI_TEXT_COLOR,
288
+ )
289
+ hud_y += line
290
+ self._draw_ui_text(
291
+ f"move_heading={self._move_heading:.2f} aim_heading={self._aim_heading:.2f}",
292
+ hud_x,
293
+ hud_y,
294
+ UI_TEXT_COLOR,
295
+ )
296
+ hud_y += line
297
+ self._draw_ui_text(
298
+ "WASD move, mouse aim, LMB recoil, F1 grid, F2 shadow, F3 torso mode",
299
+ hud_x,
300
+ hud_y,
301
+ UI_HINT_COLOR,
302
+ )
303
+ hud_y += line
304
+ self._draw_ui_text(
305
+ "[/] torso base, ;/' legs base, ,/. frame count, R reset",
306
+ hud_x,
307
+ hud_y,
308
+ UI_HINT_COLOR,
309
+ )
310
+
311
+
312
+ @register_view("player-sprite-debug", "Player sprite debug")
313
+ def build_player_sprite_debug_view(*, ctx: ViewContext) -> View:
314
+ return PlayerSpriteDebugView(ctx)