crimsonland 0.1.0.dev7__py3-none-any.whl → 0.1.0.dev9__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/anim.py +1 -0
- crimson/game.py +5 -0
- crimson/gameplay.py +33 -8
- crimson/modes/base_gameplay_mode.py +75 -1
- crimson/modes/quest_mode.py +471 -44
- crimson/modes/rush_mode.py +12 -3
- crimson/modes/survival_mode.py +13 -2
- crimson/modes/tutorial_mode.py +12 -1
- crimson/modes/typo_mode.py +12 -4
- crimson/render/world_renderer.py +12 -16
- crimson/ui/hud.py +274 -51
- crimson/views/arsenal_debug.py +46 -1
- crimson/views/player.py +2 -2
- {crimsonland-0.1.0.dev7.dist-info → crimsonland-0.1.0.dev9.dist-info}/METADATA +1 -1
- {crimsonland-0.1.0.dev7.dist-info → crimsonland-0.1.0.dev9.dist-info}/RECORD +18 -18
- grim/console.py +14 -0
- {crimsonland-0.1.0.dev7.dist-info → crimsonland-0.1.0.dev9.dist-info}/WHEEL +0 -0
- {crimsonland-0.1.0.dev7.dist-info → crimsonland-0.1.0.dev9.dist-info}/entry_points.txt +0 -0
crimson/modes/quest_mode.py
CHANGED
|
@@ -7,24 +7,42 @@ 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.fonts.grim_mono import GrimMonoFont, load_grim_mono_font
|
|
12
13
|
from grim.view import ViewContext
|
|
13
14
|
|
|
14
15
|
from ..game_modes import GameMode
|
|
15
|
-
from ..gameplay import most_used_weapon_id_for_player, weapon_assign_player
|
|
16
|
+
from ..gameplay import most_used_weapon_id_for_player, perk_selection_current_choices, perk_selection_pick, weapon_assign_player
|
|
16
17
|
from ..input_codes import config_keybinds, input_code_is_down, input_code_is_pressed, player_move_fire_binds
|
|
17
18
|
from ..persistence.save_status import GameStatus
|
|
19
|
+
from ..perks import PerkId, perk_display_description, perk_display_name
|
|
18
20
|
from ..quests import quest_by_level
|
|
19
21
|
from ..quests.runtime import build_quest_spawn_table, tick_quest_completion_transition
|
|
20
22
|
from ..quests.timeline import quest_spawn_table_empty, tick_quest_mode_spawns
|
|
21
23
|
from ..quests.types import QuestContext, QuestDefinition, SpawnEntry
|
|
22
24
|
from ..terrain_assets import terrain_texture_by_id
|
|
23
25
|
from ..ui.cursor import draw_aim_cursor, draw_menu_cursor
|
|
24
|
-
from ..ui.hud import draw_hud_overlay,
|
|
25
|
-
from ..ui.perk_menu import
|
|
26
|
+
from ..ui.hud import draw_hud_overlay, hud_flags_for_game_mode
|
|
27
|
+
from ..ui.perk_menu import (
|
|
28
|
+
PerkMenuAssets,
|
|
29
|
+
PerkMenuLayout,
|
|
30
|
+
UiButtonState,
|
|
31
|
+
button_draw,
|
|
32
|
+
button_update,
|
|
33
|
+
button_width,
|
|
34
|
+
draw_menu_item,
|
|
35
|
+
draw_menu_panel,
|
|
36
|
+
draw_ui_text,
|
|
37
|
+
load_perk_menu_assets,
|
|
38
|
+
menu_item_hit_rect,
|
|
39
|
+
perk_menu_compute_layout,
|
|
40
|
+
ui_origin,
|
|
41
|
+
ui_scale,
|
|
42
|
+
wrap_ui_text,
|
|
43
|
+
)
|
|
26
44
|
from ..views.quest_title_overlay import draw_quest_title_overlay
|
|
27
|
-
from .base_gameplay_mode import BaseGameplayMode
|
|
45
|
+
from .base_gameplay_mode import BaseGameplayMode, _clamp
|
|
28
46
|
|
|
29
47
|
WORLD_SIZE = 1024.0
|
|
30
48
|
QUEST_TITLE_FADE_IN_MS = 500.0
|
|
@@ -32,6 +50,36 @@ QUEST_TITLE_HOLD_MS = 1000.0
|
|
|
32
50
|
QUEST_TITLE_FADE_OUT_MS = 500.0
|
|
33
51
|
QUEST_TITLE_TOTAL_MS = QUEST_TITLE_FADE_IN_MS + QUEST_TITLE_HOLD_MS + QUEST_TITLE_FADE_OUT_MS
|
|
34
52
|
|
|
53
|
+
UI_TEXT_SCALE = 1.0
|
|
54
|
+
UI_TEXT_COLOR = rl.Color(220, 220, 220, 255)
|
|
55
|
+
UI_HINT_COLOR = rl.Color(140, 140, 140, 255)
|
|
56
|
+
UI_SPONSOR_COLOR = rl.Color(255, 255, 255, int(255 * 0.5))
|
|
57
|
+
|
|
58
|
+
PERK_PROMPT_MAX_TIMER_MS = 200.0
|
|
59
|
+
PERK_PROMPT_OUTSET_X = 50.0
|
|
60
|
+
# Perk prompt bar geometry comes from `ui_menu_assets_init` + `ui_menu_layout_init`:
|
|
61
|
+
# - `ui_menu_item_element` is set_rect(512x64, offset -72,-60)
|
|
62
|
+
# - the perk prompt mutates quad coords: x = (x - 300) * 0.75, y = y * 0.75
|
|
63
|
+
PERK_PROMPT_BAR_SCALE = 0.75
|
|
64
|
+
PERK_PROMPT_BAR_BASE_OFFSET_X = -72.0
|
|
65
|
+
PERK_PROMPT_BAR_BASE_OFFSET_Y = -60.0
|
|
66
|
+
PERK_PROMPT_BAR_SHIFT_X = -300.0
|
|
67
|
+
|
|
68
|
+
# `ui_textLevelUp` is set_rect(75x25, offset -230,-27), then its quad coords are:
|
|
69
|
+
# x = x * 0.85 - 46, y = y * 0.85 - 4
|
|
70
|
+
PERK_PROMPT_LEVEL_UP_SCALE = 0.85
|
|
71
|
+
PERK_PROMPT_LEVEL_UP_BASE_OFFSET_X = -230.0
|
|
72
|
+
PERK_PROMPT_LEVEL_UP_BASE_OFFSET_Y = -27.0
|
|
73
|
+
PERK_PROMPT_LEVEL_UP_BASE_W = 75.0
|
|
74
|
+
PERK_PROMPT_LEVEL_UP_BASE_H = 25.0
|
|
75
|
+
PERK_PROMPT_LEVEL_UP_SHIFT_X = -46.0
|
|
76
|
+
PERK_PROMPT_LEVEL_UP_SHIFT_Y = -4.0
|
|
77
|
+
|
|
78
|
+
PERK_PROMPT_TEXT_MARGIN_X = 16.0
|
|
79
|
+
PERK_PROMPT_TEXT_OFFSET_Y = 8.0
|
|
80
|
+
|
|
81
|
+
PERK_MENU_TRANSITION_MS = 500.0
|
|
82
|
+
|
|
35
83
|
|
|
36
84
|
@dataclass(slots=True)
|
|
37
85
|
class _QuestRunState:
|
|
@@ -99,6 +147,7 @@ class QuestMode(BaseGameplayMode):
|
|
|
99
147
|
demo_mode_active: bool = False,
|
|
100
148
|
texture_cache: PaqTextureCache | None = None,
|
|
101
149
|
config: CrimsonConfig | None = None,
|
|
150
|
+
console: ConsoleState | None = None,
|
|
102
151
|
audio: AudioState | None = None,
|
|
103
152
|
audio_rng: random.Random | None = None,
|
|
104
153
|
) -> None:
|
|
@@ -111,32 +160,51 @@ class QuestMode(BaseGameplayMode):
|
|
|
111
160
|
hardcore=False,
|
|
112
161
|
texture_cache=texture_cache,
|
|
113
162
|
config=config,
|
|
163
|
+
console=console,
|
|
114
164
|
audio=audio,
|
|
115
165
|
audio_rng=audio_rng,
|
|
116
166
|
)
|
|
117
167
|
self._quest = _QuestRunState()
|
|
118
168
|
self._selected_level: str | None = None
|
|
119
169
|
self._outcome: QuestRunOutcome | None = None
|
|
120
|
-
self.
|
|
170
|
+
self._perk_menu_assets: PerkMenuAssets | None = None
|
|
121
171
|
self._grim_mono: GrimMonoFont | None = None
|
|
122
172
|
|
|
173
|
+
self._perk_prompt_timer_ms = 0.0
|
|
174
|
+
self._perk_prompt_hover = False
|
|
175
|
+
self._perk_prompt_pulse = 0.0
|
|
176
|
+
self._perk_menu_open = False
|
|
177
|
+
self._perk_menu_selected = 0
|
|
178
|
+
self._perk_menu_timeline_ms = 0.0
|
|
179
|
+
self._perk_ui_layout = PerkMenuLayout()
|
|
180
|
+
self._perk_cancel_button = UiButtonState("Cancel")
|
|
181
|
+
|
|
123
182
|
def open(self) -> None:
|
|
124
183
|
super().open()
|
|
125
184
|
self._quest = _QuestRunState()
|
|
126
185
|
self._outcome = None
|
|
127
|
-
self.
|
|
128
|
-
if self.
|
|
129
|
-
self._missing_assets.extend(self.
|
|
186
|
+
self._perk_menu_assets = load_perk_menu_assets(self._assets_root)
|
|
187
|
+
if self._perk_menu_assets.missing:
|
|
188
|
+
self._missing_assets.extend(self._perk_menu_assets.missing)
|
|
130
189
|
try:
|
|
131
190
|
self._grim_mono = load_grim_mono_font(self._assets_root, self._missing_assets)
|
|
132
191
|
except Exception:
|
|
133
192
|
self._grim_mono = None
|
|
134
193
|
|
|
194
|
+
self._perk_prompt_timer_ms = 0.0
|
|
195
|
+
self._perk_prompt_hover = False
|
|
196
|
+
self._perk_prompt_pulse = 0.0
|
|
197
|
+
self._perk_menu_open = False
|
|
198
|
+
self._perk_menu_selected = 0
|
|
199
|
+
self._perk_menu_timeline_ms = 0.0
|
|
200
|
+
self._perk_ui_layout = PerkMenuLayout()
|
|
201
|
+
self._perk_cancel_button = UiButtonState("Cancel")
|
|
202
|
+
|
|
135
203
|
def close(self) -> None:
|
|
136
204
|
if self._grim_mono is not None:
|
|
137
205
|
rl.unload_texture(self._grim_mono.texture)
|
|
138
206
|
self._grim_mono = None
|
|
139
|
-
self.
|
|
207
|
+
self._perk_menu_assets = None
|
|
140
208
|
super().close()
|
|
141
209
|
|
|
142
210
|
def select_level(self, level: str | None) -> None:
|
|
@@ -229,6 +297,11 @@ class QuestMode(BaseGameplayMode):
|
|
|
229
297
|
status.increment_quest_play_count(idx)
|
|
230
298
|
|
|
231
299
|
def _handle_input(self) -> None:
|
|
300
|
+
if self._perk_menu_open and rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
301
|
+
self._world.audio_router.play_sfx("sfx_ui_buttonclick")
|
|
302
|
+
self._close_perk_menu()
|
|
303
|
+
return
|
|
304
|
+
|
|
232
305
|
if rl.is_key_pressed(rl.KeyboardKey.KEY_TAB):
|
|
233
306
|
self._paused = not self._paused
|
|
234
307
|
|
|
@@ -274,22 +347,381 @@ class QuestMode(BaseGameplayMode):
|
|
|
274
347
|
reload_pressed=bool(reload_pressed),
|
|
275
348
|
)
|
|
276
349
|
|
|
350
|
+
def _perk_prompt_label(self) -> str:
|
|
351
|
+
if self._config is not None and not bool(int(self._config.data.get("ui_info_texts", 1) or 0)):
|
|
352
|
+
return ""
|
|
353
|
+
pending = int(self._state.perk_selection.pending_count)
|
|
354
|
+
if pending <= 0:
|
|
355
|
+
return ""
|
|
356
|
+
suffix = f" ({pending})" if pending > 1 else ""
|
|
357
|
+
return f"Press Mouse2 to pick a perk{suffix}"
|
|
358
|
+
|
|
359
|
+
def _perk_prompt_hinge(self) -> tuple[float, float]:
|
|
360
|
+
screen_w = float(rl.get_screen_width())
|
|
361
|
+
hinge_x = screen_w + PERK_PROMPT_OUTSET_X
|
|
362
|
+
hinge_y = 80.0 if int(screen_w) == 640 else 40.0
|
|
363
|
+
return hinge_x, hinge_y
|
|
364
|
+
|
|
365
|
+
def _perk_prompt_rect(self, label: str, *, scale: float = UI_TEXT_SCALE) -> rl.Rectangle:
|
|
366
|
+
hinge_x, hinge_y = self._perk_prompt_hinge()
|
|
367
|
+
if self._perk_menu_assets is not None and self._perk_menu_assets.menu_item is not None:
|
|
368
|
+
tex = self._perk_menu_assets.menu_item
|
|
369
|
+
bar_w = float(tex.width) * PERK_PROMPT_BAR_SCALE
|
|
370
|
+
bar_h = float(tex.height) * PERK_PROMPT_BAR_SCALE
|
|
371
|
+
local_x = (PERK_PROMPT_BAR_BASE_OFFSET_X + PERK_PROMPT_BAR_SHIFT_X) * PERK_PROMPT_BAR_SCALE
|
|
372
|
+
local_y = PERK_PROMPT_BAR_BASE_OFFSET_Y * PERK_PROMPT_BAR_SCALE
|
|
373
|
+
return rl.Rectangle(
|
|
374
|
+
float(hinge_x + local_x),
|
|
375
|
+
float(hinge_y + local_y),
|
|
376
|
+
float(bar_w),
|
|
377
|
+
float(bar_h),
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
margin = 16.0 * scale
|
|
381
|
+
text_w = float(self._ui_text_width(label, scale))
|
|
382
|
+
text_h = float(self._ui_line_height(scale))
|
|
383
|
+
x = float(rl.get_screen_width()) - margin - text_w
|
|
384
|
+
y = margin
|
|
385
|
+
return rl.Rectangle(x, y, text_w, text_h)
|
|
386
|
+
|
|
387
|
+
def _open_perk_menu(self) -> None:
|
|
388
|
+
if self._perk_menu_open:
|
|
389
|
+
return
|
|
390
|
+
players = self._world.players
|
|
391
|
+
choices = perk_selection_current_choices(
|
|
392
|
+
self._state,
|
|
393
|
+
players,
|
|
394
|
+
self._state.perk_selection,
|
|
395
|
+
game_mode=int(GameMode.QUESTS),
|
|
396
|
+
player_count=len(players),
|
|
397
|
+
)
|
|
398
|
+
if not choices:
|
|
399
|
+
self._perk_menu_open = False
|
|
400
|
+
return
|
|
401
|
+
self._world.audio_router.play_sfx("sfx_ui_panelclick")
|
|
402
|
+
self._perk_menu_open = True
|
|
403
|
+
self._perk_menu_selected = 0
|
|
404
|
+
|
|
405
|
+
def _close_perk_menu(self) -> None:
|
|
406
|
+
self._perk_menu_open = False
|
|
407
|
+
if int(self._state.perk_selection.pending_count) > 0:
|
|
408
|
+
# Reset the prompt swing so each pending perk replays the intro.
|
|
409
|
+
self._perk_prompt_timer_ms = 0.0
|
|
410
|
+
self._perk_prompt_hover = False
|
|
411
|
+
self._perk_prompt_pulse = 0.0
|
|
412
|
+
|
|
413
|
+
def _perk_menu_handle_input(self, dt_frame: float, dt_ms: float) -> None:
|
|
414
|
+
if self._perk_menu_assets is None:
|
|
415
|
+
self._close_perk_menu()
|
|
416
|
+
return
|
|
417
|
+
|
|
418
|
+
perk_state = self._state.perk_selection
|
|
419
|
+
players = self._world.players
|
|
420
|
+
choices = perk_selection_current_choices(
|
|
421
|
+
self._state,
|
|
422
|
+
players,
|
|
423
|
+
perk_state,
|
|
424
|
+
game_mode=int(GameMode.QUESTS),
|
|
425
|
+
player_count=len(players),
|
|
426
|
+
)
|
|
427
|
+
if not choices:
|
|
428
|
+
self._close_perk_menu()
|
|
429
|
+
return
|
|
430
|
+
if self._perk_menu_selected >= len(choices):
|
|
431
|
+
self._perk_menu_selected = 0
|
|
432
|
+
|
|
433
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_DOWN):
|
|
434
|
+
self._perk_menu_selected = (self._perk_menu_selected + 1) % len(choices)
|
|
435
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_UP):
|
|
436
|
+
self._perk_menu_selected = (self._perk_menu_selected - 1) % len(choices)
|
|
437
|
+
|
|
438
|
+
screen_w = float(rl.get_screen_width())
|
|
439
|
+
screen_h = float(rl.get_screen_height())
|
|
440
|
+
scale = ui_scale(screen_w, screen_h)
|
|
441
|
+
origin_x, origin_y = ui_origin(screen_w, screen_h, scale)
|
|
442
|
+
menu_t = _clamp(self._perk_menu_timeline_ms / PERK_MENU_TRANSITION_MS, 0.0, 1.0)
|
|
443
|
+
slide_x = (menu_t - 1.0) * (self._perk_ui_layout.panel_w * scale)
|
|
444
|
+
|
|
445
|
+
mouse = self._ui_mouse_pos()
|
|
446
|
+
click = rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT)
|
|
447
|
+
|
|
448
|
+
master_owned = int(self._player.perk_counts[int(PerkId.PERK_MASTER)]) > 0
|
|
449
|
+
expert_owned = int(self._player.perk_counts[int(PerkId.PERK_EXPERT)]) > 0
|
|
450
|
+
computed = perk_menu_compute_layout(
|
|
451
|
+
self._perk_ui_layout,
|
|
452
|
+
screen_w=screen_w,
|
|
453
|
+
origin_x=origin_x + slide_x,
|
|
454
|
+
origin_y=origin_y,
|
|
455
|
+
scale=scale,
|
|
456
|
+
choice_count=len(choices),
|
|
457
|
+
expert_owned=expert_owned,
|
|
458
|
+
master_owned=master_owned,
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
fx_toggle = int(self._config.data.get("fx_toggle", 0) or 0) if self._config is not None else 0
|
|
462
|
+
for idx, perk_id in enumerate(choices):
|
|
463
|
+
label = perk_display_name(int(perk_id), fx_toggle=fx_toggle)
|
|
464
|
+
item_x = computed.list_x
|
|
465
|
+
item_y = computed.list_y + float(idx) * computed.list_step_y
|
|
466
|
+
rect = menu_item_hit_rect(self._small, label, x=item_x, y=item_y, scale=scale)
|
|
467
|
+
if rl.check_collision_point_rec(mouse, rect):
|
|
468
|
+
self._perk_menu_selected = idx
|
|
469
|
+
if click:
|
|
470
|
+
self._world.audio_router.play_sfx("sfx_ui_buttonclick")
|
|
471
|
+
picked = perk_selection_pick(
|
|
472
|
+
self._state,
|
|
473
|
+
players,
|
|
474
|
+
perk_state,
|
|
475
|
+
idx,
|
|
476
|
+
game_mode=int(GameMode.QUESTS),
|
|
477
|
+
player_count=len(players),
|
|
478
|
+
dt=dt_frame,
|
|
479
|
+
creatures=self._creatures.entries,
|
|
480
|
+
)
|
|
481
|
+
if picked is not None:
|
|
482
|
+
self._world.audio_router.play_sfx("sfx_ui_bonus")
|
|
483
|
+
self._close_perk_menu()
|
|
484
|
+
return
|
|
485
|
+
break
|
|
486
|
+
|
|
487
|
+
cancel_w = button_width(self._small, self._perk_cancel_button.label, scale=scale, force_wide=self._perk_cancel_button.force_wide)
|
|
488
|
+
cancel_x = computed.cancel_x
|
|
489
|
+
button_y = computed.cancel_y
|
|
490
|
+
|
|
491
|
+
if button_update(
|
|
492
|
+
self._perk_cancel_button,
|
|
493
|
+
x=cancel_x,
|
|
494
|
+
y=button_y,
|
|
495
|
+
width=cancel_w,
|
|
496
|
+
dt_ms=dt_ms,
|
|
497
|
+
mouse=mouse,
|
|
498
|
+
click=click,
|
|
499
|
+
):
|
|
500
|
+
self._world.audio_router.play_sfx("sfx_ui_buttonclick")
|
|
501
|
+
self._close_perk_menu()
|
|
502
|
+
return
|
|
503
|
+
|
|
504
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_ENTER) or rl.is_key_pressed(rl.KeyboardKey.KEY_SPACE):
|
|
505
|
+
self._world.audio_router.play_sfx("sfx_ui_buttonclick")
|
|
506
|
+
picked = perk_selection_pick(
|
|
507
|
+
self._state,
|
|
508
|
+
players,
|
|
509
|
+
perk_state,
|
|
510
|
+
self._perk_menu_selected,
|
|
511
|
+
game_mode=int(GameMode.QUESTS),
|
|
512
|
+
player_count=len(players),
|
|
513
|
+
dt=dt_frame,
|
|
514
|
+
creatures=self._creatures.entries,
|
|
515
|
+
)
|
|
516
|
+
if picked is not None:
|
|
517
|
+
self._world.audio_router.play_sfx("sfx_ui_bonus")
|
|
518
|
+
self._close_perk_menu()
|
|
519
|
+
|
|
520
|
+
def _draw_perk_prompt(self) -> None:
|
|
521
|
+
if self._perk_menu_open or self._perk_menu_timeline_ms > 1e-3:
|
|
522
|
+
return
|
|
523
|
+
if not any(player.health > 0.0 for player in self._world.players):
|
|
524
|
+
return
|
|
525
|
+
pending = int(self._state.perk_selection.pending_count)
|
|
526
|
+
if pending <= 0:
|
|
527
|
+
return
|
|
528
|
+
label = self._perk_prompt_label()
|
|
529
|
+
if not label:
|
|
530
|
+
return
|
|
531
|
+
|
|
532
|
+
alpha = float(self._perk_prompt_timer_ms) / PERK_PROMPT_MAX_TIMER_MS
|
|
533
|
+
if alpha <= 1e-3:
|
|
534
|
+
return
|
|
535
|
+
|
|
536
|
+
hinge_x, hinge_y = self._perk_prompt_hinge()
|
|
537
|
+
# Prompt swings counter-clockwise; raylib's Y-down makes positive rotation clockwise.
|
|
538
|
+
rot_deg = -(1.0 - alpha) * 90.0
|
|
539
|
+
tint = rl.Color(255, 255, 255, int(255 * alpha))
|
|
540
|
+
|
|
541
|
+
text_w = float(self._ui_text_width(label, UI_TEXT_SCALE))
|
|
542
|
+
x = float(rl.get_screen_width()) - PERK_PROMPT_TEXT_MARGIN_X - text_w
|
|
543
|
+
y = hinge_y + PERK_PROMPT_TEXT_OFFSET_Y
|
|
544
|
+
color = rl.Color(UI_TEXT_COLOR.r, UI_TEXT_COLOR.g, UI_TEXT_COLOR.b, int(255 * alpha))
|
|
545
|
+
draw_ui_text(self._small, label, x, y, scale=UI_TEXT_SCALE, color=color)
|
|
546
|
+
|
|
547
|
+
if self._perk_menu_assets is not None and self._perk_menu_assets.menu_item is not None:
|
|
548
|
+
tex = self._perk_menu_assets.menu_item
|
|
549
|
+
bar_w = float(tex.width) * PERK_PROMPT_BAR_SCALE
|
|
550
|
+
bar_h = float(tex.height) * PERK_PROMPT_BAR_SCALE
|
|
551
|
+
local_x = (PERK_PROMPT_BAR_BASE_OFFSET_X + PERK_PROMPT_BAR_SHIFT_X) * PERK_PROMPT_BAR_SCALE
|
|
552
|
+
local_y = PERK_PROMPT_BAR_BASE_OFFSET_Y * PERK_PROMPT_BAR_SCALE
|
|
553
|
+
src = rl.Rectangle(float(tex.width), 0.0, -float(tex.width), float(tex.height))
|
|
554
|
+
dst = rl.Rectangle(float(hinge_x), float(hinge_y), float(bar_w), float(bar_h))
|
|
555
|
+
origin = rl.Vector2(float(-local_x), float(-local_y))
|
|
556
|
+
rl.draw_texture_pro(tex, src, dst, origin, rot_deg, tint)
|
|
557
|
+
|
|
558
|
+
if self._perk_menu_assets is not None and self._perk_menu_assets.title_level_up is not None:
|
|
559
|
+
tex = self._perk_menu_assets.title_level_up
|
|
560
|
+
local_x = PERK_PROMPT_LEVEL_UP_BASE_OFFSET_X * PERK_PROMPT_LEVEL_UP_SCALE + PERK_PROMPT_LEVEL_UP_SHIFT_X
|
|
561
|
+
local_y = PERK_PROMPT_LEVEL_UP_BASE_OFFSET_Y * PERK_PROMPT_LEVEL_UP_SCALE + PERK_PROMPT_LEVEL_UP_SHIFT_Y
|
|
562
|
+
w = PERK_PROMPT_LEVEL_UP_BASE_W * PERK_PROMPT_LEVEL_UP_SCALE
|
|
563
|
+
h = PERK_PROMPT_LEVEL_UP_BASE_H * PERK_PROMPT_LEVEL_UP_SCALE
|
|
564
|
+
pulse_alpha = (100.0 + float(int(self._perk_prompt_pulse * 155.0 / 1000.0))) / 255.0
|
|
565
|
+
pulse_alpha = max(0.0, min(1.0, pulse_alpha))
|
|
566
|
+
label_alpha = max(0.0, min(1.0, alpha * pulse_alpha))
|
|
567
|
+
pulse_tint = rl.Color(255, 255, 255, int(255 * label_alpha))
|
|
568
|
+
src = rl.Rectangle(0.0, 0.0, float(tex.width), float(tex.height))
|
|
569
|
+
dst = rl.Rectangle(float(hinge_x), float(hinge_y), float(w), float(h))
|
|
570
|
+
origin = rl.Vector2(float(-local_x), float(-local_y))
|
|
571
|
+
rl.draw_texture_pro(tex, src, dst, origin, rot_deg, pulse_tint)
|
|
572
|
+
if label_alpha > 0.0:
|
|
573
|
+
rl.begin_blend_mode(rl.BLEND_ADDITIVE)
|
|
574
|
+
rl.draw_texture_pro(tex, src, dst, origin, rot_deg, pulse_tint)
|
|
575
|
+
rl.end_blend_mode()
|
|
576
|
+
|
|
577
|
+
def _draw_perk_menu(self) -> None:
|
|
578
|
+
menu_t = _clamp(self._perk_menu_timeline_ms / PERK_MENU_TRANSITION_MS, 0.0, 1.0)
|
|
579
|
+
if menu_t <= 1e-3:
|
|
580
|
+
return
|
|
581
|
+
if self._perk_menu_assets is None:
|
|
582
|
+
return
|
|
583
|
+
|
|
584
|
+
perk_state = self._state.perk_selection
|
|
585
|
+
players = self._world.players
|
|
586
|
+
choices = perk_selection_current_choices(
|
|
587
|
+
self._state,
|
|
588
|
+
players,
|
|
589
|
+
perk_state,
|
|
590
|
+
game_mode=int(GameMode.QUESTS),
|
|
591
|
+
player_count=len(players),
|
|
592
|
+
)
|
|
593
|
+
if not choices:
|
|
594
|
+
return
|
|
595
|
+
|
|
596
|
+
screen_w = float(rl.get_screen_width())
|
|
597
|
+
screen_h = float(rl.get_screen_height())
|
|
598
|
+
scale = ui_scale(screen_w, screen_h)
|
|
599
|
+
origin_x, origin_y = ui_origin(screen_w, screen_h, scale)
|
|
600
|
+
slide_x = (menu_t - 1.0) * (self._perk_ui_layout.panel_w * scale)
|
|
601
|
+
|
|
602
|
+
master_owned = int(self._player.perk_counts[int(PerkId.PERK_MASTER)]) > 0
|
|
603
|
+
expert_owned = int(self._player.perk_counts[int(PerkId.PERK_EXPERT)]) > 0
|
|
604
|
+
computed = perk_menu_compute_layout(
|
|
605
|
+
self._perk_ui_layout,
|
|
606
|
+
screen_w=screen_w,
|
|
607
|
+
origin_x=origin_x + slide_x,
|
|
608
|
+
origin_y=origin_y,
|
|
609
|
+
scale=scale,
|
|
610
|
+
choice_count=len(choices),
|
|
611
|
+
expert_owned=expert_owned,
|
|
612
|
+
master_owned=master_owned,
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
panel_tex = self._perk_menu_assets.menu_panel
|
|
616
|
+
if panel_tex is not None:
|
|
617
|
+
draw_menu_panel(panel_tex, dst=computed.panel)
|
|
618
|
+
|
|
619
|
+
title_tex = self._perk_menu_assets.title_pick_perk
|
|
620
|
+
if title_tex is not None:
|
|
621
|
+
src = rl.Rectangle(0.0, 0.0, float(title_tex.width), float(title_tex.height))
|
|
622
|
+
rl.draw_texture_pro(title_tex, src, computed.title, rl.Vector2(0.0, 0.0), 0.0, rl.WHITE)
|
|
623
|
+
|
|
624
|
+
sponsor = None
|
|
625
|
+
if master_owned:
|
|
626
|
+
sponsor = "extra perks sponsored by the Perk Master"
|
|
627
|
+
elif expert_owned:
|
|
628
|
+
sponsor = "extra perk sponsored by the Perk Expert"
|
|
629
|
+
if sponsor:
|
|
630
|
+
draw_ui_text(self._small, sponsor, computed.sponsor_x, computed.sponsor_y, scale=scale, color=UI_SPONSOR_COLOR)
|
|
631
|
+
|
|
632
|
+
mouse = self._ui_mouse_pos()
|
|
633
|
+
fx_toggle = int(self._config.data.get("fx_toggle", 0) or 0) if self._config is not None else 0
|
|
634
|
+
for idx, perk_id in enumerate(choices):
|
|
635
|
+
label = perk_display_name(int(perk_id), fx_toggle=fx_toggle)
|
|
636
|
+
item_x = computed.list_x
|
|
637
|
+
item_y = computed.list_y + float(idx) * computed.list_step_y
|
|
638
|
+
rect = menu_item_hit_rect(self._small, label, x=item_x, y=item_y, scale=scale)
|
|
639
|
+
hovered = rl.check_collision_point_rec(mouse, rect) or (idx == self._perk_menu_selected)
|
|
640
|
+
draw_menu_item(self._small, label, x=item_x, y=item_y, scale=scale, hovered=hovered)
|
|
641
|
+
|
|
642
|
+
selected = choices[self._perk_menu_selected]
|
|
643
|
+
desc = perk_display_description(int(selected), fx_toggle=fx_toggle)
|
|
644
|
+
desc_x = float(computed.desc.x)
|
|
645
|
+
desc_y = float(computed.desc.y)
|
|
646
|
+
desc_w = float(computed.desc.width)
|
|
647
|
+
desc_h = float(computed.desc.height)
|
|
648
|
+
desc_scale = scale * 0.85
|
|
649
|
+
desc_lines = wrap_ui_text(self._small, desc, max_width=desc_w, scale=desc_scale)
|
|
650
|
+
line_h = float(self._small.cell_size * desc_scale) if self._small is not None else float(20 * desc_scale)
|
|
651
|
+
y = desc_y
|
|
652
|
+
for line in desc_lines:
|
|
653
|
+
if y + line_h > desc_y + desc_h:
|
|
654
|
+
break
|
|
655
|
+
draw_ui_text(self._small, line, desc_x, y, scale=desc_scale, color=UI_TEXT_COLOR)
|
|
656
|
+
y += line_h
|
|
657
|
+
|
|
658
|
+
cancel_w = button_width(self._small, self._perk_cancel_button.label, scale=scale, force_wide=self._perk_cancel_button.force_wide)
|
|
659
|
+
cancel_x = computed.cancel_x
|
|
660
|
+
button_y = computed.cancel_y
|
|
661
|
+
button_draw(self._perk_menu_assets, self._small, self._perk_cancel_button, x=cancel_x, y=button_y, width=cancel_w, scale=scale)
|
|
662
|
+
|
|
277
663
|
def update(self, dt: float) -> None:
|
|
278
664
|
self._update_audio(dt)
|
|
279
665
|
|
|
280
|
-
dt_frame = self._tick_frame(dt)
|
|
281
|
-
dt_ms = float(dt_frame * 1000.0)
|
|
666
|
+
dt_frame, dt_ui_ms = self._tick_frame(dt)
|
|
282
667
|
self._handle_input()
|
|
283
668
|
|
|
284
669
|
if self.close_requested:
|
|
285
670
|
return
|
|
286
671
|
|
|
287
672
|
any_alive = any(player.health > 0.0 for player in self._world.players)
|
|
288
|
-
|
|
673
|
+
perk_pending = int(self._state.perk_selection.pending_count) > 0 and any_alive
|
|
674
|
+
|
|
675
|
+
self._perk_prompt_hover = False
|
|
676
|
+
if self._perk_menu_open:
|
|
677
|
+
self._perk_menu_handle_input(dt_frame, dt_ui_ms)
|
|
678
|
+
|
|
679
|
+
perk_menu_active = self._perk_menu_open or self._perk_menu_timeline_ms > 1e-3
|
|
680
|
+
|
|
681
|
+
if (not perk_menu_active) and perk_pending and (not self._paused):
|
|
682
|
+
label = self._perk_prompt_label()
|
|
683
|
+
if label:
|
|
684
|
+
rect = self._perk_prompt_rect(label)
|
|
685
|
+
self._perk_prompt_hover = rl.check_collision_point_rec(self._ui_mouse_pos(), rect)
|
|
686
|
+
|
|
687
|
+
keybinds = config_keybinds(self._config)
|
|
688
|
+
if not keybinds:
|
|
689
|
+
keybinds = (0x11, 0x1F, 0x1E, 0x20, 0x100)
|
|
690
|
+
_up_key, _down_key, _left_key, _right_key, fire_key = player_move_fire_binds(keybinds, 0)
|
|
691
|
+
|
|
692
|
+
pick_key = 0x101
|
|
693
|
+
if self._config is not None:
|
|
694
|
+
pick_key = int(self._config.data.get("keybind_pick_perk", pick_key) or pick_key)
|
|
695
|
+
|
|
696
|
+
if input_code_is_pressed(pick_key) and (not input_code_is_down(fire_key)):
|
|
697
|
+
self._perk_prompt_pulse = 1000.0
|
|
698
|
+
self._open_perk_menu()
|
|
699
|
+
elif self._perk_prompt_hover and input_code_is_pressed(fire_key):
|
|
700
|
+
self._perk_prompt_pulse = 1000.0
|
|
701
|
+
self._open_perk_menu()
|
|
702
|
+
|
|
703
|
+
perk_menu_active = self._perk_menu_open or self._perk_menu_timeline_ms > 1e-3
|
|
704
|
+
|
|
705
|
+
if not self._paused:
|
|
706
|
+
pulse_delta = dt_ui_ms * (6.0 if self._perk_prompt_hover else -2.0)
|
|
707
|
+
self._perk_prompt_pulse = _clamp(self._perk_prompt_pulse + pulse_delta, 0.0, 1000.0)
|
|
708
|
+
|
|
709
|
+
prompt_active = perk_pending and (not perk_menu_active) and (not self._paused)
|
|
710
|
+
if prompt_active:
|
|
711
|
+
self._perk_prompt_timer_ms = _clamp(self._perk_prompt_timer_ms + dt_ui_ms, 0.0, PERK_PROMPT_MAX_TIMER_MS)
|
|
712
|
+
else:
|
|
713
|
+
self._perk_prompt_timer_ms = _clamp(self._perk_prompt_timer_ms - dt_ui_ms, 0.0, PERK_PROMPT_MAX_TIMER_MS)
|
|
714
|
+
|
|
715
|
+
if self._perk_menu_open:
|
|
716
|
+
self._perk_menu_timeline_ms = _clamp(self._perk_menu_timeline_ms + dt_ui_ms, 0.0, PERK_MENU_TRANSITION_MS)
|
|
717
|
+
else:
|
|
718
|
+
self._perk_menu_timeline_ms = _clamp(self._perk_menu_timeline_ms - dt_ui_ms, 0.0, PERK_MENU_TRANSITION_MS)
|
|
719
|
+
|
|
720
|
+
dt_world = 0.0 if self._paused or (not any_alive) or perk_menu_active else dt_frame
|
|
289
721
|
if dt_world <= 0.0:
|
|
290
722
|
return
|
|
291
723
|
|
|
292
|
-
self._quest.quest_name_timer_ms +=
|
|
724
|
+
self._quest.quest_name_timer_ms += dt_world * 1000.0
|
|
293
725
|
|
|
294
726
|
input_state = self._build_input()
|
|
295
727
|
self._world.update(
|
|
@@ -405,11 +837,17 @@ class QuestMode(BaseGameplayMode):
|
|
|
405
837
|
self.close_requested = True
|
|
406
838
|
|
|
407
839
|
def draw(self) -> None:
|
|
408
|
-
self.
|
|
840
|
+
perk_menu_active = self._perk_menu_open or self._perk_menu_timeline_ms > 1e-3
|
|
841
|
+
self._world.draw(draw_aim_indicators=not perk_menu_active)
|
|
409
842
|
self._draw_screen_fade()
|
|
410
843
|
|
|
411
844
|
hud_bottom = 0.0
|
|
412
|
-
if self._hud_assets is not None:
|
|
845
|
+
if (not perk_menu_active) and self._hud_assets is not None:
|
|
846
|
+
total = int(self._quest.total_spawn_count)
|
|
847
|
+
kills = int(self._creatures.kill_count)
|
|
848
|
+
quest_progress_ratio = float(kills) / float(total) if total > 0 else None
|
|
849
|
+
hud_flags = hud_flags_for_game_mode(self._config_game_mode_id())
|
|
850
|
+
self._draw_target_health_bar()
|
|
413
851
|
hud_bottom = draw_hud_overlay(
|
|
414
852
|
self._hud_assets,
|
|
415
853
|
player=self._player,
|
|
@@ -418,31 +856,14 @@ class QuestMode(BaseGameplayMode):
|
|
|
418
856
|
elapsed_ms=float(self._quest.spawn_timeline_ms),
|
|
419
857
|
font=self._small,
|
|
420
858
|
frame_dt_ms=self._last_dt_ms,
|
|
421
|
-
|
|
422
|
-
|
|
859
|
+
show_health=hud_flags.show_health,
|
|
860
|
+
show_weapon=hud_flags.show_weapon,
|
|
861
|
+
show_xp=hud_flags.show_xp,
|
|
862
|
+
show_time=hud_flags.show_time,
|
|
863
|
+
show_quest_hud=hud_flags.show_quest_hud,
|
|
864
|
+
quest_progress_ratio=quest_progress_ratio,
|
|
865
|
+
small_indicators=self._hud_small_indicators(),
|
|
423
866
|
)
|
|
424
|
-
total = int(self._quest.total_spawn_count)
|
|
425
|
-
if total > 0:
|
|
426
|
-
kills = int(self._creatures.kill_count)
|
|
427
|
-
ratio = max(0.0, min(1.0, float(kills) / float(total)))
|
|
428
|
-
scale = hud_ui_scale(float(rl.get_screen_width()), float(rl.get_screen_height()))
|
|
429
|
-
bar_x = 255.0 * scale
|
|
430
|
-
bar_y = 30.0 * scale
|
|
431
|
-
bar_w = 120.0 * scale
|
|
432
|
-
bar_h = 6.0 * scale
|
|
433
|
-
bg = rl.Color(40, 40, 48, 200)
|
|
434
|
-
fg = rl.Color(220, 220, 220, 240)
|
|
435
|
-
rl.draw_rectangle(int(bar_x), int(bar_y), int(bar_w), int(bar_h), bg)
|
|
436
|
-
inner_w = max(0.0, bar_w - 2.0 * scale)
|
|
437
|
-
inner_h = max(0.0, bar_h - 2.0 * scale)
|
|
438
|
-
rl.draw_rectangle(
|
|
439
|
-
int(bar_x + scale),
|
|
440
|
-
int(bar_y + scale),
|
|
441
|
-
int(inner_w * ratio),
|
|
442
|
-
int(inner_h),
|
|
443
|
-
fg,
|
|
444
|
-
)
|
|
445
|
-
self._draw_ui_text(f"{kills}/{total}", bar_x + bar_w + 8.0 * scale, bar_y - 3.0 * scale, rl.Color(220, 220, 220, 255), scale=0.8 * scale)
|
|
446
867
|
|
|
447
868
|
self._draw_quest_title()
|
|
448
869
|
|
|
@@ -455,15 +876,21 @@ class QuestMode(BaseGameplayMode):
|
|
|
455
876
|
warn = "Missing HUD assets: " + ", ".join(self._hud_missing)
|
|
456
877
|
self._draw_ui_text(warn, 24.0, warn_y, rl.Color(240, 80, 80, 255), scale=0.8)
|
|
457
878
|
|
|
458
|
-
self.
|
|
459
|
-
|
|
879
|
+
self._draw_perk_prompt()
|
|
880
|
+
self._draw_perk_menu()
|
|
881
|
+
|
|
882
|
+
if perk_menu_active:
|
|
883
|
+
self._draw_game_cursor()
|
|
884
|
+
elif self._paused:
|
|
460
885
|
self._draw_game_cursor()
|
|
461
886
|
x = 18.0
|
|
462
887
|
y = max(18.0, hud_bottom + 10.0)
|
|
463
|
-
self._draw_ui_text("paused (TAB)", x, y,
|
|
888
|
+
self._draw_ui_text("paused (TAB)", x, y, UI_HINT_COLOR)
|
|
889
|
+
else:
|
|
890
|
+
self._draw_aim_cursor()
|
|
464
891
|
|
|
465
892
|
def _draw_game_cursor(self) -> None:
|
|
466
|
-
assets = self.
|
|
893
|
+
assets = self._perk_menu_assets
|
|
467
894
|
cursor_tex = assets.cursor if assets is not None else None
|
|
468
895
|
draw_menu_cursor(
|
|
469
896
|
self._world.particles_texture,
|
|
@@ -474,7 +901,7 @@ class QuestMode(BaseGameplayMode):
|
|
|
474
901
|
)
|
|
475
902
|
|
|
476
903
|
def _draw_aim_cursor(self) -> None:
|
|
477
|
-
assets = self.
|
|
904
|
+
assets = self._perk_menu_assets
|
|
478
905
|
aim_tex = assets.aim if assets is not None else None
|
|
479
906
|
draw_aim_cursor(
|
|
480
907
|
self._world.particles_texture,
|
crimson/modes/rush_mode.py
CHANGED
|
@@ -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
|
|
|
@@ -16,7 +17,7 @@ from ..gameplay import PlayerInput, most_used_weapon_id_for_player, weapon_assig
|
|
|
16
17
|
from ..input_codes import config_keybinds, input_code_is_down, input_code_is_pressed, player_move_fire_binds
|
|
17
18
|
from ..persistence.highscores import HighScoreRecord
|
|
18
19
|
from ..ui.cursor import draw_aim_cursor, draw_menu_cursor
|
|
19
|
-
from ..ui.hud import draw_hud_overlay
|
|
20
|
+
from ..ui.hud import draw_hud_overlay, hud_flags_for_game_mode
|
|
20
21
|
from ..ui.perk_menu import load_perk_menu_assets
|
|
21
22
|
from .base_gameplay_mode import BaseGameplayMode
|
|
22
23
|
|
|
@@ -42,6 +43,7 @@ class RushMode(BaseGameplayMode):
|
|
|
42
43
|
*,
|
|
43
44
|
texture_cache: PaqTextureCache | None = None,
|
|
44
45
|
config: CrimsonConfig | None = None,
|
|
46
|
+
console: ConsoleState | None = None,
|
|
45
47
|
audio: AudioState | None = None,
|
|
46
48
|
audio_rng: random.Random | None = None,
|
|
47
49
|
) -> None:
|
|
@@ -54,6 +56,7 @@ class RushMode(BaseGameplayMode):
|
|
|
54
56
|
hardcore=False,
|
|
55
57
|
texture_cache=texture_cache,
|
|
56
58
|
config=config,
|
|
59
|
+
console=console,
|
|
57
60
|
audio=audio,
|
|
58
61
|
audio_rng=audio_rng,
|
|
59
62
|
)
|
|
@@ -255,6 +258,8 @@ class RushMode(BaseGameplayMode):
|
|
|
255
258
|
|
|
256
259
|
hud_bottom = 0.0
|
|
257
260
|
if (not self._game_over_active) and self._hud_assets is not None:
|
|
261
|
+
hud_flags = hud_flags_for_game_mode(self._config_game_mode_id())
|
|
262
|
+
self._draw_target_health_bar()
|
|
258
263
|
hud_bottom = draw_hud_overlay(
|
|
259
264
|
self._hud_assets,
|
|
260
265
|
player=self._player,
|
|
@@ -263,8 +268,12 @@ class RushMode(BaseGameplayMode):
|
|
|
263
268
|
elapsed_ms=self._rush.elapsed_ms,
|
|
264
269
|
font=self._small,
|
|
265
270
|
frame_dt_ms=self._last_dt_ms,
|
|
266
|
-
|
|
267
|
-
|
|
271
|
+
show_health=hud_flags.show_health,
|
|
272
|
+
show_weapon=hud_flags.show_weapon,
|
|
273
|
+
show_xp=hud_flags.show_xp,
|
|
274
|
+
show_time=hud_flags.show_time,
|
|
275
|
+
show_quest_hud=hud_flags.show_quest_hud,
|
|
276
|
+
small_indicators=self._hud_small_indicators(),
|
|
268
277
|
)
|
|
269
278
|
|
|
270
279
|
if not self._game_over_active:
|