crimsonland 0.1.0.dev7__py3-none-any.whl → 0.1.0.dev8__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.
@@ -7,6 +7,7 @@ import pyray as rl
7
7
 
8
8
  from grim.assets import PaqTextureCache
9
9
  from grim.audio import AudioState
10
+ from grim.console import ConsoleState
10
11
  from grim.config import CrimsonConfig
11
12
  from grim.view import ViewContext
12
13
 
@@ -17,7 +18,7 @@ from ..gameplay import PlayerInput, most_used_weapon_id_for_player, perk_selecti
17
18
  from ..persistence.highscores import HighScoreRecord
18
19
  from ..perks import PerkId, perk_display_description, perk_display_name
19
20
  from ..ui.cursor import draw_aim_cursor, draw_menu_cursor
20
- from ..ui.hud import draw_hud_overlay
21
+ from ..ui.hud import draw_hud_overlay, hud_flags_for_game_mode
21
22
  from ..input_codes import config_keybinds, input_code_is_down, input_code_is_pressed, player_move_fire_binds
22
23
  from ..ui.perk_menu import (
23
24
  PerkMenuLayout,
@@ -85,6 +86,7 @@ class SurvivalMode(BaseGameplayMode):
85
86
  *,
86
87
  texture_cache: PaqTextureCache | None = None,
87
88
  config: CrimsonConfig | None = None,
89
+ console: ConsoleState | None = None,
88
90
  audio: AudioState | None = None,
89
91
  audio_rng: random.Random | None = None,
90
92
  ) -> None:
@@ -97,6 +99,7 @@ class SurvivalMode(BaseGameplayMode):
97
99
  hardcore=False,
98
100
  texture_cache=texture_cache,
99
101
  config=config,
102
+ console=console,
100
103
  audio=audio,
101
104
  audio_rng=audio_rng,
102
105
  )
@@ -744,6 +747,8 @@ class SurvivalMode(BaseGameplayMode):
744
747
  hud_bottom = 0.0
745
748
  if (not self._game_over_active) and (not perk_menu_active) and self._hud_assets is not None:
746
749
  hud_alpha = _clamp(self._hud_fade_ms / PERK_MENU_TRANSITION_MS, 0.0, 1.0)
750
+ hud_flags = hud_flags_for_game_mode(self._config_game_mode_id())
751
+ self._draw_target_health_bar(alpha=hud_alpha)
747
752
  hud_bottom = draw_hud_overlay(
748
753
  self._hud_assets,
749
754
  player=self._player,
@@ -754,9 +759,15 @@ class SurvivalMode(BaseGameplayMode):
754
759
  font=self._small,
755
760
  alpha=hud_alpha,
756
761
  frame_dt_ms=self._last_dt_ms,
762
+ show_health=hud_flags.show_health,
763
+ show_weapon=hud_flags.show_weapon,
764
+ show_xp=hud_flags.show_xp,
765
+ show_time=hud_flags.show_time,
766
+ show_quest_hud=hud_flags.show_quest_hud,
767
+ small_indicators=self._hud_small_indicators(),
757
768
  )
758
769
 
759
- if (not self._game_over_active) and (not perk_menu_active):
770
+ if debug_enabled() and (not self._game_over_active) and (not perk_menu_active):
760
771
  # Minimal debug text.
761
772
  x = 18.0
762
773
  y = max(18.0, hud_bottom + 10.0)
@@ -8,6 +8,7 @@ import pyray as rl
8
8
 
9
9
  from grim.assets import PaqTextureCache
10
10
  from grim.audio import AudioState
11
+ from grim.console import ConsoleState
11
12
  from grim.config import CrimsonConfig
12
13
  from grim.view import ViewContext
13
14
 
@@ -19,7 +20,7 @@ from ..input_codes import config_keybinds, input_code_is_down, input_code_is_pre
19
20
  from ..perks import PerkId, perk_display_description, perk_display_name
20
21
  from ..tutorial.timeline import TutorialFrameActions, TutorialState, tick_tutorial_timeline
21
22
  from ..ui.cursor import draw_aim_cursor, draw_menu_cursor
22
- from ..ui.hud import draw_hud_overlay, hud_ui_scale
23
+ from ..ui.hud import draw_hud_overlay, hud_flags_for_game_mode, hud_ui_scale
23
24
  from ..ui.perk_menu import (
24
25
  PerkMenuAssets,
25
26
  PerkMenuLayout,
@@ -69,6 +70,7 @@ class TutorialMode(BaseGameplayMode):
69
70
  demo_mode_active: bool = False,
70
71
  texture_cache: PaqTextureCache | None = None,
71
72
  config: CrimsonConfig | None = None,
73
+ console: ConsoleState | None = None,
72
74
  audio: AudioState | None = None,
73
75
  audio_rng: random.Random | None = None,
74
76
  ) -> None:
@@ -81,6 +83,7 @@ class TutorialMode(BaseGameplayMode):
81
83
  hardcore=False,
82
84
  texture_cache=texture_cache,
83
85
  config=config,
86
+ console=console,
84
87
  audio=audio,
85
88
  audio_rng=audio_rng,
86
89
  )
@@ -453,6 +456,8 @@ class TutorialMode(BaseGameplayMode):
453
456
 
454
457
  hud_bottom = 0.0
455
458
  if (not perk_menu_active) and self._hud_assets is not None:
459
+ hud_flags = hud_flags_for_game_mode(self._config_game_mode_id())
460
+ self._draw_target_health_bar()
456
461
  hud_bottom = draw_hud_overlay(
457
462
  self._hud_assets,
458
463
  player=self._player,
@@ -463,6 +468,12 @@ class TutorialMode(BaseGameplayMode):
463
468
  font=self._small,
464
469
  alpha=1.0,
465
470
  frame_dt_ms=self._last_dt_ms,
471
+ show_health=hud_flags.show_health,
472
+ show_weapon=hud_flags.show_weapon,
473
+ show_xp=hud_flags.show_xp,
474
+ show_time=hud_flags.show_time,
475
+ show_quest_hud=hud_flags.show_quest_hud,
476
+ small_indicators=self._hud_small_indicators(),
466
477
  )
467
478
 
468
479
  self._draw_tutorial_prompts(hud_bottom=hud_bottom)
@@ -8,6 +8,7 @@ import pyray as rl
8
8
 
9
9
  from grim.assets import PaqTextureCache
10
10
  from grim.audio import AudioState
11
+ from grim.console import ConsoleState
11
12
  from grim.config import CrimsonConfig
12
13
  from grim.view import ViewContext
13
14
 
@@ -20,7 +21,7 @@ from ..typo.names import CreatureNameTable, load_typo_dictionary
20
21
  from ..typo.spawns import tick_typo_spawns
21
22
  from ..typo.typing import TypingBuffer
22
23
  from ..ui.cursor import draw_aim_cursor, draw_menu_cursor
23
- from ..ui.hud import draw_hud_overlay
24
+ from ..ui.hud import draw_hud_overlay, hud_flags_for_game_mode
24
25
  from ..ui.perk_menu import load_perk_menu_assets
25
26
  from .base_gameplay_mode import BaseGameplayMode
26
27
 
@@ -56,6 +57,7 @@ class TypoShooterMode(BaseGameplayMode):
56
57
  *,
57
58
  texture_cache: PaqTextureCache | None = None,
58
59
  config: CrimsonConfig | None = None,
60
+ console: ConsoleState | None = None,
59
61
  audio: AudioState | None = None,
60
62
  audio_rng: random.Random | None = None,
61
63
  ) -> None:
@@ -68,6 +70,7 @@ class TypoShooterMode(BaseGameplayMode):
68
70
  hardcore=False,
69
71
  texture_cache=texture_cache,
70
72
  config=config,
73
+ console=console,
71
74
  audio=audio,
72
75
  audio_rng=audio_rng,
73
76
  )
@@ -434,6 +437,8 @@ class TypoShooterMode(BaseGameplayMode):
434
437
  self._draw_name_labels()
435
438
 
436
439
  if show_gameplay_ui and self._hud_assets is not None:
440
+ hud_flags = hud_flags_for_game_mode(self._config_game_mode_id())
441
+ self._draw_target_health_bar()
437
442
  draw_hud_overlay(
438
443
  self._hud_assets,
439
444
  player=self._player,
@@ -442,9 +447,12 @@ class TypoShooterMode(BaseGameplayMode):
442
447
  elapsed_ms=float(self._typo.elapsed_ms),
443
448
  font=self._small,
444
449
  frame_dt_ms=self._last_dt_ms,
445
- show_weapon=False,
446
- show_xp=True,
447
- show_time=True,
450
+ show_health=hud_flags.show_health,
451
+ show_weapon=hud_flags.show_weapon,
452
+ show_xp=hud_flags.show_xp,
453
+ show_time=hud_flags.show_time,
454
+ show_quest_hud=hud_flags.show_quest_hud,
455
+ small_indicators=self._hud_small_indicators(),
448
456
  )
449
457
 
450
458
  if show_gameplay_ui:
crimson/ui/hud.py CHANGED
@@ -8,6 +8,7 @@ import pyray as rl
8
8
 
9
9
  from grim.assets import TextureLoader
10
10
  from grim.fonts.small import SmallFontData, draw_small_text
11
+ from ..game_modes import GameMode
11
12
  from ..gameplay import BonusHudState, PlayerState, survival_level_threshold
12
13
  from ..weapons import WEAPON_BY_ID
13
14
 
@@ -53,6 +54,7 @@ HUD_BONUS_TEXT_OFFSET = (36.0, 6.0)
53
54
  HUD_BONUS_SPACING = 52.0
54
55
  HUD_BONUS_PANEL_OFFSET_Y = -11.0
55
56
  HUD_XP_BAR_RGBA = (0.1, 0.3, 0.6, 1.0)
57
+ HUD_QUEST_LEFT_Y_SHIFT = 80.0
56
58
 
57
59
  _SURVIVAL_XP_SMOOTHED = 0
58
60
 
@@ -74,6 +76,60 @@ class HudAssets:
74
76
  missing: list[str] = field(default_factory=list)
75
77
 
76
78
 
79
+ @dataclass(frozen=True, slots=True)
80
+ class HudRenderFlags:
81
+ show_health: bool
82
+ show_weapon: bool
83
+ show_xp: bool
84
+ show_time: bool
85
+ show_quest_hud: bool
86
+
87
+
88
+ def hud_flags_for_game_mode(game_mode_id: int) -> HudRenderFlags:
89
+ """Match `hud_update_and_render` (0x0041ca90) flag mapping."""
90
+
91
+ mode = int(game_mode_id)
92
+ if mode == int(GameMode.QUESTS):
93
+ return HudRenderFlags(
94
+ show_health=True,
95
+ show_weapon=True,
96
+ show_xp=True,
97
+ show_time=False,
98
+ show_quest_hud=True,
99
+ )
100
+ if mode == int(GameMode.SURVIVAL):
101
+ return HudRenderFlags(
102
+ show_health=True,
103
+ show_weapon=True,
104
+ show_xp=True,
105
+ show_time=False,
106
+ show_quest_hud=False,
107
+ )
108
+ if mode == int(GameMode.RUSH):
109
+ return HudRenderFlags(
110
+ show_health=True,
111
+ show_weapon=False,
112
+ show_xp=False,
113
+ show_time=True,
114
+ show_quest_hud=False,
115
+ )
116
+ if mode == int(GameMode.TYPO):
117
+ return HudRenderFlags(
118
+ show_health=True,
119
+ show_weapon=False,
120
+ show_xp=True,
121
+ show_time=True,
122
+ show_quest_hud=False,
123
+ )
124
+ return HudRenderFlags(
125
+ show_health=False,
126
+ show_weapon=False,
127
+ show_xp=False,
128
+ show_time=False,
129
+ show_quest_hud=False,
130
+ )
131
+
132
+
77
133
  def hud_ui_scale(screen_w: float, screen_h: float) -> float:
78
134
  scale = min(screen_w / HUD_BASE_WIDTH, screen_h / HUD_BASE_HEIGHT)
79
135
  if scale < 0.75:
@@ -171,6 +227,18 @@ def _draw_progress_bar(x: float, y: float, width: float, ratio: float, rgba: tup
171
227
  rl.draw_rectangle(int(x + scale), int(y + scale), int(inner_w), int(inner_h), fg_color)
172
228
 
173
229
 
230
+ def draw_target_health_bar(*, x: float, y: float, width: float, ratio: float, alpha: float = 1.0, scale: float = 1.0) -> None:
231
+ ratio = max(0.0, min(1.0, float(ratio)))
232
+ alpha = max(0.0, min(1.0, float(alpha)))
233
+ scale = max(0.1, float(scale))
234
+
235
+ # Matches `hud_update_and_render` (0x0041ca90): color shifts from red->green as ratio increases.
236
+ r = (1.0 - ratio) * 0.9 + 0.1
237
+ g = ratio * 0.9 + 0.1
238
+ rgba = (r, g, 0.7, 0.2 * alpha)
239
+ _draw_progress_bar(float(x), float(y), float(width), ratio, rgba, scale)
240
+
241
+
174
242
  def _weapon_icon_index(weapon_id: int) -> int | None:
175
243
  entry = WEAPON_BY_ID.get(int(weapon_id))
176
244
  icon_index = entry.icon_index if entry is not None else None
@@ -215,9 +283,13 @@ def draw_hud_overlay(
215
283
  font: SmallFontData | None = None,
216
284
  alpha: float = 1.0,
217
285
  frame_dt_ms: float | None = None,
286
+ show_health: bool = True,
218
287
  show_weapon: bool = True,
219
288
  show_xp: bool = True,
220
289
  show_time: bool = False,
290
+ show_quest_hud: bool = False,
291
+ quest_progress_ratio: float | None = None,
292
+ small_indicators: bool = False,
221
293
  ) -> float:
222
294
  if frame_dt_ms is None:
223
295
  frame_dt_ms = max(0.0, float(rl.get_frame_time()) * 1000.0)
@@ -241,8 +313,8 @@ def draw_hud_overlay(
241
313
  max_y = 0.0
242
314
  alpha = max(0.0, min(1.0, float(alpha)))
243
315
  text_color = _with_alpha(HUD_TEXT_COLOR, alpha)
244
- accent_color = _with_alpha(HUD_ACCENT_COLOR, alpha)
245
316
  panel_text_color = _with_alpha(HUD_TEXT_COLOR, alpha * HUD_PANEL_ALPHA)
317
+ hud_y_shift = HUD_QUEST_LEFT_Y_SHIFT if show_quest_hud else 0.0
246
318
 
247
319
  # Top bar background.
248
320
  if assets.game_top is not None:
@@ -265,7 +337,7 @@ def draw_hud_overlay(
265
337
  max_y = max(max_y, dst.y + dst.height)
266
338
 
267
339
  # Pulsing heart.
268
- if assets.life_heart is not None:
340
+ if show_health and assets.life_heart is not None:
269
341
  t = max(0.0, elapsed_ms) / 1000.0
270
342
  src = rl.Rectangle(0.0, 0.0, float(assets.life_heart.width), float(assets.life_heart.height))
271
343
  if player_count == 1:
@@ -301,7 +373,7 @@ def draw_hud_overlay(
301
373
  max_y = max(max_y, dst.y + dst.height)
302
374
 
303
375
  # Health bar.
304
- if assets.ind_life is not None:
376
+ if show_health and assets.ind_life is not None:
305
377
  bar_x, bar_y = HUD_HEALTH_BAR_POS
306
378
  bar_w, bar_h = HUD_HEALTH_BAR_SIZE
307
379
  bg_src = rl.Rectangle(0.0, 0.0, float(assets.ind_life.width), float(assets.ind_life.height))
@@ -427,11 +499,91 @@ def draw_hud_overlay(
427
499
  text_y = base_y + HUD_AMMO_TEXT_OFFSET[1]
428
500
  _draw_text(font, f"+ {extra}", sx(text_x), sy(text_y), text_scale, text_color)
429
501
 
502
+ # Quest HUD panels (mm:ss timer + progress).
503
+ if show_quest_hud:
504
+ time_ms = max(0.0, float(elapsed_ms))
505
+ slide_x = 0.0
506
+ if time_ms < 1000.0:
507
+ slide_x = (1000.0 - time_ms) * -0.128
508
+
509
+ quest_panel_alpha = alpha * 0.7
510
+ quest_text_color = _with_alpha(HUD_TEXT_COLOR, quest_panel_alpha)
511
+
512
+ if assets.ind_panel is not None:
513
+ src = rl.Rectangle(0.0, 0.0, float(assets.ind_panel.width), float(assets.ind_panel.height))
514
+
515
+ # Sliding top panel (first second).
516
+ dst = rl.Rectangle(sx(slide_x - 90.0), sy(67.0), sx(182.0), sy(53.0))
517
+ rl.draw_texture_pro(
518
+ assets.ind_panel,
519
+ src,
520
+ dst,
521
+ rl.Vector2(0.0, 0.0),
522
+ 0.0,
523
+ rl.Color(255, 255, 255, int(255 * quest_panel_alpha)),
524
+ )
525
+ max_y = max(max_y, dst.y + dst.height)
526
+
527
+ # Static progress panel.
528
+ dst = rl.Rectangle(sx(-80.0), sy(107.0), sx(182.0), sy(53.0))
529
+ rl.draw_texture_pro(
530
+ assets.ind_panel,
531
+ src,
532
+ dst,
533
+ rl.Vector2(0.0, 0.0),
534
+ 0.0,
535
+ rl.Color(255, 255, 255, int(255 * quest_panel_alpha)),
536
+ )
537
+ max_y = max(max_y, dst.y + dst.height)
538
+
539
+ # Clock table + pointer inside the sliding panel.
540
+ clock_alpha = alpha * HUD_CLOCK_ALPHA
541
+ if assets.clock_table is not None:
542
+ dst = rl.Rectangle(sx(slide_x + 2.0), sy(78.0), sx(32.0), sy(32.0))
543
+ src = rl.Rectangle(0.0, 0.0, float(assets.clock_table.width), float(assets.clock_table.height))
544
+ rl.draw_texture_pro(
545
+ assets.clock_table,
546
+ src,
547
+ dst,
548
+ rl.Vector2(0.0, 0.0),
549
+ 0.0,
550
+ rl.Color(255, 255, 255, int(255 * clock_alpha)),
551
+ )
552
+
553
+ if assets.clock_pointer is not None:
554
+ # NOTE: Raylib's draw_texture_pro uses dst.x/y as the rotation origin position;
555
+ # offset by half-size so the 32x32 quad stays aligned with the table.
556
+ dst = rl.Rectangle(sx(slide_x + 2.0 + 16.0), sy(78.0 + 16.0), sx(32.0), sy(32.0))
557
+ src = rl.Rectangle(0.0, 0.0, float(assets.clock_pointer.width), float(assets.clock_pointer.height))
558
+ rotation = time_ms / 1000.0 * 6.0
559
+ origin = rl.Vector2(sx(16.0), sy(16.0))
560
+ rl.draw_texture_pro(
561
+ assets.clock_pointer,
562
+ src,
563
+ dst,
564
+ origin,
565
+ rotation,
566
+ rl.Color(255, 255, 255, int(255 * clock_alpha)),
567
+ )
568
+
569
+ total_seconds = max(0, int(time_ms) // 1000)
570
+ minutes = total_seconds // 60
571
+ seconds = total_seconds % 60
572
+ _draw_text(font, f"{minutes}:{seconds:02d}", sx(slide_x + 32.0), sy(86.0), text_scale, quest_text_color)
573
+
574
+ _draw_text(font, "Progress", sx(18.0), sy(122.0), text_scale, quest_text_color)
575
+
576
+ if quest_progress_ratio is not None:
577
+ ratio = max(0.0, min(1.0, float(quest_progress_ratio)))
578
+ quest_bar_rgba = (0.2, 0.8, 0.3, alpha * 0.8)
579
+ _draw_progress_bar(sx(10.0), sy(139.0), sx(70.0), ratio, quest_bar_rgba, scale)
580
+
430
581
  # Survival XP panel.
431
582
  xp_target = int(player.experience if score is None else score)
432
583
  xp_display = _smooth_xp(xp_target, frame_dt_ms) if show_xp else xp_target
433
584
  if show_xp and assets.ind_panel is not None:
434
585
  panel_x, panel_y = HUD_SURV_PANEL_POS
586
+ panel_y += hud_y_shift
435
587
  panel_w, panel_h = HUD_SURV_PANEL_SIZE
436
588
  dst = rl.Rectangle(sx(panel_x), sy(panel_y), sx(panel_w), sy(panel_h))
437
589
  src = rl.Rectangle(0.0, 0.0, float(assets.ind_panel.width), float(assets.ind_panel.height))
@@ -450,7 +602,7 @@ def draw_hud_overlay(
450
602
  font,
451
603
  "Xp",
452
604
  sx(HUD_SURV_XP_LABEL_POS[0]),
453
- sy(HUD_SURV_XP_LABEL_POS[1]),
605
+ sy(HUD_SURV_XP_LABEL_POS[1] + hud_y_shift),
454
606
  text_scale,
455
607
  panel_text_color,
456
608
  )
@@ -458,7 +610,7 @@ def draw_hud_overlay(
458
610
  font,
459
611
  f"{xp_display}",
460
612
  sx(HUD_SURV_XP_VALUE_POS[0]),
461
- sy(HUD_SURV_XP_VALUE_POS[1]),
613
+ sy(HUD_SURV_XP_VALUE_POS[1] + hud_y_shift),
462
614
  text_scale,
463
615
  panel_text_color,
464
616
  )
@@ -466,7 +618,7 @@ def draw_hud_overlay(
466
618
  font,
467
619
  f"{int(player.level)}",
468
620
  sx(HUD_SURV_LVL_VALUE_POS[0]),
469
- sy(HUD_SURV_LVL_VALUE_POS[1]),
621
+ sy(HUD_SURV_LVL_VALUE_POS[1] + hud_y_shift),
470
622
  text_scale,
471
623
  panel_text_color,
472
624
  )
@@ -478,6 +630,7 @@ def draw_hud_overlay(
478
630
  if next_threshold > prev_threshold:
479
631
  progress_ratio = (xp_target - prev_threshold) / float(next_threshold - prev_threshold)
480
632
  bar_x, bar_y = HUD_SURV_PROGRESS_POS
633
+ bar_y += hud_y_shift
481
634
  bar_w = HUD_SURV_PROGRESS_WIDTH
482
635
  bar_rgba = (HUD_XP_BAR_RGBA[0], HUD_XP_BAR_RGBA[1], HUD_XP_BAR_RGBA[2], HUD_XP_BAR_RGBA[3] * alpha)
483
636
  _draw_progress_bar(sx(bar_x), sy(bar_y), sx(bar_w), progress_ratio, bar_rgba, scale)
@@ -528,35 +681,61 @@ def draw_hud_overlay(
528
681
  _draw_text(font, time_text, sx(255.0), sy(10.0), text_scale, text_color)
529
682
  max_y = max(max_y, sy(10.0 + line_h))
530
683
 
531
- # Bonus HUD slots (text + icons), anchored below the survival panel.
532
- slots = [slot for slot in bonus_hud.slots if slot.active] if bonus_hud is not None else []
533
- if slots:
534
- bonus_x = sx(4.0)
535
- bonus_y = sy(HUD_BONUS_BASE_Y)
536
- for slot in slots[:16]:
684
+ # Bonus HUD slots (icon + timers), slide in/out from the left.
685
+ bonus_bottom_y = float(HUD_BONUS_BASE_Y + hud_y_shift)
686
+ if bonus_hud is not None:
687
+ bonus_y = float(HUD_BONUS_BASE_Y + hud_y_shift)
688
+ bonus_panel_alpha = alpha * 0.7
689
+ bonus_text_color = _with_alpha(HUD_TEXT_COLOR, bonus_panel_alpha)
690
+ bar_rgba = (HUD_XP_BAR_RGBA[0], HUD_XP_BAR_RGBA[1], HUD_XP_BAR_RGBA[2], bonus_panel_alpha)
691
+
692
+ slots = bonus_hud.slots[:16]
693
+ for slot in slots:
694
+ if not slot.active:
695
+ continue
696
+
697
+ if slot.slide_x < -184.0:
698
+ bonus_y += HUD_BONUS_SPACING
699
+ continue
700
+
701
+ has_alt = slot.timer_ref_alt is not None and player_count > 1
702
+ timer = float(slot.timer_value)
703
+ timer_alt = float(slot.timer_value_alt) if has_alt else 0.0
704
+
705
+ # Slot panel.
537
706
  if assets.ind_panel is not None:
538
- panel_x, panel_y = HUD_SURV_PANEL_POS
539
- dst = rl.Rectangle(
540
- sx(panel_x),
541
- bonus_y + sy(HUD_BONUS_PANEL_OFFSET_Y),
542
- sx(HUD_SURV_PANEL_SIZE[0]),
543
- sy(HUD_SURV_PANEL_SIZE[1]),
544
- )
707
+ if not small_indicators:
708
+ panel_x = slot.slide_x
709
+ panel_y = bonus_y + HUD_BONUS_PANEL_OFFSET_Y
710
+ panel_w = 182.0
711
+ panel_h = 53.0
712
+ else:
713
+ panel_x = (slot.slide_x - 100.0) + 4.0
714
+ panel_y = bonus_y + 5.0
715
+ panel_w = 182.0
716
+ panel_h = 26.5
717
+
545
718
  src = rl.Rectangle(0.0, 0.0, float(assets.ind_panel.width), float(assets.ind_panel.height))
719
+ dst = rl.Rectangle(sx(panel_x), sy(panel_y), sx(panel_w), sy(panel_h))
546
720
  rl.draw_texture_pro(
547
721
  assets.ind_panel,
548
722
  src,
549
723
  dst,
550
724
  rl.Vector2(0.0, 0.0),
551
725
  0.0,
552
- rl.Color(255, 255, 255, int(255 * alpha * HUD_TOP_BAR_ALPHA)),
726
+ rl.Color(255, 255, 255, int(255 * bonus_panel_alpha)),
553
727
  )
554
728
  max_y = max(max_y, dst.y + dst.height)
555
729
 
556
- icon_drawn = False
730
+ # Slot icon.
557
731
  if assets.bonuses is not None and slot.icon_id >= 0:
558
732
  src = _bonus_icon_src(assets.bonuses, slot.icon_id)
559
- dst = rl.Rectangle(bonus_x, bonus_y, sx(HUD_BONUS_ICON_SIZE), sy(HUD_BONUS_ICON_SIZE))
733
+ dst = rl.Rectangle(
734
+ sx(slot.slide_x - 1.0),
735
+ sy(bonus_y),
736
+ sx(HUD_BONUS_ICON_SIZE),
737
+ sy(HUD_BONUS_ICON_SIZE),
738
+ )
560
739
  rl.draw_texture_pro(
561
740
  assets.bonuses,
562
741
  src,
@@ -565,37 +744,81 @@ def draw_hud_overlay(
565
744
  0.0,
566
745
  rl.Color(255, 255, 255, int(255 * alpha)),
567
746
  )
568
- label_x = bonus_x + sx(HUD_BONUS_TEXT_OFFSET[0])
569
- icon_drawn = True
570
747
  max_y = max(max_y, dst.y + dst.height)
748
+
749
+ # Slot timer bars.
750
+ if not small_indicators:
751
+ if not has_alt:
752
+ _draw_progress_bar(sx(slot.slide_x + 36.0), sy(bonus_y + 21.0), sx(100.0), timer * 0.05, bar_rgba, scale)
753
+ _draw_text(font, slot.label, sx(slot.slide_x + 36.0), sy(bonus_y + 6.0), text_scale, bonus_text_color)
754
+ else:
755
+ _draw_progress_bar(sx(slot.slide_x + 36.0), sy(bonus_y + 17.0), sx(100.0), timer * 0.05, bar_rgba, scale)
756
+ _draw_progress_bar(sx(slot.slide_x + 36.0), sy(bonus_y + 23.0), sx(100.0), timer_alt * 0.05, bar_rgba, scale)
757
+ _draw_text(font, slot.label, sx(slot.slide_x + 36.0), sy(bonus_y + 2.0), text_scale, bonus_text_color)
571
758
  else:
572
- label_x = bonus_x
573
-
574
- if not icon_drawn and assets.wicons is not None:
575
- alt_icon_index = _weapon_icon_index(player.weapon_id)
576
- if alt_icon_index is not None:
577
- src = _weapon_icon_src(assets.wicons, alt_icon_index)
578
- dst = rl.Rectangle(bonus_x, bonus_y, sx(HUD_BONUS_ICON_SIZE), sy(HUD_BONUS_ICON_SIZE))
579
- rl.draw_texture_pro(
580
- assets.wicons,
581
- src,
582
- dst,
583
- rl.Vector2(0.0, 0.0),
584
- 0.0,
585
- rl.Color(255, 255, 255, int(255 * alpha)),
586
- )
587
- label_x = bonus_x + sx(HUD_BONUS_TEXT_OFFSET[0])
588
- max_y = max(max_y, dst.y + dst.height)
589
-
590
- _draw_text(
591
- font,
592
- slot.label,
593
- label_x,
594
- bonus_y + sy(HUD_BONUS_TEXT_OFFSET[1]),
595
- text_scale,
596
- accent_color,
759
+ if not has_alt:
760
+ _draw_progress_bar(sx(slot.slide_x + 36.0), sy(bonus_y + 17.0), sx(32.0), timer * 0.05, bar_rgba, scale)
761
+ else:
762
+ _draw_progress_bar(sx(slot.slide_x + 36.0), sy(bonus_y + 13.0), sx(32.0), timer * 0.05, bar_rgba, scale)
763
+ _draw_progress_bar(sx(slot.slide_x + 36.0), sy(bonus_y + 19.0), sx(32.0), timer_alt * 0.05, bar_rgba, scale)
764
+
765
+ bonus_y += HUD_BONUS_SPACING
766
+ max_y = max(max_y, sy(bonus_y))
767
+ bonus_bottom_y = bonus_y
768
+
769
+ # Weapon aux timer overlay (weapon name popup).
770
+ if assets.ind_panel is not None and assets.wicons is not None:
771
+ aux_base_y = float(bonus_bottom_y)
772
+ aux_step_y = 32.0
773
+ for idx, hud_player in enumerate(hud_players):
774
+ aux_timer = float(hud_player.aux_timer)
775
+ if aux_timer <= 0.0:
776
+ continue
777
+
778
+ fade = 2.0 - aux_timer if aux_timer > 1.0 else aux_timer
779
+ fade = max(0.0, min(1.0, fade)) * alpha
780
+ if fade <= 1e-3:
781
+ continue
782
+
783
+ panel_alpha = fade * 0.8
784
+ text_alpha = fade
785
+
786
+ panel_x = -12.0
787
+ panel_y = (aux_base_y - 17.0) + float(idx) * aux_step_y
788
+ panel_w = 182.0
789
+ panel_h = 53.0
790
+
791
+ src = rl.Rectangle(0.0, 0.0, float(assets.ind_panel.width), float(assets.ind_panel.height))
792
+ dst = rl.Rectangle(sx(panel_x), sy(panel_y), sx(panel_w), sy(panel_h))
793
+ rl.draw_texture_pro(
794
+ assets.ind_panel,
795
+ src,
796
+ dst,
797
+ rl.Vector2(0.0, 0.0),
798
+ 0.0,
799
+ rl.Color(255, 255, 255, int(255 * panel_alpha)),
597
800
  )
598
- bonus_y += sy(HUD_BONUS_SPACING)
599
- max_y = max(max_y, bonus_y)
801
+ max_y = max(max_y, dst.y + dst.height)
802
+
803
+ icon_index = _weapon_icon_index(hud_player.weapon_id)
804
+ if icon_index is not None:
805
+ src = _weapon_icon_src(assets.wicons, icon_index)
806
+ icon_x = 105.0
807
+ icon_y = (aux_base_y - 5.0) + float(idx) * aux_step_y
808
+ dst = rl.Rectangle(sx(icon_x), sy(icon_y), sx(60.0), sy(30.0))
809
+ rl.draw_texture_pro(
810
+ assets.wicons,
811
+ src,
812
+ dst,
813
+ rl.Vector2(0.0, 0.0),
814
+ 0.0,
815
+ rl.Color(255, 255, 255, int(255 * panel_alpha)),
816
+ )
817
+ max_y = max(max_y, dst.y + dst.height)
818
+
819
+ weapon_entry = WEAPON_BY_ID.get(int(hud_player.weapon_id))
820
+ weapon_name = weapon_entry.name if weapon_entry is not None else f"weapon_{int(hud_player.weapon_id)}"
821
+ weapon_color = _with_alpha(HUD_TEXT_COLOR, text_alpha)
822
+ _draw_text(font, weapon_name, sx(8.0), sy((aux_base_y + 1.0) + float(idx) * aux_step_y), text_scale, weapon_color)
600
823
 
601
824
  return max_y