crimsonland 0.1.0.dev5__py3-none-any.whl → 0.1.0.dev11__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.
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
@@ -6,9 +6,11 @@ import random
6
6
  import pyray as rl
7
7
 
8
8
  from grim.audio import AudioState, shutdown_audio, update_audio
9
+ from grim.console import ConsoleState
9
10
  from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
10
11
  from grim.view import View, ViewContext
11
12
 
13
+ from ..bonuses import BONUS_TABLE, BonusId
12
14
  from ..creatures.spawn import SpawnId
13
15
  from ..game_modes import GameMode
14
16
  from ..game_world import GameWorld
@@ -36,6 +38,7 @@ UI_ERROR = rl.Color(240, 80, 80, 255)
36
38
 
37
39
  ARSENAL_PLAYER_MOVE_SPEED_MULTIPLIER = 6.0
38
40
  ARSENAL_PLAYER_INVULNERABLE_SHIELD_TIMER = 1e-3
41
+ ARSENAL_BONUS_RING_RADIUS = 140.0
39
42
 
40
43
  DEFAULT_SPAWN_IDS = (
41
44
  SpawnId.ZOMBIE_CONST_GREY_42,
@@ -192,6 +195,45 @@ class ArsenalDebugView:
192
195
  rand=self._world.state.rng.rand,
193
196
  )
194
197
 
198
+ def _spawn_all_bonuses(self) -> None:
199
+ player = self._player
200
+ if player is None:
201
+ return
202
+
203
+ bonus_pool = self._world.state.bonus_pool
204
+ bonus_pool.reset()
205
+
206
+ bonus_ids = [int(entry.bonus_id) for entry in BONUS_TABLE if int(entry.bonus_id) != int(BonusId.UNUSED)]
207
+ count = max(1, len(bonus_ids))
208
+
209
+ base_x = float(player.pos_x)
210
+ base_y = float(player.pos_y)
211
+ rng = self._world.state.rng.rand
212
+ current_weapon_id = int(player.weapon_id)
213
+
214
+ for idx, bonus_id in enumerate(bonus_ids):
215
+ angle = float(idx) / float(count) * math.tau
216
+ x = base_x + math.cos(angle) * float(ARSENAL_BONUS_RING_RADIUS)
217
+ y = base_y + math.sin(angle) * float(ARSENAL_BONUS_RING_RADIUS)
218
+
219
+ amount_override = -1
220
+ if bonus_id == int(BonusId.WEAPON) and self._weapon_ids:
221
+ weapon_id = int(current_weapon_id)
222
+ for _ in range(8):
223
+ weapon_id = int(self._weapon_ids[int(rng()) % len(self._weapon_ids)])
224
+ if weapon_id != current_weapon_id:
225
+ break
226
+ amount_override = int(weapon_id)
227
+
228
+ bonus_pool.spawn_at(
229
+ x,
230
+ y,
231
+ bonus_id,
232
+ duration_override=int(amount_override),
233
+ world_width=float(WORLD_SIZE),
234
+ world_height=float(WORLD_SIZE),
235
+ )
236
+
195
237
  def _handle_debug_input(self) -> None:
196
238
  if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
197
239
  self.close_requested = True
@@ -209,6 +251,9 @@ class ArsenalDebugView:
209
251
  if rl.is_key_pressed(rl.KeyboardKey.KEY_T):
210
252
  self._reset_creatures()
211
253
 
254
+ if rl.is_key_pressed(rl.KeyboardKey.KEY_B):
255
+ self._spawn_all_bonuses()
256
+
212
257
  if rl.is_key_pressed(rl.KeyboardKey.KEY_BACKSPACE):
213
258
  self._reset_scene()
214
259
 
@@ -387,7 +432,7 @@ class ArsenalDebugView:
387
432
 
388
433
  y += 6.0
389
434
  self._draw_ui_text(
390
- "WASD move LMB fire R reload [/] cycle weapons Space pause T respawn Backspace reset Esc quit",
435
+ "WASD move LMB fire R reload [/] cycle weapons Space pause T respawn B spawn all bonuses Backspace reset Esc quit",
391
436
  x,
392
437
  y,
393
438
  UI_HINT,
crimson/views/player.py CHANGED
@@ -206,7 +206,7 @@ class PlayerSandboxView:
206
206
  self._player.shield_timer = 0.0
207
207
  self._player.speed_bonus_timer = 0.0
208
208
  self._player.fire_bullets_timer = 0.0
209
- bonus_hud_update(self._state, [self._player])
209
+ bonus_hud_update(self._state, [self._player], dt=0.0)
210
210
 
211
211
  def _camera_world_to_screen(self, x: float, y: float) -> tuple[float, float]:
212
212
  return self._camera_x + x, self._camera_y + y
@@ -308,7 +308,7 @@ class PlayerSandboxView:
308
308
  input_state = self._build_input()
309
309
  player_update(self._player, input_state, dt, self._state, world_size=WORLD_SIZE)
310
310
 
311
- bonus_hud_update(self._state, [self._player])
311
+ bonus_hud_update(self._state, [self._player], dt=dt)
312
312
  self._update_camera(dt)
313
313
 
314
314
  def draw(self) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: crimsonland
3
- Version: 0.1.0.dev5
3
+ Version: 0.1.0.dev11
4
4
  Requires-Dist: construct>=2.10.70
5
5
  Requires-Dist: pillow>=12.1.0
6
6
  Requires-Dist: platformdirs>=4.5.1