crimsonland 0.1.0.dev7__tar.gz → 0.1.0.dev9__tar.gz

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. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/PKG-INFO +1 -1
  2. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/pyproject.toml +1 -1
  3. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/creatures/anim.py +1 -0
  4. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/game.py +5 -0
  5. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/gameplay.py +33 -8
  6. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/modes/base_gameplay_mode.py +75 -1
  7. crimsonland-0.1.0.dev9/src/crimson/modes/quest_mode.py +929 -0
  8. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/modes/rush_mode.py +12 -3
  9. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/modes/survival_mode.py +13 -2
  10. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/modes/tutorial_mode.py +12 -1
  11. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/modes/typo_mode.py +12 -4
  12. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/render/world_renderer.py +12 -16
  13. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/ui/hud.py +274 -51
  14. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/arsenal_debug.py +46 -1
  15. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/player.py +2 -2
  16. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/console.py +14 -0
  17. crimsonland-0.1.0.dev7/src/crimson/modes/quest_mode.py +0 -502
  18. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/__init__.py +0 -0
  19. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/assets_fetch.py +0 -0
  20. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/atlas.py +0 -0
  21. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/audio_router.py +0 -0
  22. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/bonuses.py +0 -0
  23. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/camera.py +0 -0
  24. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/cli.py +0 -0
  25. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/creatures/__init__.py +0 -0
  26. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/creatures/ai.py +0 -0
  27. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/creatures/damage.py +0 -0
  28. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/creatures/runtime.py +0 -0
  29. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/creatures/spawn.py +0 -0
  30. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/debug.py +0 -0
  31. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/demo.py +0 -0
  32. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/demo_trial.py +0 -0
  33. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/effects.py +0 -0
  34. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/effects_atlas.py +0 -0
  35. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/frontend/__init__.py +0 -0
  36. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/frontend/assets.py +0 -0
  37. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/frontend/boot.py +0 -0
  38. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/frontend/menu.py +0 -0
  39. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/frontend/panels/__init__.py +0 -0
  40. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/frontend/panels/base.py +0 -0
  41. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/frontend/panels/controls.py +0 -0
  42. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/frontend/panels/mods.py +0 -0
  43. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/frontend/panels/options.py +0 -0
  44. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/frontend/panels/play_game.py +0 -0
  45. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/frontend/panels/stats.py +0 -0
  46. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/frontend/transitions.py +0 -0
  47. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/game_modes.py +0 -0
  48. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/game_world.py +0 -0
  49. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/input_codes.py +0 -0
  50. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/modes/__init__.py +0 -0
  51. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/paths.py +0 -0
  52. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/perks.py +0 -0
  53. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/persistence/__init__.py +0 -0
  54. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/persistence/highscores.py +0 -0
  55. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/persistence/save_status.py +0 -0
  56. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/player_damage.py +0 -0
  57. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/projectiles.py +0 -0
  58. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/quests/__init__.py +0 -0
  59. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/quests/helpers.py +0 -0
  60. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/quests/registry.py +0 -0
  61. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/quests/results.py +0 -0
  62. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/quests/runtime.py +0 -0
  63. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/quests/tier1.py +0 -0
  64. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/quests/tier2.py +0 -0
  65. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/quests/tier3.py +0 -0
  66. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/quests/tier4.py +0 -0
  67. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/quests/tier5.py +0 -0
  68. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/quests/timeline.py +0 -0
  69. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/quests/types.py +0 -0
  70. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/render/__init__.py +0 -0
  71. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/render/terrain_fx.py +0 -0
  72. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/sim/__init__.py +0 -0
  73. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/sim/world_defs.py +0 -0
  74. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/sim/world_state.py +0 -0
  75. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/terrain_assets.py +0 -0
  76. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/tutorial/__init__.py +0 -0
  77. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/tutorial/timeline.py +0 -0
  78. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/typo/__init__.py +0 -0
  79. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/typo/names.py +0 -0
  80. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/typo/player.py +0 -0
  81. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/typo/spawns.py +0 -0
  82. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/typo/typing.py +0 -0
  83. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/ui/__init__.py +0 -0
  84. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/ui/cursor.py +0 -0
  85. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/ui/demo_trial_overlay.py +0 -0
  86. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/ui/game_over.py +0 -0
  87. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/ui/perk_menu.py +0 -0
  88. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/__init__.py +0 -0
  89. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/aim_debug.py +0 -0
  90. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/animations.py +0 -0
  91. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/audio_bootstrap.py +0 -0
  92. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/bonuses.py +0 -0
  93. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/camera_debug.py +0 -0
  94. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/camera_shake.py +0 -0
  95. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/corpse_stamp_debug.py +0 -0
  96. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/decals_debug.py +0 -0
  97. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/empty.py +0 -0
  98. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/fonts.py +0 -0
  99. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/game_over.py +0 -0
  100. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/ground.py +0 -0
  101. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/lighting_debug.py +0 -0
  102. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/particles.py +0 -0
  103. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/perk_menu_debug.py +0 -0
  104. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/perks.py +0 -0
  105. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/player_sprite_debug.py +0 -0
  106. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/projectile_fx.py +0 -0
  107. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/projectile_render_debug.py +0 -0
  108. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/projectiles.py +0 -0
  109. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/quest_title_overlay.py +0 -0
  110. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/registry.py +0 -0
  111. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/rush.py +0 -0
  112. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/small_font_debug.py +0 -0
  113. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/spawn_plan.py +0 -0
  114. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/sprites.py +0 -0
  115. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/survival.py +0 -0
  116. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/terrain.py +0 -0
  117. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/ui.py +0 -0
  118. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/views/wicons.py +0 -0
  119. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/weapon_sfx.py +0 -0
  120. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/crimson/weapons.py +0 -0
  121. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/__init__.py +0 -0
  122. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/app.py +0 -0
  123. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/assets.py +0 -0
  124. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/audio.py +0 -0
  125. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/config.py +0 -0
  126. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/fonts/__init__.py +0 -0
  127. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/fonts/grim_mono.py +0 -0
  128. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/fonts/small.py +0 -0
  129. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/input.py +0 -0
  130. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/jaz.py +0 -0
  131. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/math.py +0 -0
  132. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/music.py +0 -0
  133. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/paq.py +0 -0
  134. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/rand.py +0 -0
  135. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/sfx.py +0 -0
  136. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/sfx_map.py +0 -0
  137. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/terrain_render.py +0 -0
  138. {crimsonland-0.1.0.dev7 → crimsonland-0.1.0.dev9}/src/grim/view.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: crimsonland
3
- Version: 0.1.0.dev7
3
+ Version: 0.1.0.dev9
4
4
  Requires-Dist: construct>=2.10.70
5
5
  Requires-Dist: pillow>=12.1.0
6
6
  Requires-Dist: platformdirs>=4.5.1
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "crimsonland"
3
- version = "0.1.0.dev7"
3
+ version = "0.1.0.dev9"
4
4
  requires-python = ">=3.13"
5
5
  dependencies = [
6
6
  "construct>=2.10.70",
@@ -28,6 +28,7 @@ _CREATURE_CORPSE_FRAMES: dict[int, int] = {
28
28
  3: 1, # spider sp1
29
29
  4: 2, # spider sp2
30
30
  5: 7, # trooper
31
+ 7: 6, # ping-pong strip corpse fallback
31
32
  }
32
33
 
33
34
 
@@ -789,6 +789,7 @@ class SurvivalGameView:
789
789
  ViewContext(assets_dir=state.assets_dir),
790
790
  texture_cache=state.texture_cache,
791
791
  config=state.config,
792
+ console=state.console,
792
793
  audio=state.audio,
793
794
  audio_rng=state.rng,
794
795
  )
@@ -848,6 +849,7 @@ class RushGameView:
848
849
  ViewContext(assets_dir=state.assets_dir),
849
850
  texture_cache=state.texture_cache,
850
851
  config=state.config,
852
+ console=state.console,
851
853
  audio=state.audio,
852
854
  audio_rng=state.rng,
853
855
  )
@@ -905,6 +907,7 @@ class TypoShooterGameView:
905
907
  ViewContext(assets_dir=state.assets_dir),
906
908
  texture_cache=state.texture_cache,
907
909
  config=state.config,
910
+ console=state.console,
908
911
  audio=state.audio,
909
912
  audio_rng=state.rng,
910
913
  )
@@ -962,6 +965,7 @@ class TutorialGameView:
962
965
  ViewContext(assets_dir=state.assets_dir),
963
966
  texture_cache=state.texture_cache,
964
967
  config=state.config,
968
+ console=state.console,
965
969
  audio=state.audio,
966
970
  audio_rng=state.rng,
967
971
  demo_mode_active=state.demo_enabled,
@@ -1011,6 +1015,7 @@ class QuestGameView:
1011
1015
  ViewContext(assets_dir=state.assets_dir),
1012
1016
  texture_cache=state.texture_cache,
1013
1017
  config=state.config,
1018
+ console=state.console,
1014
1019
  audio=state.audio,
1015
1020
  audio_rng=state.rng,
1016
1021
  demo_mode_active=state.demo_enabled,
@@ -159,8 +159,11 @@ class BonusHudSlot:
159
159
  bonus_id: int = 0
160
160
  label: str = ""
161
161
  icon_id: int = -1
162
+ slide_x: float = -184.0
162
163
  timer_ref: _TimerRef | None = None
163
164
  timer_ref_alt: _TimerRef | None = None
165
+ timer_value: float = 0.0
166
+ timer_value_alt: float = 0.0
164
167
 
165
168
 
166
169
  BONUS_HUD_SLOT_COUNT = 16
@@ -199,8 +202,11 @@ class BonusHudState:
199
202
  slot.bonus_id = int(bonus_id)
200
203
  slot.label = label
201
204
  slot.icon_id = int(icon_id)
205
+ slot.slide_x = -184.0
202
206
  slot.timer_ref = timer_ref
203
207
  slot.timer_ref_alt = timer_ref_alt
208
+ slot.timer_value = 0.0
209
+ slot.timer_value_alt = 0.0
204
210
 
205
211
 
206
212
  @dataclass(slots=True)
@@ -2356,8 +2362,8 @@ def bonus_apply(
2356
2362
  return
2357
2363
 
2358
2364
 
2359
- def bonus_hud_update(state: GameplayState, players: list[PlayerState]) -> None:
2360
- """Refresh HUD slots based on current timer values."""
2365
+ def bonus_hud_update(state: GameplayState, players: list[PlayerState], *, dt: float = 0.0) -> None:
2366
+ """Refresh HUD slots based on current timer values + advance slide animation."""
2361
2367
 
2362
2368
  def _timer_value(ref: _TimerRef | None) -> float:
2363
2369
  if ref is None:
@@ -2371,16 +2377,35 @@ def bonus_hud_update(state: GameplayState, players: list[PlayerState]) -> None:
2371
2377
  return float(getattr(players[idx], ref.key, 0.0) or 0.0)
2372
2378
  return 0.0
2373
2379
 
2374
- for slot in state.bonus_hud.slots:
2380
+ player_count = len(players)
2381
+ dt = max(0.0, float(dt))
2382
+
2383
+ for slot_index, slot in enumerate(state.bonus_hud.slots):
2375
2384
  if not slot.active:
2376
2385
  continue
2377
- timer = _timer_value(slot.timer_ref)
2378
- if slot.timer_ref_alt is not None:
2379
- timer = max(timer, _timer_value(slot.timer_ref_alt))
2380
- if timer <= 0.0:
2386
+ timer = max(0.0, _timer_value(slot.timer_ref))
2387
+ timer_alt = max(0.0, _timer_value(slot.timer_ref_alt)) if (slot.timer_ref_alt is not None and player_count > 1) else 0.0
2388
+ slot.timer_value = float(timer)
2389
+ slot.timer_value_alt = float(timer_alt)
2390
+
2391
+ if timer > 0.0 or timer_alt > 0.0:
2392
+ slot.slide_x += dt * 350.0
2393
+ else:
2394
+ slot.slide_x -= dt * 320.0
2395
+
2396
+ if slot.slide_x > -2.0:
2397
+ slot.slide_x = -2.0
2398
+
2399
+ if slot.slide_x < -184.0 and not any(other.active for other in state.bonus_hud.slots[slot_index + 1 :]):
2381
2400
  slot.active = False
2401
+ slot.bonus_id = 0
2402
+ slot.label = ""
2403
+ slot.icon_id = -1
2404
+ slot.slide_x = -184.0
2382
2405
  slot.timer_ref = None
2383
2406
  slot.timer_ref_alt = None
2407
+ slot.timer_value = 0.0
2408
+ slot.timer_value_alt = 0.0
2384
2409
 
2385
2410
 
2386
2411
  def bonus_telekinetic_update(
@@ -2495,6 +2520,6 @@ def bonus_update(
2495
2520
  state.bonuses.freeze = max(0.0, state.bonuses.freeze - dt)
2496
2521
 
2497
2522
  if update_hud:
2498
- bonus_hud_update(state, players)
2523
+ bonus_hud_update(state, players, dt=dt)
2499
2524
 
2500
2525
  return pickups
@@ -8,14 +8,17 @@ import pyray as rl
8
8
 
9
9
  from grim.assets import PaqTextureCache
10
10
  from grim.audio import AudioState, update_audio
11
+ from grim.console import ConsoleState
11
12
  from grim.config import CrimsonConfig
12
13
  from grim.fonts.small import SmallFontData, draw_small_text, load_small_font, measure_small_text_width
13
14
  from grim.view import ViewContext
14
15
 
16
+ from ..gameplay import _creature_find_in_radius, perk_count_get
15
17
  from ..game_world import GameWorld
16
18
  from ..persistence.highscores import HighScoreRecord
19
+ from ..perks import PerkId
17
20
  from ..ui.game_over import GameOverUi
18
- from ..ui.hud import HudAssets, load_hud_assets
21
+ from ..ui.hud import HudAssets, draw_target_health_bar, load_hud_assets
19
22
 
20
23
  if TYPE_CHECKING:
21
24
  from ..persistence.save_status import GameStatus
@@ -45,6 +48,7 @@ class BaseGameplayMode:
45
48
  hardcore: bool = False,
46
49
  texture_cache: PaqTextureCache | None = None,
47
50
  config: CrimsonConfig | None = None,
51
+ console: ConsoleState | None = None,
48
52
  audio: AudioState | None = None,
49
53
  audio_rng: random.Random | None = None,
50
54
  ) -> None:
@@ -54,7 +58,9 @@ class BaseGameplayMode:
54
58
  self._small: SmallFontData | None = None
55
59
  self._hud_assets: HudAssets | None = None
56
60
 
61
+ self._default_game_mode_id = int(default_game_mode_id)
57
62
  self._config = config
63
+ self._console = console
58
64
  self._base_dir = config.path.parent if config is not None else Path.cwd()
59
65
 
60
66
  self.close_requested = False
@@ -90,6 +96,74 @@ class BaseGameplayMode:
90
96
  self._last_dt_ms = 0.0
91
97
  self._screen_fade: _ScreenFade | None = None
92
98
 
99
+ def _cvar_float(self, name: str, default: float = 0.0) -> float:
100
+ console = self._console
101
+ if console is None:
102
+ return float(default)
103
+ cvar = console.cvars.get(name)
104
+ if cvar is None:
105
+ return float(default)
106
+ return float(cvar.value_f)
107
+
108
+ def _hud_small_indicators(self) -> bool:
109
+ return self._cvar_float("cv_uiSmallIndicators", 0.0) != 0.0
110
+
111
+ def _config_game_mode_id(self) -> int:
112
+ config = self._config
113
+ if config is None:
114
+ return int(self._default_game_mode_id)
115
+ try:
116
+ value = config.data.get("game_mode", self._default_game_mode_id)
117
+ return int(value or self._default_game_mode_id)
118
+ except Exception:
119
+ return int(self._default_game_mode_id)
120
+
121
+ def _draw_target_health_bar(self, *, alpha: float = 1.0) -> None:
122
+ creatures = getattr(self._creatures, "entries", [])
123
+ if not creatures:
124
+ return
125
+
126
+ if perk_count_get(self._player, PerkId.DOCTOR) <= 0:
127
+ return
128
+
129
+ target_idx = _creature_find_in_radius(
130
+ creatures,
131
+ pos_x=float(getattr(self._player, "aim_x", 0.0)),
132
+ pos_y=float(getattr(self._player, "aim_y", 0.0)),
133
+ radius=12.0,
134
+ start_index=0,
135
+ )
136
+ if target_idx == -1:
137
+ return
138
+
139
+ creature = creatures[target_idx]
140
+ if not bool(getattr(creature, "active", False)):
141
+ return
142
+ hp = float(getattr(creature, "hp", 0.0))
143
+ max_hp = float(getattr(creature, "max_hp", 0.0))
144
+ if hp <= 0.0 or max_hp <= 0.0:
145
+ return
146
+
147
+ ratio = hp / max_hp
148
+ if ratio < 0.0:
149
+ ratio = 0.0
150
+ if ratio > 1.0:
151
+ ratio = 1.0
152
+
153
+ x0, y0 = self._world.world_to_screen(float(creature.x) - 32.0, float(creature.y) + 32.0)
154
+ x1, _y1 = self._world.world_to_screen(float(creature.x) + 32.0, float(creature.y) + 32.0)
155
+ width = float(x1) - float(x0)
156
+ if width <= 1e-3:
157
+ return
158
+ draw_target_health_bar(
159
+ x=float(x0),
160
+ y=float(y0),
161
+ width=width,
162
+ ratio=ratio,
163
+ alpha=float(alpha),
164
+ scale=width / 64.0,
165
+ )
166
+
93
167
  def _bind_world(self) -> None:
94
168
  self._state = self._world.state
95
169
  self._creatures = self._world.creatures