crimsonland 0.1.0.dev10__py3-none-any.whl → 0.1.0.dev12__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/creatures/runtime.py +15 -0
- crimson/demo.py +47 -38
- crimson/effects.py +46 -1
- crimson/frontend/boot.py +2 -1
- crimson/frontend/menu.py +6 -27
- crimson/frontend/panels/base.py +13 -3
- crimson/frontend/panels/controls.py +1 -3
- crimson/frontend/panels/mods.py +1 -3
- crimson/frontend/panels/options.py +35 -42
- crimson/frontend/panels/play_game.py +78 -70
- crimson/frontend/panels/stats.py +1 -3
- crimson/frontend/pause_menu.py +425 -0
- crimson/game.py +315 -446
- crimson/gameplay.py +37 -8
- crimson/modes/base_gameplay_mode.py +3 -0
- crimson/modes/quest_mode.py +45 -36
- crimson/modes/rush_mode.py +4 -1
- crimson/modes/survival_mode.py +6 -2
- crimson/modes/tutorial_mode.py +6 -2
- crimson/modes/typo_mode.py +4 -1
- crimson/persistence/highscores.py +6 -2
- crimson/render/world_renderer.py +1 -1
- crimson/sim/world_state.py +8 -1
- crimson/typo/spawns.py +3 -4
- crimson/ui/demo_trial_overlay.py +3 -3
- crimson/ui/game_over.py +18 -2
- crimson/ui/perk_menu.py +106 -14
- crimson/ui/quest_results.py +663 -0
- crimson/ui/shadow.py +39 -0
- crimson/views/particles.py +1 -1
- crimson/weapons.py +110 -110
- {crimsonland-0.1.0.dev10.dist-info → crimsonland-0.1.0.dev12.dist-info}/METADATA +1 -1
- {crimsonland-0.1.0.dev10.dist-info → crimsonland-0.1.0.dev12.dist-info}/RECORD +36 -33
- grim/app.py +3 -0
- {crimsonland-0.1.0.dev10.dist-info → crimsonland-0.1.0.dev12.dist-info}/WHEEL +0 -0
- {crimsonland-0.1.0.dev10.dist-info → crimsonland-0.1.0.dev12.dist-info}/entry_points.txt +0 -0
crimson/gameplay.py
CHANGED
|
@@ -472,7 +472,7 @@ class BonusPool:
|
|
|
472
472
|
player,
|
|
473
473
|
BonusId(entry.bonus_id),
|
|
474
474
|
amount=entry.amount,
|
|
475
|
-
origin=
|
|
475
|
+
origin=entry,
|
|
476
476
|
creatures=creatures,
|
|
477
477
|
players=players,
|
|
478
478
|
apply_creature_damage=apply_creature_damage,
|
|
@@ -585,7 +585,7 @@ def _creature_find_in_radius(creatures: list[_CreatureForPerks], *, pos_x: float
|
|
|
585
585
|
continue
|
|
586
586
|
|
|
587
587
|
dist = math.hypot(float(creature.x) - pos_x, float(creature.y) - pos_y) - radius
|
|
588
|
-
threshold = float(creature.size) * 0.
|
|
588
|
+
threshold = float(creature.size) * 0.14285715 + 3.0
|
|
589
589
|
if threshold < dist:
|
|
590
590
|
continue
|
|
591
591
|
if float(creature.hitbox_size) < 5.0:
|
|
@@ -1511,9 +1511,9 @@ def player_start_reload(player: PlayerState, state: GameplayState) -> None:
|
|
|
1511
1511
|
player.reload_active = True
|
|
1512
1512
|
|
|
1513
1513
|
if perk_active(player, PerkId.FASTLOADER):
|
|
1514
|
-
reload_time *= 0.
|
|
1514
|
+
reload_time *= 0.7
|
|
1515
1515
|
if state.bonuses.weapon_power_up > 0.0:
|
|
1516
|
-
reload_time *= 0.
|
|
1516
|
+
reload_time *= 0.6
|
|
1517
1517
|
|
|
1518
1518
|
player.reload_timer = max(0.0, reload_time)
|
|
1519
1519
|
player.reload_timer_max = player.reload_timer
|
|
@@ -1618,7 +1618,14 @@ def _perk_update_fire_cough(player: PlayerState, dt: float, state: GameplayState
|
|
|
1618
1618
|
state.perk_intervals.fire_cough = float(state.rng.rand() % 4) + 2.0
|
|
1619
1619
|
|
|
1620
1620
|
|
|
1621
|
-
def player_fire_weapon(
|
|
1621
|
+
def player_fire_weapon(
|
|
1622
|
+
player: PlayerState,
|
|
1623
|
+
input_state: PlayerInput,
|
|
1624
|
+
dt: float,
|
|
1625
|
+
state: GameplayState,
|
|
1626
|
+
*,
|
|
1627
|
+
detail_preset: int = 5,
|
|
1628
|
+
) -> None:
|
|
1622
1629
|
dt = float(dt)
|
|
1623
1630
|
|
|
1624
1631
|
weapon_id = int(player.weapon_id)
|
|
@@ -1689,6 +1696,20 @@ def player_fire_weapon(player: PlayerState, input_state: PlayerInput, dt: float,
|
|
|
1689
1696
|
aim_y = float(input_state.aim_y)
|
|
1690
1697
|
dx = aim_x - float(player.pos_x)
|
|
1691
1698
|
dy = aim_y - float(player.pos_y)
|
|
1699
|
+
aim_heading = math.atan2(dy, dx) + math.pi / 2.0
|
|
1700
|
+
|
|
1701
|
+
muzzle_dir = (aim_heading - math.pi / 2.0) - 0.150915
|
|
1702
|
+
muzzle_shell_x = player.pos_x + math.cos(muzzle_dir) * 16.0
|
|
1703
|
+
muzzle_shell_y = player.pos_y + math.sin(muzzle_dir) * 16.0
|
|
1704
|
+
state.effects.spawn_shell_casing(
|
|
1705
|
+
pos_x=muzzle_shell_x,
|
|
1706
|
+
pos_y=muzzle_shell_y,
|
|
1707
|
+
aim_heading=aim_heading,
|
|
1708
|
+
weapon_flags=int(weapon.flags or 0),
|
|
1709
|
+
rand=state.rng.rand,
|
|
1710
|
+
detail_preset=int(detail_preset),
|
|
1711
|
+
)
|
|
1712
|
+
|
|
1692
1713
|
dist = math.hypot(dx, dy)
|
|
1693
1714
|
max_offset = dist * float(player.spread_heat) * 0.5
|
|
1694
1715
|
dir_angle = float(int(state.rng.rand()) & 0x1FF) * (math.tau / 512.0)
|
|
@@ -1885,7 +1906,15 @@ def player_fire_weapon(player: PlayerState, input_state: PlayerInput, dt: float,
|
|
|
1885
1906
|
player_start_reload(player, state)
|
|
1886
1907
|
|
|
1887
1908
|
|
|
1888
|
-
def player_update(
|
|
1909
|
+
def player_update(
|
|
1910
|
+
player: PlayerState,
|
|
1911
|
+
input_state: PlayerInput,
|
|
1912
|
+
dt: float,
|
|
1913
|
+
state: GameplayState,
|
|
1914
|
+
*,
|
|
1915
|
+
detail_preset: int = 5,
|
|
1916
|
+
world_size: float = 1024.0,
|
|
1917
|
+
) -> None:
|
|
1889
1918
|
"""Port of `player_update` (0x004136b0) for the rewrite runtime."""
|
|
1890
1919
|
|
|
1891
1920
|
if dt <= 0.0:
|
|
@@ -2048,7 +2077,7 @@ def player_update(player: PlayerState, input_state: PlayerInput, dt: float, stat
|
|
|
2048
2077
|
elif player.reload_timer == 0.0:
|
|
2049
2078
|
player_start_reload(player, state)
|
|
2050
2079
|
|
|
2051
|
-
player_fire_weapon(player, input_state, dt, state)
|
|
2080
|
+
player_fire_weapon(player, input_state, dt, state, detail_preset=int(detail_preset))
|
|
2052
2081
|
|
|
2053
2082
|
while player.move_phase > 14.0:
|
|
2054
2083
|
player.move_phase -= 14.0
|
|
@@ -2458,7 +2487,7 @@ def bonus_telekinetic_update(
|
|
|
2458
2487
|
player,
|
|
2459
2488
|
BonusId(int(entry.bonus_id)),
|
|
2460
2489
|
amount=int(entry.amount),
|
|
2461
|
-
origin=
|
|
2490
|
+
origin=entry,
|
|
2462
2491
|
creatures=creatures,
|
|
2463
2492
|
players=players,
|
|
2464
2493
|
apply_creature_damage=apply_creature_damage,
|
|
@@ -283,6 +283,9 @@ class BaseGameplayMode:
|
|
|
283
283
|
self._action = None
|
|
284
284
|
return action
|
|
285
285
|
|
|
286
|
+
def draw_pause_background(self) -> None:
|
|
287
|
+
self._world.draw(draw_aim_indicators=False)
|
|
288
|
+
|
|
286
289
|
def _draw_screen_fade(self) -> None:
|
|
287
290
|
fade_alpha = 0.0
|
|
288
291
|
if self._screen_fade is not None:
|
crimson/modes/quest_mode.py
CHANGED
|
@@ -306,7 +306,8 @@ class QuestMode(BaseGameplayMode):
|
|
|
306
306
|
self._paused = not self._paused
|
|
307
307
|
|
|
308
308
|
if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
309
|
-
self.
|
|
309
|
+
self._action = "open_pause_menu"
|
|
310
|
+
return
|
|
310
311
|
|
|
311
312
|
def _build_input(self):
|
|
312
313
|
keybinds = config_keybinds(self._config)
|
|
@@ -517,6 +518,42 @@ class QuestMode(BaseGameplayMode):
|
|
|
517
518
|
self._world.audio_router.play_sfx("sfx_ui_bonus")
|
|
518
519
|
self._close_perk_menu()
|
|
519
520
|
|
|
521
|
+
def _close_failed_run(self) -> None:
|
|
522
|
+
if self._outcome is None:
|
|
523
|
+
fired = 0
|
|
524
|
+
hit = 0
|
|
525
|
+
try:
|
|
526
|
+
fired = int(self._state.shots_fired[int(self._player.index)])
|
|
527
|
+
hit = int(self._state.shots_hit[int(self._player.index)])
|
|
528
|
+
except Exception:
|
|
529
|
+
fired = 0
|
|
530
|
+
hit = 0
|
|
531
|
+
fired = max(0, int(fired))
|
|
532
|
+
hit = max(0, min(int(hit), fired))
|
|
533
|
+
most_used_weapon_id = most_used_weapon_id_for_player(
|
|
534
|
+
self._state,
|
|
535
|
+
player_index=int(self._player.index),
|
|
536
|
+
fallback_weapon_id=int(self._player.weapon_id),
|
|
537
|
+
)
|
|
538
|
+
player2_health = None
|
|
539
|
+
if len(self._world.players) >= 2:
|
|
540
|
+
player2_health = float(self._world.players[1].health)
|
|
541
|
+
self._outcome = QuestRunOutcome(
|
|
542
|
+
kind="failed",
|
|
543
|
+
level=str(self._quest.level),
|
|
544
|
+
base_time_ms=int(self._quest.spawn_timeline_ms),
|
|
545
|
+
player_health=float(self._player.health),
|
|
546
|
+
player2_health=player2_health,
|
|
547
|
+
pending_perk_count=int(self._state.perk_selection.pending_count),
|
|
548
|
+
experience=int(self._player.experience),
|
|
549
|
+
kill_count=int(self._creatures.kill_count),
|
|
550
|
+
weapon_id=int(self._player.weapon_id),
|
|
551
|
+
shots_fired=fired,
|
|
552
|
+
shots_hit=hit,
|
|
553
|
+
most_used_weapon_id=int(most_used_weapon_id),
|
|
554
|
+
)
|
|
555
|
+
self.close_requested = True
|
|
556
|
+
|
|
520
557
|
def _draw_perk_prompt(self) -> None:
|
|
521
558
|
if self._perk_menu_open or self._perk_menu_timeline_ms > 1e-3:
|
|
522
559
|
return
|
|
@@ -614,7 +651,8 @@ class QuestMode(BaseGameplayMode):
|
|
|
614
651
|
|
|
615
652
|
panel_tex = self._perk_menu_assets.menu_panel
|
|
616
653
|
if panel_tex is not None:
|
|
617
|
-
|
|
654
|
+
fx_detail = bool(int(self._config.data.get("fx_detail_0", 0) or 0)) if self._config is not None else False
|
|
655
|
+
draw_menu_panel(panel_tex, dst=computed.panel, shadow=fx_detail)
|
|
618
656
|
|
|
619
657
|
title_tex = self._perk_menu_assets.title_pick_perk
|
|
620
658
|
if title_tex is not None:
|
|
@@ -665,6 +703,8 @@ class QuestMode(BaseGameplayMode):
|
|
|
665
703
|
|
|
666
704
|
dt_frame, dt_ui_ms = self._tick_frame(dt)
|
|
667
705
|
self._handle_input()
|
|
706
|
+
if self._action == "open_pause_menu":
|
|
707
|
+
return
|
|
668
708
|
|
|
669
709
|
if self.close_requested:
|
|
670
710
|
return
|
|
@@ -719,6 +759,8 @@ class QuestMode(BaseGameplayMode):
|
|
|
719
759
|
|
|
720
760
|
dt_world = 0.0 if self._paused or (not any_alive) or perk_menu_active else dt_frame
|
|
721
761
|
if dt_world <= 0.0:
|
|
762
|
+
if not any(player.health > 0.0 for player in self._world.players):
|
|
763
|
+
self._close_failed_run()
|
|
722
764
|
return
|
|
723
765
|
|
|
724
766
|
self._quest.quest_name_timer_ms += dt_world * 1000.0
|
|
@@ -734,40 +776,7 @@ class QuestMode(BaseGameplayMode):
|
|
|
734
776
|
|
|
735
777
|
any_alive_after = any(player.health > 0.0 for player in self._world.players)
|
|
736
778
|
if not any_alive_after:
|
|
737
|
-
|
|
738
|
-
fired = 0
|
|
739
|
-
hit = 0
|
|
740
|
-
try:
|
|
741
|
-
fired = int(self._state.shots_fired[int(self._player.index)])
|
|
742
|
-
hit = int(self._state.shots_hit[int(self._player.index)])
|
|
743
|
-
except Exception:
|
|
744
|
-
fired = 0
|
|
745
|
-
hit = 0
|
|
746
|
-
fired = max(0, int(fired))
|
|
747
|
-
hit = max(0, min(int(hit), fired))
|
|
748
|
-
most_used_weapon_id = most_used_weapon_id_for_player(
|
|
749
|
-
self._state,
|
|
750
|
-
player_index=int(self._player.index),
|
|
751
|
-
fallback_weapon_id=int(self._player.weapon_id),
|
|
752
|
-
)
|
|
753
|
-
player2_health = None
|
|
754
|
-
if len(self._world.players) >= 2:
|
|
755
|
-
player2_health = float(self._world.players[1].health)
|
|
756
|
-
self._outcome = QuestRunOutcome(
|
|
757
|
-
kind="failed",
|
|
758
|
-
level=str(self._quest.level),
|
|
759
|
-
base_time_ms=int(self._quest.spawn_timeline_ms),
|
|
760
|
-
player_health=float(self._player.health),
|
|
761
|
-
player2_health=player2_health,
|
|
762
|
-
pending_perk_count=int(self._state.perk_selection.pending_count),
|
|
763
|
-
experience=int(self._player.experience),
|
|
764
|
-
kill_count=int(self._creatures.kill_count),
|
|
765
|
-
weapon_id=int(self._player.weapon_id),
|
|
766
|
-
shots_fired=fired,
|
|
767
|
-
shots_hit=hit,
|
|
768
|
-
most_used_weapon_id=int(most_used_weapon_id),
|
|
769
|
-
)
|
|
770
|
-
self.close_requested = True
|
|
779
|
+
self._close_failed_run()
|
|
771
780
|
return
|
|
772
781
|
|
|
773
782
|
creatures_none_active = not bool(self._creatures.iter_active())
|
crimson/modes/rush_mode.py
CHANGED
|
@@ -95,7 +95,8 @@ class RushMode(BaseGameplayMode):
|
|
|
95
95
|
self._paused = not self._paused
|
|
96
96
|
|
|
97
97
|
if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
98
|
-
self.
|
|
98
|
+
self._action = "open_pause_menu"
|
|
99
|
+
return
|
|
99
100
|
|
|
100
101
|
def _build_input(self) -> PlayerInput:
|
|
101
102
|
keybinds = config_keybinds(self._config)
|
|
@@ -174,6 +175,8 @@ class RushMode(BaseGameplayMode):
|
|
|
174
175
|
|
|
175
176
|
dt_frame = self._tick_frame(dt)[0]
|
|
176
177
|
self._handle_input()
|
|
178
|
+
if self._action == "open_pause_menu":
|
|
179
|
+
return
|
|
177
180
|
|
|
178
181
|
if self._game_over_active:
|
|
179
182
|
record = self._game_over_record
|
crimson/modes/survival_mode.py
CHANGED
|
@@ -186,7 +186,8 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
186
186
|
survival_check_level_up(self._player, self._state.perk_selection)
|
|
187
187
|
|
|
188
188
|
if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
189
|
-
self.
|
|
189
|
+
self._action = "open_pause_menu"
|
|
190
|
+
return
|
|
190
191
|
|
|
191
192
|
def _build_input(self) -> PlayerInput:
|
|
192
193
|
keybinds = config_keybinds(self._config)
|
|
@@ -441,6 +442,8 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
441
442
|
dt_frame, dt_ui_ms = self._tick_frame(dt)
|
|
442
443
|
self._cursor_time += dt_frame
|
|
443
444
|
self._handle_input()
|
|
445
|
+
if self._action == "open_pause_menu":
|
|
446
|
+
return
|
|
444
447
|
|
|
445
448
|
if self._game_over_active:
|
|
446
449
|
record = self._game_over_record
|
|
@@ -668,7 +671,8 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
668
671
|
|
|
669
672
|
panel_tex = self._perk_menu_assets.menu_panel
|
|
670
673
|
if panel_tex is not None:
|
|
671
|
-
|
|
674
|
+
fx_detail = bool(int(self._config.data.get("fx_detail_0", 0) or 0)) if self._config is not None else False
|
|
675
|
+
draw_menu_panel(panel_tex, dst=computed.panel, shadow=fx_detail)
|
|
672
676
|
|
|
673
677
|
title_tex = self._perk_menu_assets.title_pick_perk
|
|
674
678
|
if title_tex is not None:
|
crimson/modes/tutorial_mode.py
CHANGED
|
@@ -143,7 +143,8 @@ class TutorialMode(BaseGameplayMode):
|
|
|
143
143
|
self._paused = not self._paused
|
|
144
144
|
|
|
145
145
|
if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
146
|
-
self.
|
|
146
|
+
self._action = "open_pause_menu"
|
|
147
|
+
return
|
|
147
148
|
|
|
148
149
|
def _build_input(self) -> PlayerInput:
|
|
149
150
|
keybinds = config_keybinds(self._config)
|
|
@@ -346,6 +347,8 @@ class TutorialMode(BaseGameplayMode):
|
|
|
346
347
|
dt_frame, dt_ui_ms = self._tick_frame(dt, clamp_cursor_pulse=True)
|
|
347
348
|
|
|
348
349
|
self._handle_input()
|
|
350
|
+
if self._action == "open_pause_menu":
|
|
351
|
+
return
|
|
349
352
|
if self.close_requested:
|
|
350
353
|
return
|
|
351
354
|
|
|
@@ -605,7 +608,8 @@ class TutorialMode(BaseGameplayMode):
|
|
|
605
608
|
|
|
606
609
|
panel_tex = assets.menu_panel
|
|
607
610
|
if panel_tex is not None:
|
|
608
|
-
|
|
611
|
+
fx_detail = bool(int(self._config.data.get("fx_detail_0", 0) or 0)) if self._config is not None else False
|
|
612
|
+
draw_menu_panel(panel_tex, dst=computed.panel, shadow=fx_detail)
|
|
609
613
|
|
|
610
614
|
title_tex = assets.title_pick_perk
|
|
611
615
|
if title_tex is not None:
|
crimson/modes/typo_mode.py
CHANGED
|
@@ -120,7 +120,8 @@ class TypoShooterMode(BaseGameplayMode):
|
|
|
120
120
|
self._paused = not self._paused
|
|
121
121
|
|
|
122
122
|
if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
123
|
-
self.
|
|
123
|
+
self._action = "open_pause_menu"
|
|
124
|
+
return
|
|
124
125
|
|
|
125
126
|
def _active_mask(self) -> list[bool]:
|
|
126
127
|
return [bool(entry.active) for entry in self._creatures.entries]
|
|
@@ -257,6 +258,8 @@ class TypoShooterMode(BaseGameplayMode):
|
|
|
257
258
|
|
|
258
259
|
dt_frame = self._tick_frame(dt)[0]
|
|
259
260
|
self._handle_input()
|
|
261
|
+
if self._action == "open_pause_menu":
|
|
262
|
+
return
|
|
260
263
|
|
|
261
264
|
if self._game_over_active:
|
|
262
265
|
self._update_game_over_ui(dt)
|
|
@@ -239,7 +239,9 @@ def scores_path_for_mode(
|
|
|
239
239
|
# Native `highscore_build_path` uses `questhc*.hi` when hardcore is OFF,
|
|
240
240
|
# and `quest*.hi` when hardcore is ON.
|
|
241
241
|
prefix = "quest" if hardcore else "questhc"
|
|
242
|
-
|
|
242
|
+
major = int(quest_stage_major)
|
|
243
|
+
minor = int(quest_stage_minor)
|
|
244
|
+
return root / f"{prefix}{major}_{minor}.hi"
|
|
243
245
|
return root / "unknown.hi"
|
|
244
246
|
|
|
245
247
|
|
|
@@ -272,7 +274,9 @@ def scores_path_for_config(base_dir: Path, config: CrimsonConfig, *, quest_stage
|
|
|
272
274
|
# Native `highscore_build_path` uses `questhc*.hi` when hardcore is OFF,
|
|
273
275
|
# and `quest*.hi` when hardcore is ON.
|
|
274
276
|
prefix = "quest" if hardcore else "questhc"
|
|
275
|
-
|
|
277
|
+
major = int(quest_stage_major)
|
|
278
|
+
minor = int(quest_stage_minor)
|
|
279
|
+
return root / f"{prefix}{major}_{minor}.hi"
|
|
276
280
|
return root / "unknown.hi"
|
|
277
281
|
|
|
278
282
|
|
crimson/render/world_renderer.py
CHANGED
|
@@ -947,7 +947,7 @@ class WorldRenderer:
|
|
|
947
947
|
if float(getattr(creature, "hitbox_size", 0.0)) <= 5.0:
|
|
948
948
|
continue
|
|
949
949
|
d = math.hypot(float(creature.x) - pos_x, float(creature.y) - pos_y)
|
|
950
|
-
threshold = float(creature.size) * 0.
|
|
950
|
+
threshold = float(creature.size) * 0.14285715 + 3.0
|
|
951
951
|
if d - radius < threshold:
|
|
952
952
|
targets.append(creature)
|
|
953
953
|
|
crimson/sim/world_state.py
CHANGED
|
@@ -290,7 +290,14 @@ class WorldState:
|
|
|
290
290
|
|
|
291
291
|
for idx, player in enumerate(self.players):
|
|
292
292
|
input_state = inputs[idx] if idx < len(inputs) else PlayerInput()
|
|
293
|
-
player_update(
|
|
293
|
+
player_update(
|
|
294
|
+
player,
|
|
295
|
+
input_state,
|
|
296
|
+
dt,
|
|
297
|
+
self.state,
|
|
298
|
+
detail_preset=int(detail_preset),
|
|
299
|
+
world_size=float(world_size),
|
|
300
|
+
)
|
|
294
301
|
|
|
295
302
|
if dt > 0.0:
|
|
296
303
|
self._advance_creature_anim(dt)
|
crimson/typo/spawns.py
CHANGED
|
@@ -47,9 +47,9 @@ def tick_typo_spawns(
|
|
|
47
47
|
y = math.cos(t) * 256.0 + float(world_height) * 0.5
|
|
48
48
|
|
|
49
49
|
tint_t = float(elapsed_ms + 1)
|
|
50
|
-
tint_r = _clamp(tint_t * 0.0000083333334 + 0.
|
|
51
|
-
tint_g = _clamp(tint_t * 10000.0 + 0.
|
|
52
|
-
tint_b = _clamp(math.sin(tint_t * 0.0001) + 0.
|
|
50
|
+
tint_r = _clamp(tint_t * 0.0000083333334 + 0.3, 0.0, 1.0)
|
|
51
|
+
tint_g = _clamp(tint_t * 10000.0 + 0.3, 0.0, 1.0)
|
|
52
|
+
tint_b = _clamp(math.sin(tint_t * 0.0001) + 0.3, 0.0, 1.0)
|
|
53
53
|
tint = (tint_r, tint_g, tint_b, 1.0)
|
|
54
54
|
|
|
55
55
|
spawns.append(
|
|
@@ -70,4 +70,3 @@ def tick_typo_spawns(
|
|
|
70
70
|
)
|
|
71
71
|
|
|
72
72
|
return cooldown, spawns
|
|
73
|
-
|
crimson/ui/demo_trial_overlay.py
CHANGED
|
@@ -66,13 +66,14 @@ class DemoTrialOverlayUi:
|
|
|
66
66
|
|
|
67
67
|
if self._assets is None:
|
|
68
68
|
cursor = cache.get_or_load("ui_cursor", "ui/ui_cursor.jaz").texture
|
|
69
|
-
|
|
69
|
+
button_sm = cache.get_or_load("ui_buttonSm", "ui/ui_button_64x32.jaz").texture
|
|
70
|
+
button_md = cache.get_or_load("ui_buttonMd", "ui/ui_button_128x32.jaz").texture
|
|
70
71
|
self._assets = PerkMenuAssets(
|
|
71
72
|
menu_panel=None,
|
|
72
73
|
title_pick_perk=None,
|
|
73
74
|
title_level_up=None,
|
|
74
75
|
menu_item=None,
|
|
75
|
-
button_sm=
|
|
76
|
+
button_sm=button_sm,
|
|
76
77
|
button_md=button_md,
|
|
77
78
|
cursor=cursor,
|
|
78
79
|
aim=None,
|
|
@@ -232,4 +233,3 @@ class DemoTrialOverlayUi:
|
|
|
232
233
|
scale=float(scale),
|
|
233
234
|
)
|
|
234
235
|
cursor_draw(assets, mouse=rl.get_mouse_position(), scale=1.0, alpha=1.0)
|
|
235
|
-
|
crimson/ui/game_over.py
CHANGED
|
@@ -31,6 +31,7 @@ from .perk_menu import (
|
|
|
31
31
|
draw_ui_text,
|
|
32
32
|
load_perk_menu_assets,
|
|
33
33
|
)
|
|
34
|
+
from .shadow import UI_SHADOW_OFFSET, draw_ui_quad_shadow
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
UI_BASE_WIDTH = 640.0
|
|
@@ -568,9 +569,24 @@ class GameOverUi:
|
|
|
568
569
|
|
|
569
570
|
# Panel background
|
|
570
571
|
if self.assets.menu_panel is not None:
|
|
571
|
-
|
|
572
|
+
panel_tex = self.assets.menu_panel
|
|
573
|
+
src = rl.Rectangle(0.0, 0.0, float(panel_tex.width), float(panel_tex.height))
|
|
572
574
|
dst = rl.Rectangle(panel.x, panel.y, panel.width, panel.height)
|
|
573
|
-
|
|
575
|
+
fx_detail = bool(int(getattr(self.config, "data", {}).get("fx_detail_0", 0) or 0))
|
|
576
|
+
if fx_detail:
|
|
577
|
+
draw_ui_quad_shadow(
|
|
578
|
+
texture=panel_tex,
|
|
579
|
+
src=src,
|
|
580
|
+
dst=rl.Rectangle(
|
|
581
|
+
float(dst.x + UI_SHADOW_OFFSET),
|
|
582
|
+
float(dst.y + UI_SHADOW_OFFSET),
|
|
583
|
+
float(dst.width),
|
|
584
|
+
float(dst.height),
|
|
585
|
+
),
|
|
586
|
+
origin=rl.Vector2(0.0, 0.0),
|
|
587
|
+
rotation_deg=0.0,
|
|
588
|
+
)
|
|
589
|
+
rl.draw_texture_pro(panel_tex, src, dst, rl.Vector2(0.0, 0.0), 0.0, rl.WHITE)
|
|
574
590
|
|
|
575
591
|
# Banner (Reaper / Well done)
|
|
576
592
|
banner = self.assets.text_reaper if banner_kind == "reaper" else self.assets.text_well_done
|
crimson/ui/perk_menu.py
CHANGED
|
@@ -2,12 +2,15 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import Protocol
|
|
5
6
|
|
|
6
7
|
import pyray as rl
|
|
7
8
|
|
|
8
9
|
from grim.assets import TextureLoader
|
|
9
10
|
from grim.fonts.small import SmallFontData, draw_small_text, measure_small_text_width
|
|
10
11
|
|
|
12
|
+
from .shadow import UI_SHADOW_OFFSET, draw_ui_quad_shadow
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
UI_BASE_WIDTH = 640.0
|
|
13
16
|
UI_BASE_HEIGHT = 480.0
|
|
@@ -143,13 +146,32 @@ def perk_menu_compute_layout(
|
|
|
143
146
|
)
|
|
144
147
|
|
|
145
148
|
|
|
146
|
-
def draw_menu_panel(
|
|
149
|
+
def draw_menu_panel(
|
|
150
|
+
texture: rl.Texture,
|
|
151
|
+
*,
|
|
152
|
+
dst: rl.Rectangle,
|
|
153
|
+
tint: rl.Color = rl.WHITE,
|
|
154
|
+
shadow: bool = False,
|
|
155
|
+
) -> None:
|
|
147
156
|
scale = float(dst.width) / float(texture.width)
|
|
148
157
|
top_h = MENU_PANEL_SLICE_Y1 * scale
|
|
149
158
|
bottom_h = (float(texture.height) - MENU_PANEL_SLICE_Y2) * scale
|
|
150
159
|
mid_h = float(dst.height) - top_h - bottom_h
|
|
151
160
|
if mid_h < 0.0:
|
|
152
161
|
src = rl.Rectangle(0.0, 0.0, float(texture.width), float(texture.height))
|
|
162
|
+
if shadow:
|
|
163
|
+
draw_ui_quad_shadow(
|
|
164
|
+
texture=texture,
|
|
165
|
+
src=src,
|
|
166
|
+
dst=rl.Rectangle(
|
|
167
|
+
float(dst.x + UI_SHADOW_OFFSET),
|
|
168
|
+
float(dst.y + UI_SHADOW_OFFSET),
|
|
169
|
+
float(dst.width),
|
|
170
|
+
float(dst.height),
|
|
171
|
+
),
|
|
172
|
+
origin=rl.Vector2(0.0, 0.0),
|
|
173
|
+
rotation_deg=0.0,
|
|
174
|
+
)
|
|
153
175
|
rl.draw_texture_pro(texture, src, dst, rl.Vector2(0.0, 0.0), 0.0, tint)
|
|
154
176
|
return
|
|
155
177
|
|
|
@@ -165,6 +187,43 @@ def draw_menu_panel(texture: rl.Texture, *, dst: rl.Rectangle, tint: rl.Color =
|
|
|
165
187
|
dst_bot = rl.Rectangle(float(dst.x), float(dst.y) + top_h + mid_h, float(dst.width), bottom_h)
|
|
166
188
|
|
|
167
189
|
origin = rl.Vector2(0.0, 0.0)
|
|
190
|
+
if shadow:
|
|
191
|
+
draw_ui_quad_shadow(
|
|
192
|
+
texture=texture,
|
|
193
|
+
src=src_top,
|
|
194
|
+
dst=rl.Rectangle(
|
|
195
|
+
float(dst_top.x + UI_SHADOW_OFFSET),
|
|
196
|
+
float(dst_top.y + UI_SHADOW_OFFSET),
|
|
197
|
+
float(dst_top.width),
|
|
198
|
+
float(dst_top.height),
|
|
199
|
+
),
|
|
200
|
+
origin=origin,
|
|
201
|
+
rotation_deg=0.0,
|
|
202
|
+
)
|
|
203
|
+
draw_ui_quad_shadow(
|
|
204
|
+
texture=texture,
|
|
205
|
+
src=src_mid,
|
|
206
|
+
dst=rl.Rectangle(
|
|
207
|
+
float(dst_mid.x + UI_SHADOW_OFFSET),
|
|
208
|
+
float(dst_mid.y + UI_SHADOW_OFFSET),
|
|
209
|
+
float(dst_mid.width),
|
|
210
|
+
float(dst_mid.height),
|
|
211
|
+
),
|
|
212
|
+
origin=origin,
|
|
213
|
+
rotation_deg=0.0,
|
|
214
|
+
)
|
|
215
|
+
draw_ui_quad_shadow(
|
|
216
|
+
texture=texture,
|
|
217
|
+
src=src_bot,
|
|
218
|
+
dst=rl.Rectangle(
|
|
219
|
+
float(dst_bot.x + UI_SHADOW_OFFSET),
|
|
220
|
+
float(dst_bot.y + UI_SHADOW_OFFSET),
|
|
221
|
+
float(dst_bot.width),
|
|
222
|
+
float(dst_bot.height),
|
|
223
|
+
),
|
|
224
|
+
origin=origin,
|
|
225
|
+
rotation_deg=0.0,
|
|
226
|
+
)
|
|
168
227
|
rl.draw_texture_pro(texture, src_top, dst_top, origin, 0.0, tint)
|
|
169
228
|
rl.draw_texture_pro(texture, src_mid, dst_mid, origin, 0.0, tint)
|
|
170
229
|
rl.draw_texture_pro(texture, src_bot, dst_bot, origin, 0.0, tint)
|
|
@@ -198,11 +257,11 @@ def load_perk_menu_assets(assets_root: Path) -> PerkMenuAssets:
|
|
|
198
257
|
fs_rel="ui/ui_textLevelUp.png",
|
|
199
258
|
),
|
|
200
259
|
menu_item=loader.get(name="ui_menuItem", paq_rel="ui/ui_menuItem.jaz", fs_rel="ui/ui_menuItem.png"),
|
|
201
|
-
button_sm=loader.get(name="ui_buttonSm", paq_rel="ui/
|
|
260
|
+
button_sm=loader.get(name="ui_buttonSm", paq_rel="ui/ui_button_64x32.jaz", fs_rel="ui/ui_button_64x32.png"),
|
|
202
261
|
button_md=loader.get(
|
|
203
262
|
name="ui_buttonMd",
|
|
204
|
-
paq_rel="ui/
|
|
205
|
-
fs_rel="ui/
|
|
263
|
+
paq_rel="ui/ui_button_128x32.jaz",
|
|
264
|
+
fs_rel="ui/ui_button_128x32.png",
|
|
206
265
|
),
|
|
207
266
|
cursor=loader.get(name="ui_cursor", paq_rel="ui/ui_cursor.jaz", fs_rel="ui/ui_cursor.png"),
|
|
208
267
|
aim=loader.get(name="ui_aim", paq_rel="ui/ui_aim.jaz", fs_rel="ui/ui_aim.png"),
|
|
@@ -281,6 +340,17 @@ def draw_menu_item(
|
|
|
281
340
|
return float(width)
|
|
282
341
|
|
|
283
342
|
|
|
343
|
+
class UiButtonTextures(Protocol):
|
|
344
|
+
button_sm: rl.Texture | None
|
|
345
|
+
button_md: rl.Texture | None
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@dataclass(slots=True)
|
|
349
|
+
class UiButtonTextureSet:
|
|
350
|
+
button_sm: rl.Texture | None
|
|
351
|
+
button_md: rl.Texture | None
|
|
352
|
+
|
|
353
|
+
|
|
284
354
|
@dataclass(slots=True)
|
|
285
355
|
class UiButtonState:
|
|
286
356
|
label: str
|
|
@@ -343,7 +413,7 @@ def _clamp(value: float, lo: float, hi: float) -> float:
|
|
|
343
413
|
|
|
344
414
|
|
|
345
415
|
def button_draw(
|
|
346
|
-
assets:
|
|
416
|
+
assets: UiButtonTextures,
|
|
347
417
|
font: SmallFontData | None,
|
|
348
418
|
state: UiButtonState,
|
|
349
419
|
*,
|
|
@@ -357,23 +427,45 @@ def button_draw(
|
|
|
357
427
|
return
|
|
358
428
|
|
|
359
429
|
if state.hover_t > 0:
|
|
360
|
-
alpha
|
|
430
|
+
# ui_button_update: highlight fill uses a hover-scaled alpha and click-biased blue tint.
|
|
431
|
+
# - base: (0.5, 0.5, 0.7)
|
|
432
|
+
# - click_anim: +0.0005 / +0.0007, clamped to 1.0 (towards white)
|
|
433
|
+
# - alpha: hover_anim * 0.001 * button.alpha
|
|
434
|
+
r = 0.5
|
|
435
|
+
g = 0.5
|
|
436
|
+
b = 0.7
|
|
361
437
|
if state.press_t > 0:
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
438
|
+
click_t = float(state.press_t)
|
|
439
|
+
g = min(1.0, 0.5 + click_t * 0.0005)
|
|
440
|
+
r = g
|
|
441
|
+
b = min(1.0, 0.7 + click_t * 0.0007)
|
|
442
|
+
a = float(state.hover_t) * 0.001 * state.alpha
|
|
443
|
+
hl = rl.Color(
|
|
444
|
+
int(255 * r),
|
|
445
|
+
int(255 * g),
|
|
446
|
+
int(255 * b),
|
|
447
|
+
int(255 * _clamp(a, 0.0, 1.0)),
|
|
448
|
+
)
|
|
449
|
+
rl.draw_rectangle(
|
|
450
|
+
int(x + 12.0 * scale),
|
|
451
|
+
int(y + 5.0 * scale),
|
|
452
|
+
int(width - 24.0 * scale),
|
|
453
|
+
int(22.0 * scale),
|
|
454
|
+
hl,
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
plate_tint = rl.Color(255, 255, 255, int(255 * _clamp(state.alpha, 0.0, 1.0)))
|
|
368
458
|
|
|
369
459
|
src = rl.Rectangle(0.0, 0.0, float(texture.width), float(texture.height))
|
|
370
460
|
dst = rl.Rectangle(float(x), float(y), float(width), float(32.0 * scale))
|
|
371
|
-
rl.draw_texture_pro(texture, src, dst, rl.Vector2(0.0, 0.0), 0.0,
|
|
461
|
+
rl.draw_texture_pro(texture, src, dst, rl.Vector2(0.0, 0.0), 0.0, plate_tint)
|
|
372
462
|
|
|
463
|
+
text_a = state.alpha if state.hovered else state.alpha * 0.7
|
|
464
|
+
text_tint = rl.Color(255, 255, 255, int(255 * _clamp(text_a, 0.0, 1.0)))
|
|
373
465
|
text_w = _ui_text_width(font, state.label, scale)
|
|
374
466
|
text_x = x + width * 0.5 - text_w * 0.5 + 1.0 * scale
|
|
375
467
|
text_y = y + 10.0 * scale
|
|
376
|
-
draw_ui_text(font, state.label, text_x, text_y, scale=scale, color=
|
|
468
|
+
draw_ui_text(font, state.label, text_x, text_y, scale=scale, color=text_tint)
|
|
377
469
|
|
|
378
470
|
|
|
379
471
|
def cursor_draw(assets: PerkMenuAssets, *, mouse: rl.Vector2, scale: float, alpha: float = 1.0) -> None:
|