crimsonland 0.1.0.dev1__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 (138) 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 +153 -0
  5. crimson/bonuses.py +167 -0
  6. crimson/camera.py +75 -0
  7. crimson/cli.py +377 -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 +663 -0
  34. crimson/gameplay.py +2450 -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 +1039 -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 +1338 -0
  65. crimson/sim/__init__.py +1 -0
  66. crimson/sim/world_defs.py +56 -0
  67. crimson/sim/world_state.py +421 -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 +414 -0
  86. crimson/views/bonuses.py +201 -0
  87. crimson/views/camera_debug.py +359 -0
  88. crimson/views/camera_shake.py +229 -0
  89. crimson/views/corpse_stamp_debug.py +324 -0
  90. crimson/views/decals_debug.py +739 -0
  91. crimson/views/empty.py +19 -0
  92. crimson/views/fonts.py +114 -0
  93. crimson/views/game_over.py +117 -0
  94. crimson/views/ground.py +259 -0
  95. crimson/views/lighting_debug.py +1166 -0
  96. crimson/views/particles.py +293 -0
  97. crimson/views/perk_menu_debug.py +430 -0
  98. crimson/views/perks.py +398 -0
  99. crimson/views/player.py +433 -0
  100. crimson/views/player_sprite_debug.py +314 -0
  101. crimson/views/projectile_fx.py +608 -0
  102. crimson/views/projectile_render_debug.py +407 -0
  103. crimson/views/projectiles.py +221 -0
  104. crimson/views/quest_title_overlay.py +108 -0
  105. crimson/views/registry.py +34 -0
  106. crimson/views/rush.py +16 -0
  107. crimson/views/small_font_debug.py +204 -0
  108. crimson/views/spawn_plan.py +363 -0
  109. crimson/views/sprites.py +214 -0
  110. crimson/views/survival.py +15 -0
  111. crimson/views/terrain.py +132 -0
  112. crimson/views/ui.py +123 -0
  113. crimson/views/wicons.py +166 -0
  114. crimson/weapon_sfx.py +63 -0
  115. crimson/weapons.py +860 -0
  116. crimsonland-0.1.0.dev1.dist-info/METADATA +9 -0
  117. crimsonland-0.1.0.dev1.dist-info/RECORD +138 -0
  118. crimsonland-0.1.0.dev1.dist-info/WHEEL +4 -0
  119. crimsonland-0.1.0.dev1.dist-info/entry_points.txt +4 -0
  120. grim/__init__.py +20 -0
  121. grim/app.py +92 -0
  122. grim/assets.py +231 -0
  123. grim/audio.py +106 -0
  124. grim/config.py +294 -0
  125. grim/console.py +737 -0
  126. grim/fonts/__init__.py +7 -0
  127. grim/fonts/grim_mono.py +111 -0
  128. grim/fonts/small.py +120 -0
  129. grim/input.py +44 -0
  130. grim/jaz.py +103 -0
  131. grim/math.py +17 -0
  132. grim/music.py +403 -0
  133. grim/paq.py +76 -0
  134. grim/rand.py +37 -0
  135. grim/sfx.py +276 -0
  136. grim/sfx_map.py +103 -0
  137. grim/terrain_render.py +840 -0
  138. grim/view.py +16 -0
@@ -0,0 +1,173 @@
1
+ from __future__ import annotations
2
+
3
+ import struct
4
+
5
+ from .spawn import CreatureFlags
6
+
7
+
8
+ def _f32(value: float) -> float:
9
+ """Round-trip through float32 to match the game's stored float behavior."""
10
+ return struct.unpack("<f", struct.pack("<f", float(value)))[0]
11
+
12
+
13
+ def _u32(value: int) -> int:
14
+ return value & 0xFFFFFFFF
15
+
16
+
17
+ def _i32(value: int) -> int:
18
+ value &= 0xFFFFFFFF
19
+ if value & 0x80000000:
20
+ return value - 0x100000000
21
+ return value
22
+
23
+
24
+ _CREATURE_CORPSE_FRAMES: dict[int, int] = {
25
+ 0: 0, # zombie
26
+ 1: 3, # lizard
27
+ 2: 4, # alien
28
+ 3: 1, # spider sp1
29
+ 4: 2, # spider sp2
30
+ 5: 7, # trooper
31
+ }
32
+
33
+
34
+ def creature_corpse_frame_for_type(type_id: int) -> int:
35
+ """Resolve the bodyset frame index used for corpse decals (`fx_queue_render`)."""
36
+
37
+ return _CREATURE_CORPSE_FRAMES.get(int(type_id), int(type_id) & 0xF)
38
+
39
+
40
+ def creature_anim_is_long_strip(flags: CreatureFlags) -> bool:
41
+ # From creature_update_all / creature_render_type:
42
+ # long strip when (flags & 4) == 0 OR (flags & 0x40) != 0
43
+ return (flags & CreatureFlags.ANIM_PING_PONG) == 0 or (flags & CreatureFlags.ANIM_LONG_STRIP) != 0
44
+
45
+
46
+ def creature_anim_phase_step(
47
+ *,
48
+ anim_rate: float,
49
+ move_speed: float,
50
+ dt: float,
51
+ size: float,
52
+ local_scale: float = 1.0,
53
+ flags: CreatureFlags = CreatureFlags(0),
54
+ ai_mode: int = 0,
55
+ quantize_f32: bool = True,
56
+ ) -> float:
57
+ """Compute the per-frame animation phase increment (creature_update_all)."""
58
+ if size == 0.0:
59
+ return 0.0
60
+
61
+ if quantize_f32:
62
+ anim_rate = _f32(anim_rate)
63
+ move_speed = _f32(move_speed)
64
+ dt = _f32(dt)
65
+ size = _f32(size)
66
+ local_scale = _f32(local_scale)
67
+
68
+ speed_scale = (_f32(30.0) if quantize_f32 else 30.0) / size
69
+ strip_mul = _f32(25.0) if quantize_f32 else 25.0
70
+ if not creature_anim_is_long_strip(flags):
71
+ strip_mul = _f32(22.0) if quantize_f32 else 22.0
72
+ elif ai_mode == 7:
73
+ # Long-strip creatures stop advancing animation phase in ai_mode == 7.
74
+ return 0.0
75
+
76
+ step = anim_rate * move_speed * dt * speed_scale * local_scale * strip_mul
77
+ return _f32(step) if quantize_f32 else step
78
+
79
+
80
+ def creature_anim_advance_phase(
81
+ phase: float,
82
+ *,
83
+ anim_rate: float,
84
+ move_speed: float,
85
+ dt: float,
86
+ size: float,
87
+ local_scale: float = 1.0,
88
+ flags: CreatureFlags = CreatureFlags(0),
89
+ ai_mode: int = 0,
90
+ quantize_f32: bool = True,
91
+ ) -> tuple[float, float]:
92
+ """Advance anim_phase and wrap it the same way as creature_update_all.
93
+
94
+ Returns (new_phase, applied_step).
95
+ """
96
+ if quantize_f32:
97
+ phase = _f32(phase)
98
+
99
+ step = creature_anim_phase_step(
100
+ anim_rate=anim_rate,
101
+ move_speed=move_speed,
102
+ dt=dt,
103
+ size=size,
104
+ local_scale=local_scale,
105
+ flags=flags,
106
+ ai_mode=ai_mode,
107
+ quantize_f32=quantize_f32,
108
+ )
109
+ if step == 0.0:
110
+ return phase, 0.0
111
+
112
+ phase = phase + step
113
+ if quantize_f32:
114
+ phase = _f32(phase)
115
+
116
+ if creature_anim_is_long_strip(flags):
117
+ limit = _f32(31.0) if quantize_f32 else 31.0
118
+ while phase > limit:
119
+ phase = phase - limit
120
+ if quantize_f32:
121
+ phase = _f32(phase)
122
+ else:
123
+ limit = _f32(15.0) if quantize_f32 else 15.0
124
+ if phase > limit:
125
+ while phase > limit:
126
+ phase = phase - limit
127
+ if quantize_f32:
128
+ phase = _f32(phase)
129
+
130
+ return phase, step
131
+
132
+
133
+ def creature_anim_select_frame(
134
+ phase: float,
135
+ *,
136
+ base_frame: int,
137
+ mirror_long: bool,
138
+ flags: CreatureFlags = CreatureFlags(0),
139
+ ) -> tuple[int, bool, str]:
140
+ """Select an 8x8 atlas frame index (creature_render_type).
141
+
142
+ Returns (frame_index, mirror_applied, mode).
143
+
144
+ Note: mirror_applied refers to the long-strip ping-pong index mirroring
145
+ (frame = 0x1f - frame) when the per-type mirror flag is set, not a texture flip.
146
+ """
147
+ if creature_anim_is_long_strip(flags):
148
+ if phase < 0.0:
149
+ # Negative anim_phase is used as a special render state in the game; keep the
150
+ # same fallback frame selection.
151
+ frame = base_frame + 0x0F
152
+ mirrored = False
153
+ else:
154
+ # Matches __ftol(phase + 0.5f) used by the original binary.
155
+ frame = int(phase + 0.5)
156
+ mirrored = False
157
+ if mirror_long and frame > 0x0F:
158
+ frame = 0x1F - frame
159
+ mirrored = True
160
+ if flags & CreatureFlags.RANGED_ATTACK_SHOCK:
161
+ frame += 0x20
162
+ return frame, mirrored, "long"
163
+
164
+ # Ping-pong strip:
165
+ # idx = (__ftol(phase + 0.5f) & 0x8000000f); then normalize negatives; then mirror >7.
166
+ raw = int(phase + 0.5)
167
+ idx = _i32(_u32(raw) & 0x8000000F)
168
+ if idx < 0:
169
+ idx = _i32(_u32(((idx - 1) | 0xFFFFFFF0) + 1))
170
+ if idx > 7:
171
+ idx = 0x0F - idx
172
+ frame = base_frame + 0x10 + idx
173
+ return frame, False, "ping-pong"
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from typing import Callable
5
+
6
+ from ..gameplay import PlayerState, perk_active
7
+ from ..perks import PerkId
8
+ from .runtime import CREATURE_HITBOX_ALIVE, CreatureState
9
+ from .spawn import CreatureFlags
10
+
11
+
12
+ def _owner_id_to_player_index(owner_id: int) -> int | None:
13
+ if owner_id == -100:
14
+ return 0
15
+ if owner_id < 0:
16
+ return -1 - owner_id
17
+ return None
18
+
19
+
20
+ def creature_apply_damage(
21
+ creature: CreatureState,
22
+ *,
23
+ damage_amount: float,
24
+ damage_type: int,
25
+ impulse_x: float,
26
+ impulse_y: float,
27
+ owner_id: int,
28
+ dt: float,
29
+ players: list[PlayerState],
30
+ rand: Callable[[], int],
31
+ ) -> bool:
32
+ """Apply damage to a creature, returning True if the hit killed it.
33
+
34
+ This is a partial port of `creature_apply_damage` (FUN_004207c0).
35
+
36
+ Notes:
37
+ - Death side-effects are handled by the caller (see Phase 2 in `plan.md`).
38
+ - `damage_type` is a native integer category; call sites must supply it.
39
+ """
40
+
41
+ creature.last_hit_owner_id = int(owner_id)
42
+ creature.hit_flash_timer = 0.2
43
+
44
+ player_index = _owner_id_to_player_index(owner_id)
45
+ attacker = players[player_index] if player_index is not None and 0 <= player_index < len(players) else None
46
+
47
+ damage = float(damage_amount)
48
+
49
+ if int(damage_type) == 1 and attacker is not None:
50
+ if perk_active(attacker, PerkId.URANIUM_FILLED_BULLETS):
51
+ damage *= 2.0
52
+
53
+ if perk_active(attacker, PerkId.LIVING_FORTRESS):
54
+ for player in players:
55
+ if float(player.health) <= 0.0:
56
+ continue
57
+ timer = float(player.living_fortress_timer)
58
+ if timer > 0.0:
59
+ damage *= timer * 0.05 + 1.0
60
+
61
+ if perk_active(attacker, PerkId.BARREL_GREASER):
62
+ damage *= 1.4
63
+ if perk_active(attacker, PerkId.DOCTOR):
64
+ damage *= 1.2
65
+
66
+ if (creature.flags & CreatureFlags.ANIM_PING_PONG) == 0:
67
+ jitter = float((int(rand()) & 0x7F) - 0x40) * 0.002
68
+ size = max(1e-6, float(creature.size))
69
+ turn = jitter / (size * 0.025)
70
+ turn = max(-math.pi / 2.0, min(math.pi / 2.0, turn))
71
+ creature.heading += turn
72
+
73
+ if int(damage_type) == 7 and attacker is not None:
74
+ if perk_active(attacker, PerkId.ION_GUN_MASTER):
75
+ damage *= 1.2
76
+
77
+ if creature.hp <= 0.0:
78
+ if dt > 0.0:
79
+ creature.hitbox_size -= float(dt) * 15.0
80
+ return True
81
+
82
+ if int(damage_type) == 4 and attacker is not None:
83
+ if perk_active(attacker, PerkId.PYROMANIAC):
84
+ damage *= 1.5
85
+ rand()
86
+
87
+ creature.hp -= damage
88
+ creature.vel_x -= float(impulse_x)
89
+ creature.vel_y -= float(impulse_y)
90
+
91
+ if creature.hp <= 0.0:
92
+ if dt > 0.0:
93
+ creature.hitbox_size = float(creature.hitbox_size) - float(dt)
94
+ else:
95
+ creature.hitbox_size = float(creature.hitbox_size) - 0.001
96
+ creature.vel_x -= float(impulse_x) * 2.0
97
+ creature.vel_y -= float(impulse_y) * 2.0
98
+ return True
99
+
100
+ if creature.hitbox_size != CREATURE_HITBOX_ALIVE and dt > 0.0:
101
+ creature.hitbox_size = CREATURE_HITBOX_ALIVE
102
+
103
+ return False