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
crimson/ui/cursor.py ADDED
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+
5
+ import pyray as rl
6
+
7
+ from ..effects_atlas import effect_src_rect
8
+
9
+ CURSOR_EFFECT_ID = 0x0D
10
+
11
+
12
+ def _clamp01(value: float) -> float:
13
+ if value < 0.0:
14
+ return 0.0
15
+ if value > 1.0:
16
+ return 1.0
17
+ return value
18
+
19
+
20
+ def draw_cursor_glow(
21
+ particles: rl.Texture | None,
22
+ *,
23
+ x: float,
24
+ y: float,
25
+ pulse_time: float | None = None,
26
+ effect_id: int = CURSOR_EFFECT_ID,
27
+ ) -> None:
28
+ if particles is None:
29
+ return
30
+ src = effect_src_rect(
31
+ int(effect_id),
32
+ texture_width=float(particles.width),
33
+ texture_height=float(particles.height),
34
+ )
35
+ if src is None:
36
+ return
37
+
38
+ src_rect = rl.Rectangle(src[0], src[1], src[2], src[3])
39
+ origin = rl.Vector2(0.0, 0.0)
40
+
41
+ rl.begin_blend_mode(rl.BLEND_ADDITIVE)
42
+ if pulse_time is None:
43
+ dst = rl.Rectangle(float(x - 32.0), float(y - 32.0), 64.0, 64.0)
44
+ rl.draw_texture_pro(particles, src_rect, dst, origin, 0.0, rl.WHITE)
45
+ else:
46
+ alpha = (math.pow(2.0, math.sin(float(pulse_time))) + 2.0) * 0.32
47
+ alpha = _clamp01(alpha)
48
+ tint = rl.Color(255, 255, 255, int(alpha * 255.0 + 0.5))
49
+ for dx, dy, size in (
50
+ (-28.0, -28.0, 64.0),
51
+ (-10.0, -18.0, 64.0),
52
+ (-18.0, -10.0, 64.0),
53
+ (-48.0, -48.0, 128.0),
54
+ ):
55
+ dst = rl.Rectangle(float(x + dx), float(y + dy), float(size), float(size))
56
+ rl.draw_texture_pro(particles, src_rect, dst, origin, 0.0, tint)
57
+ rl.end_blend_mode()
58
+
59
+
60
+ def draw_aim_cursor(
61
+ particles: rl.Texture | None,
62
+ aim: rl.Texture | None,
63
+ *,
64
+ x: float,
65
+ y: float,
66
+ ) -> None:
67
+ draw_cursor_glow(particles, x=x, y=y)
68
+ if aim is None:
69
+ color = rl.Color(235, 235, 235, 220)
70
+ rl.draw_circle_lines(int(x), int(y), 10, color)
71
+ rl.draw_line(int(x - 14.0), int(y), int(x - 6.0), int(y), color)
72
+ rl.draw_line(int(x + 6.0), int(y), int(x + 14.0), int(y), color)
73
+ rl.draw_line(int(x), int(y - 14.0), int(x), int(y - 6.0), color)
74
+ rl.draw_line(int(x), int(y + 6.0), int(x), int(y + 14.0), color)
75
+ return
76
+ src = rl.Rectangle(0.0, 0.0, float(aim.width), float(aim.height))
77
+ dst = rl.Rectangle(float(x - 10.0), float(y - 10.0), 20.0, 20.0)
78
+ rl.draw_texture_pro(aim, src, dst, rl.Vector2(0.0, 0.0), 0.0, rl.WHITE)
79
+
80
+
81
+ def draw_menu_cursor(
82
+ particles: rl.Texture | None,
83
+ cursor: rl.Texture | None,
84
+ *,
85
+ x: float,
86
+ y: float,
87
+ pulse_time: float,
88
+ ) -> None:
89
+ draw_cursor_glow(particles, x=x, y=y, pulse_time=pulse_time)
90
+ if cursor is None:
91
+ return
92
+ src = rl.Rectangle(0.0, 0.0, float(cursor.width), float(cursor.height))
93
+ dst = rl.Rectangle(float(x - 2.0), float(y - 2.0), 32.0, 32.0)
94
+ rl.draw_texture_pro(cursor, src, dst, rl.Vector2(0.0, 0.0), 0.0, rl.WHITE)
95
+
@@ -0,0 +1,235 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import pyray as rl
6
+
7
+ from grim.assets import PaqTextureCache
8
+ from grim.fonts.small import SmallFontData, draw_small_text, load_small_font, measure_small_text_width
9
+
10
+ from ..demo_trial import DemoTrialOverlayInfo
11
+ from .perk_menu import (
12
+ PerkMenuAssets,
13
+ UiButtonState,
14
+ button_draw,
15
+ button_update,
16
+ button_width,
17
+ cursor_draw,
18
+ wrap_ui_text,
19
+ )
20
+
21
+ DEMO_PURCHASE_URL = "http://buy.crimsonland.com"
22
+
23
+
24
+ def _clamp(value: float, lo: float, hi: float) -> float:
25
+ if value < lo:
26
+ return lo
27
+ if value > hi:
28
+ return hi
29
+ return value
30
+
31
+
32
+ class DemoTrialOverlayUi:
33
+ def __init__(self, assets_root: Path) -> None:
34
+ self._assets_root = assets_root
35
+ self._cache: PaqTextureCache | None = None
36
+
37
+ self._missing_assets: list[str] = []
38
+ self._font: SmallFontData | None = None
39
+ self._assets: PerkMenuAssets | None = None
40
+ self._cl_logo: rl.Texture2D | None = None
41
+
42
+ self._purchase_button = UiButtonState("Purchase", force_wide=True)
43
+ self._maybe_later_button = UiButtonState("Maybe later", force_wide=True)
44
+
45
+ def bind_cache(self, cache: PaqTextureCache | None) -> None:
46
+ self._cache = cache
47
+
48
+ def close(self) -> None:
49
+ if self._font is not None:
50
+ rl.unload_texture(self._font.texture)
51
+ self._font = None
52
+ self._assets = None
53
+ self._cl_logo = None
54
+
55
+ def _ensure_loaded(self) -> None:
56
+ if self._font is None:
57
+ self._missing_assets.clear()
58
+ try:
59
+ self._font = load_small_font(self._assets_root, self._missing_assets)
60
+ except Exception:
61
+ self._font = None
62
+
63
+ cache = self._cache
64
+ if cache is None:
65
+ return
66
+
67
+ if self._assets is None:
68
+ cursor = cache.get_or_load("ui_cursor", "ui/ui_cursor.jaz").texture
69
+ button_md = cache.get_or_load("ui_button_md", "ui/ui_button_145x32.jaz").texture
70
+ self._assets = PerkMenuAssets(
71
+ menu_panel=None,
72
+ title_pick_perk=None,
73
+ title_level_up=None,
74
+ menu_item=None,
75
+ button_sm=None,
76
+ button_md=button_md,
77
+ cursor=cursor,
78
+ aim=None,
79
+ missing=[],
80
+ )
81
+
82
+ if self._cl_logo is None:
83
+ self._cl_logo = cache.get_or_load("cl_logo", "load/logo_crimsonland.tga").texture
84
+
85
+ @staticmethod
86
+ def _panel_xy(*, screen_w: float, screen_h: float) -> tuple[float, float]:
87
+ return screen_w * 0.5 - 256.0, screen_h * 0.5 - 128.0
88
+
89
+ def update(self, dt_ms: int) -> str | None:
90
+ self._ensure_loaded()
91
+
92
+ dt_ms = max(0, int(dt_ms))
93
+ mouse = rl.get_mouse_position()
94
+ screen_w = float(rl.get_screen_width())
95
+ screen_h = float(rl.get_screen_height())
96
+ mouse.x = _clamp(float(mouse.x), 0.0, max(0.0, screen_w - 1.0))
97
+ mouse.y = _clamp(float(mouse.y), 0.0, max(0.0, screen_h - 1.0))
98
+
99
+ click = rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT)
100
+ panel_x, panel_y = self._panel_xy(screen_w=screen_w, screen_h=screen_h)
101
+
102
+ font = self._font
103
+ scale = 1.0
104
+ button_w = button_width(font, self._purchase_button.label, scale=scale, force_wide=True)
105
+ gap = 20.0
106
+ row_w = button_w * 2.0 + gap
107
+ base_x = panel_x + 256.0 - row_w * 0.5
108
+ button_y = panel_y + 214.0
109
+
110
+ purchase_clicked = button_update(
111
+ self._purchase_button,
112
+ x=float(base_x),
113
+ y=float(button_y),
114
+ width=float(button_w),
115
+ dt_ms=float(dt_ms),
116
+ mouse=mouse,
117
+ click=bool(click),
118
+ )
119
+ maybe_clicked = button_update(
120
+ self._maybe_later_button,
121
+ x=float(base_x + button_w + gap),
122
+ y=float(button_y),
123
+ width=float(button_w),
124
+ dt_ms=float(dt_ms),
125
+ mouse=mouse,
126
+ click=bool(click),
127
+ )
128
+
129
+ if purchase_clicked:
130
+ return "purchase"
131
+ if maybe_clicked:
132
+ return "maybe_later"
133
+ return None
134
+
135
+ def _draw_text_block(self, text: str, *, x: float, y: float, width: float, scale: float) -> float:
136
+ font = self._font
137
+ lines = wrap_ui_text(font, text, max_width=float(width), scale=float(scale))
138
+ line_h = (font.cell_size if font is not None else 16) * scale
139
+ color = rl.Color(220, 220, 220, 255)
140
+ y_pos = float(y)
141
+ for line in lines:
142
+ if font is not None:
143
+ draw_small_text(font, line, float(x), y_pos, float(scale), color)
144
+ else:
145
+ rl.draw_text(line, int(x), int(y_pos), int(20 * scale), color)
146
+ y_pos += float(line_h)
147
+ return y_pos
148
+
149
+ def draw(self, info: DemoTrialOverlayInfo) -> None:
150
+ if not info.visible:
151
+ return
152
+ self._ensure_loaded()
153
+
154
+ screen_w = float(rl.get_screen_width())
155
+ screen_h = float(rl.get_screen_height())
156
+ panel_x, panel_y = self._panel_xy(screen_w=screen_w, screen_h=screen_h)
157
+
158
+ rl.draw_rectangle(0, 0, int(screen_w), int(screen_h), rl.Color(0, 0, 0, 180))
159
+ rl.draw_rectangle(int(panel_x), int(panel_y), 512, 256, rl.Color(18, 18, 22, 230))
160
+ rl.draw_rectangle_lines(int(panel_x), int(panel_y), 512, 256, rl.Color(255, 255, 255, 255))
161
+
162
+ logo = self._cl_logo
163
+ if logo is not None:
164
+ src = rl.Rectangle(0.0, 0.0, float(logo.width), float(logo.height))
165
+ dst = rl.Rectangle(panel_x + 72.0, panel_y + 22.0, 371.2, 46.4)
166
+ rl.draw_texture_pro(logo, src, dst, rl.Vector2(0.0, 0.0), 0.0, rl.WHITE)
167
+
168
+ font = self._font
169
+ header = "You have been playing the Demo version of Crimsonland."
170
+ if font is not None:
171
+ draw_small_text(font, header, panel_x + 28.0, panel_y + 9.0, 1.0, rl.Color(220, 220, 220, 255))
172
+ else:
173
+ rl.draw_text(header, int(panel_x + 28.0), int(panel_y + 9.0), 16, rl.Color(220, 220, 220, 255))
174
+
175
+ body = ""
176
+ if info.kind == "quest_tier_limit":
177
+ body = (
178
+ "You have completed all Quest mode levels.\n"
179
+ f"However, you still have {info.remaining_label} time left.\n\n"
180
+ "If you would like to have unlimited play time and access to all features,\n"
181
+ "please upgrade to the full version.\n\n"
182
+ "Buy the full version to gain unrestricted game modes and be able to post your high scores online.\n"
183
+ "Buy it now. You'll have a great time."
184
+ )
185
+ elif info.kind == "quest_grace_left":
186
+ body = (
187
+ "You have used up your play time in the Demo version.\n"
188
+ f"You have {info.remaining_label} time left to play Quest mode.\n\n"
189
+ "If you would like to have unlimited play time and access to all features,\n"
190
+ "please upgrade to the full version.\n\n"
191
+ "Buy the full version to gain unrestricted game modes and be able to post your high scores online.\n"
192
+ "Buy it now. You'll have a great time."
193
+ )
194
+ else:
195
+ body = (
196
+ "Trial time is up.\n\n"
197
+ "If you would like all features, please upgrade to the full version.\n"
198
+ "Purchasing is very easy and takes just minutes.\n\n"
199
+ "Buy the full version to gain unrestricted game modes and be able to post your high scores online.\n"
200
+ "Buy it now. You'll have a great time."
201
+ )
202
+
203
+ text_x = panel_x + 26.0
204
+ text_y = panel_y + 78.0
205
+ text_w = 512.0 - 52.0
206
+ self._draw_text_block(body, x=text_x, y=text_y, width=text_w, scale=1.0)
207
+
208
+ assets = self._assets
209
+ if assets is not None:
210
+ scale = 1.0
211
+ button_w = 145.0 * scale
212
+ gap = 20.0
213
+ row_w = button_w * 2.0 + gap
214
+ base_x = panel_x + 256.0 - row_w * 0.5
215
+ button_y = panel_y + 214.0
216
+ button_draw(
217
+ assets,
218
+ font,
219
+ self._purchase_button,
220
+ x=float(base_x),
221
+ y=float(button_y),
222
+ width=float(button_w),
223
+ scale=float(scale),
224
+ )
225
+ button_draw(
226
+ assets,
227
+ font,
228
+ self._maybe_later_button,
229
+ x=float(base_x + button_w + gap),
230
+ y=float(button_y),
231
+ width=float(button_w),
232
+ scale=float(scale),
233
+ )
234
+ cursor_draw(assets, mouse=rl.get_mouse_position(), scale=1.0, alpha=1.0)
235
+