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,115 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import replace
4
+
5
+ from ..creatures.spawn import SpawnTemplateCall
6
+
7
+ from .types import SpawnEntry
8
+
9
+
10
+ def tick_quest_spawn_timeline(
11
+ entries: tuple[SpawnEntry, ...],
12
+ quest_spawn_timeline_ms: float,
13
+ frame_dt_ms: float,
14
+ *,
15
+ terrain_width: float,
16
+ creatures_none_active: bool,
17
+ no_creatures_timer_ms: float,
18
+ ) -> tuple[tuple[SpawnEntry, ...], bool, float, tuple[SpawnTemplateCall, ...]]:
19
+ """Advance quest spawn-table firing (pure model of `quest_spawn_timeline_update` / 0x00434250).
20
+
21
+ Returns:
22
+ (updated_entries, creatures_none_active, no_creatures_timer_ms, spawn_calls)
23
+ """
24
+ timeline_ms = float(quest_spawn_timeline_ms)
25
+ dt_ms = float(frame_dt_ms)
26
+
27
+ if not creatures_none_active:
28
+ no_creatures_timer_ms = 0.0
29
+ else:
30
+ no_creatures_timer_ms += dt_ms
31
+
32
+ force_spawn = creatures_none_active and 3000.0 < no_creatures_timer_ms and 0x6A4 < timeline_ms
33
+
34
+ start_idx: int | None = None
35
+ for idx, entry in enumerate(entries):
36
+ if entry.count <= 0:
37
+ continue
38
+ if float(entry.trigger_ms) < timeline_ms or force_spawn:
39
+ start_idx = idx
40
+ break
41
+
42
+ if start_idx is None:
43
+ return entries, creatures_none_active, no_creatures_timer_ms, ()
44
+
45
+ spawns: list[SpawnTemplateCall] = []
46
+ updated_entries = list(entries)
47
+
48
+ trigger_ms = entries[start_idx].trigger_ms
49
+ for idx in range(start_idx, len(entries)):
50
+ entry = entries[idx]
51
+ if entry.trigger_ms != trigger_ms:
52
+ break
53
+
54
+ base_x = float(entry.x)
55
+ base_y = float(entry.y)
56
+ offscreen_x = base_x < 0.0 or float(terrain_width) < base_x
57
+
58
+ for spawn_idx in range(int(entry.count)):
59
+ magnitude = float(spawn_idx * 0x28)
60
+ offset = magnitude if (spawn_idx & 1) == 0 else -magnitude
61
+ if offscreen_x:
62
+ pos = (base_x, base_y + offset)
63
+ else:
64
+ pos = (base_x + offset, base_y)
65
+ spawns.append(SpawnTemplateCall(template_id=entry.spawn_id, pos=pos, heading=float(entry.heading)))
66
+
67
+ if entry.count != 0:
68
+ updated_entries[idx] = replace(entry, count=0)
69
+
70
+ # After spawning, the original forces the "none active" flag off.
71
+ creatures_none_active = False
72
+
73
+ return tuple(updated_entries), creatures_none_active, no_creatures_timer_ms, tuple(spawns)
74
+
75
+
76
+ def quest_spawn_table_empty(entries: tuple[SpawnEntry, ...]) -> bool:
77
+ """Return True when all quest spawn entries are exhausted (count <= 0)."""
78
+ return all(entry.count <= 0 for entry in entries)
79
+
80
+
81
+ def tick_quest_mode_spawns(
82
+ entries: tuple[SpawnEntry, ...],
83
+ quest_spawn_timeline_ms: float,
84
+ frame_dt_ms: float,
85
+ *,
86
+ terrain_width: float,
87
+ creatures_none_active: bool,
88
+ no_creatures_timer_ms: float,
89
+ ) -> tuple[tuple[SpawnEntry, ...], float, bool, float, tuple[SpawnTemplateCall, ...]]:
90
+ """Advance quest-mode spawning (spawn timeline + table firing).
91
+
92
+ Modeled after the spawning portion of `quest_mode_update` (0x004070e0), which:
93
+ - Advances `quest_spawn_timeline` unless the quest is idle-complete (no creatures active and
94
+ the spawn table is empty).
95
+ - Calls `quest_spawn_timeline_update` to fire spawn entries.
96
+
97
+ Returns:
98
+ (updated_entries, quest_spawn_timeline_ms, creatures_none_active, no_creatures_timer_ms, spawn_calls)
99
+ """
100
+ timeline_ms = float(quest_spawn_timeline_ms)
101
+ dt_ms = float(frame_dt_ms)
102
+
103
+ if (not creatures_none_active) or (not quest_spawn_table_empty(entries)):
104
+ timeline_ms += dt_ms
105
+
106
+ entries, creatures_none_active, no_creatures_timer_ms, spawns = tick_quest_spawn_timeline(
107
+ entries,
108
+ quest_spawn_timeline_ms=timeline_ms,
109
+ frame_dt_ms=dt_ms,
110
+ terrain_width=terrain_width,
111
+ creatures_none_active=creatures_none_active,
112
+ no_creatures_timer_ms=no_creatures_timer_ms,
113
+ )
114
+
115
+ return entries, timeline_ms, creatures_none_active, no_creatures_timer_ms, spawns
@@ -0,0 +1,70 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Callable
5
+
6
+ from ..creatures.spawn import SpawnId
7
+
8
+
9
+ @dataclass(frozen=True, slots=True)
10
+ class QuestContext:
11
+ width: int
12
+ height: int
13
+ player_count: int
14
+
15
+
16
+ @dataclass(frozen=True, slots=True, kw_only=True)
17
+ class SpawnEntry:
18
+ x: float
19
+ y: float
20
+ heading: float
21
+ spawn_id: SpawnId
22
+ trigger_ms: int
23
+ count: int
24
+
25
+
26
+ QuestBuilder = Callable[..., list[SpawnEntry]]
27
+
28
+
29
+ def terrain_ids_for(level: str) -> tuple[int, int, int]:
30
+ tier_text, quest_text = level.split(".", 1)
31
+ tier = int(tier_text)
32
+ quest = int(quest_text)
33
+ if tier <= 4:
34
+ base = (tier - 1) * 2
35
+ alt = base + 1
36
+ if quest < 6:
37
+ return base, alt, base
38
+ return base, base, alt
39
+ return quest & 0x3, 1, 3
40
+
41
+
42
+ def terrain_id_for(level: str) -> int:
43
+ return terrain_ids_for(level)[0]
44
+
45
+
46
+ @dataclass(frozen=True, slots=True, kw_only=True)
47
+ class QuestDefinition:
48
+ level: str
49
+ title: str
50
+ builder: QuestBuilder
51
+ time_limit_ms: int
52
+ start_weapon_id: int
53
+ unlock_perk_id: int | None = None
54
+ unlock_weapon_id: int | None = None
55
+ terrain_id: int | None = None
56
+ terrain_ids: tuple[int, int, int] | None = None
57
+ builder_address: int | None = None
58
+
59
+ def __post_init__(self) -> None:
60
+ terrain_ids = self.terrain_ids
61
+ if terrain_ids is None:
62
+ terrain_ids = terrain_ids_for(self.level)
63
+ object.__setattr__(self, "terrain_ids", terrain_ids)
64
+ if self.terrain_id is None:
65
+ object.__setattr__(self, "terrain_id", terrain_ids[0])
66
+
67
+ @property
68
+ def level_key(self) -> tuple[int, int]:
69
+ tier_text, quest_text = self.level.split(".", 1)
70
+ return int(tier_text), int(quest_text)
@@ -0,0 +1 @@
1
+ from __future__ import annotations
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Callable
5
+
6
+ import pyray as rl
7
+
8
+ from crimson.effects import FxQueue, FxQueueRotated
9
+ from crimson.effects_atlas import effect_src_rect
10
+ from grim.terrain_render import GroundCorpseDecal, GroundDecal, GroundRenderer
11
+
12
+ __all__ = ["bake_fx_queues", "FxQueueTextures"]
13
+
14
+
15
+ @dataclass(frozen=True, slots=True)
16
+ class FxQueueTextures:
17
+ particles: rl.Texture
18
+ bodyset: rl.Texture
19
+
20
+
21
+ def _clamp8(value: float) -> int:
22
+ if value <= 0.0:
23
+ return 0
24
+ if value >= 1.0:
25
+ return 255
26
+ return int(value * 255.0 + 0.5)
27
+
28
+
29
+ def _rgba_to_color(rgba: tuple[float, float, float, float]) -> rl.Color:
30
+ return rl.Color(_clamp8(rgba[0]), _clamp8(rgba[1]), _clamp8(rgba[2]), _clamp8(rgba[3]))
31
+
32
+
33
+ def bake_fx_queues(
34
+ ground: GroundRenderer,
35
+ *,
36
+ fx_queue: FxQueue,
37
+ fx_queue_rotated: FxQueueRotated,
38
+ textures: FxQueueTextures,
39
+ corpse_frame_for_type: Callable[[int], int],
40
+ corpse_shadow: bool = True,
41
+ clear: bool = True,
42
+ ) -> tuple[bool, bool]:
43
+ """Bake queued terrain FX into the ground render target (port of `fx_queue_render`)."""
44
+
45
+ decals: list[GroundDecal] = []
46
+ for entry in fx_queue.iter_active():
47
+ src = effect_src_rect(
48
+ entry.effect_id,
49
+ texture_width=float(textures.particles.width),
50
+ texture_height=float(textures.particles.height),
51
+ )
52
+ if src is None:
53
+ continue
54
+ decals.append(
55
+ GroundDecal(
56
+ texture=textures.particles,
57
+ src=rl.Rectangle(*src),
58
+ x=entry.pos_x,
59
+ y=entry.pos_y,
60
+ width=entry.width,
61
+ height=entry.height,
62
+ rotation_rad=entry.rotation,
63
+ tint=_rgba_to_color((entry.color_r, entry.color_g, entry.color_b, entry.color_a)),
64
+ centered=True,
65
+ )
66
+ )
67
+
68
+ corpse_decals: list[GroundCorpseDecal] = []
69
+ for entry in fx_queue_rotated.iter_active():
70
+ corpse_decals.append(
71
+ GroundCorpseDecal(
72
+ bodyset_frame=corpse_frame_for_type(entry.creature_type_id),
73
+ top_left_x=entry.top_left_x,
74
+ top_left_y=entry.top_left_y,
75
+ size=entry.scale,
76
+ rotation_rad=entry.rotation,
77
+ tint=_rgba_to_color((entry.color_r, entry.color_g, entry.color_b, entry.color_a)),
78
+ )
79
+ )
80
+
81
+ baked_fx = ground.bake_decals(decals)
82
+ baked_corpses = ground.bake_corpse_decals(textures.bodyset, corpse_decals, shadow=corpse_shadow)
83
+
84
+ if clear:
85
+ fx_queue.clear()
86
+ fx_queue_rotated.clear()
87
+
88
+ return baked_fx, baked_corpses