crimsonland 0.1.0.dev14__py3-none-any.whl → 0.1.0.dev16__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/cli.py +63 -0
- crimson/creatures/damage.py +111 -36
- crimson/creatures/runtime.py +246 -156
- crimson/creatures/spawn.py +7 -3
- crimson/debug.py +9 -0
- crimson/demo.py +38 -45
- crimson/effects.py +7 -13
- crimson/frontend/high_scores_layout.py +81 -0
- crimson/frontend/panels/base.py +4 -1
- crimson/frontend/panels/controls.py +0 -15
- crimson/frontend/panels/databases.py +291 -3
- crimson/frontend/panels/mods.py +0 -15
- crimson/frontend/panels/play_game.py +0 -16
- crimson/game.py +689 -3
- crimson/gameplay.py +921 -569
- crimson/modes/base_gameplay_mode.py +33 -12
- crimson/modes/components/__init__.py +2 -0
- crimson/modes/components/highscore_record_builder.py +58 -0
- crimson/modes/components/perk_menu_controller.py +325 -0
- crimson/modes/quest_mode.py +94 -272
- crimson/modes/rush_mode.py +12 -43
- crimson/modes/survival_mode.py +109 -330
- crimson/modes/tutorial_mode.py +46 -247
- crimson/modes/typo_mode.py +11 -38
- crimson/oracle.py +396 -0
- crimson/perks.py +5 -2
- crimson/player_damage.py +95 -36
- crimson/projectiles.py +539 -320
- crimson/render/projectile_draw_registry.py +637 -0
- crimson/render/projectile_render_registry.py +110 -0
- crimson/render/secondary_projectile_draw_registry.py +206 -0
- crimson/render/world_renderer.py +58 -707
- crimson/sim/world_state.py +118 -61
- crimson/typo/spawns.py +5 -12
- crimson/ui/demo_trial_overlay.py +3 -11
- crimson/ui/formatting.py +24 -0
- crimson/ui/game_over.py +12 -58
- crimson/ui/hud.py +72 -39
- crimson/ui/layout.py +20 -0
- crimson/ui/perk_menu.py +9 -34
- crimson/ui/quest_results.py +28 -70
- crimson/ui/text_input.py +20 -0
- crimson/views/_ui_helpers.py +27 -0
- crimson/views/aim_debug.py +15 -32
- crimson/views/animations.py +18 -28
- crimson/views/arsenal_debug.py +22 -32
- crimson/views/bonuses.py +23 -36
- crimson/views/camera_debug.py +16 -29
- crimson/views/camera_shake.py +9 -33
- crimson/views/corpse_stamp_debug.py +13 -21
- crimson/views/decals_debug.py +36 -23
- crimson/views/fonts.py +8 -25
- crimson/views/ground.py +4 -21
- crimson/views/lighting_debug.py +42 -45
- crimson/views/particles.py +33 -42
- crimson/views/perk_menu_debug.py +3 -10
- crimson/views/player.py +50 -44
- crimson/views/player_sprite_debug.py +24 -31
- crimson/views/projectile_fx.py +57 -52
- crimson/views/projectile_render_debug.py +24 -33
- crimson/views/projectiles.py +24 -37
- crimson/views/spawn_plan.py +13 -29
- crimson/views/sprites.py +14 -29
- crimson/views/terrain.py +6 -23
- crimson/views/ui.py +7 -24
- crimson/views/wicons.py +28 -33
- {crimsonland-0.1.0.dev14.dist-info → crimsonland-0.1.0.dev16.dist-info}/METADATA +1 -1
- {crimsonland-0.1.0.dev14.dist-info → crimsonland-0.1.0.dev16.dist-info}/RECORD +73 -62
- {crimsonland-0.1.0.dev14.dist-info → crimsonland-0.1.0.dev16.dist-info}/WHEEL +1 -1
- grim/config.py +29 -1
- grim/console.py +7 -10
- grim/math.py +12 -0
- {crimsonland-0.1.0.dev14.dist-info → crimsonland-0.1.0.dev16.dist-info}/entry_points.txt +0 -0
crimson/modes/survival_mode.py
CHANGED
|
@@ -9,36 +9,26 @@ from grim.assets import PaqTextureCache
|
|
|
9
9
|
from grim.audio import AudioState
|
|
10
10
|
from grim.console import ConsoleState
|
|
11
11
|
from grim.config import CrimsonConfig
|
|
12
|
+
from grim.math import clamp
|
|
12
13
|
from grim.view import ViewContext
|
|
13
14
|
|
|
14
15
|
from ..creatures.spawn import advance_survival_spawn_stage, tick_survival_wave_spawns
|
|
15
16
|
from ..debug import debug_enabled
|
|
16
17
|
from ..game_modes import GameMode
|
|
17
|
-
from ..gameplay import
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
from ..gameplay import (
|
|
19
|
+
PlayerInput,
|
|
20
|
+
survival_check_level_up,
|
|
21
|
+
weapon_assign_player,
|
|
22
|
+
)
|
|
23
|
+
from ..perks import PerkId
|
|
20
24
|
from ..ui.cursor import draw_aim_cursor, draw_menu_cursor
|
|
21
25
|
from ..ui.hud import draw_hud_overlay, hud_flags_for_game_mode
|
|
22
|
-
from ..ui.menu_panel import draw_classic_menu_panel
|
|
23
26
|
from ..input_codes import config_keybinds, input_code_is_down, input_code_is_pressed, player_move_fire_binds
|
|
24
|
-
from ..ui.perk_menu import
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
button_update,
|
|
30
|
-
button_width,
|
|
31
|
-
draw_menu_item,
|
|
32
|
-
draw_ui_text,
|
|
33
|
-
load_perk_menu_assets,
|
|
34
|
-
menu_item_hit_rect,
|
|
35
|
-
perk_menu_panel_slide_x,
|
|
36
|
-
perk_menu_compute_layout,
|
|
37
|
-
ui_origin,
|
|
38
|
-
ui_scale,
|
|
39
|
-
wrap_ui_text,
|
|
40
|
-
)
|
|
41
|
-
from .base_gameplay_mode import BaseGameplayMode, _clamp
|
|
27
|
+
from ..ui.perk_menu import PERK_MENU_TRANSITION_MS, draw_ui_text, load_perk_menu_assets
|
|
28
|
+
from ..weapons import WEAPON_BY_ID
|
|
29
|
+
from .base_gameplay_mode import BaseGameplayMode
|
|
30
|
+
from .components.highscore_record_builder import build_highscore_record_for_game_over
|
|
31
|
+
from .components.perk_menu_controller import PerkMenuContext, PerkMenuController
|
|
42
32
|
|
|
43
33
|
WORLD_SIZE = 1024.0
|
|
44
34
|
|
|
@@ -71,6 +61,8 @@ PERK_PROMPT_LEVEL_UP_SHIFT_Y = -4.0
|
|
|
71
61
|
PERK_PROMPT_TEXT_MARGIN_X = 16.0
|
|
72
62
|
PERK_PROMPT_TEXT_OFFSET_Y = 8.0
|
|
73
63
|
|
|
64
|
+
_DEBUG_WEAPON_IDS = tuple(sorted(WEAPON_BY_ID))
|
|
65
|
+
|
|
74
66
|
|
|
75
67
|
@dataclass(slots=True)
|
|
76
68
|
class _SurvivalState:
|
|
@@ -108,15 +100,38 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
108
100
|
self._perk_prompt_timer_ms = 0.0
|
|
109
101
|
self._perk_prompt_hover = False
|
|
110
102
|
self._perk_prompt_pulse = 0.0
|
|
111
|
-
self.
|
|
112
|
-
self._perk_menu_selected = 0
|
|
113
|
-
self._perk_menu_timeline_ms = 0.0
|
|
103
|
+
self._perk_menu = PerkMenuController(on_close=self._reset_perk_prompt)
|
|
114
104
|
self._hud_fade_ms = PERK_MENU_TRANSITION_MS
|
|
115
105
|
self._perk_menu_assets = None
|
|
116
|
-
self._perk_ui_layout = PerkMenuLayout()
|
|
117
|
-
self._perk_cancel_button = UiButtonState("Cancel")
|
|
118
106
|
self._cursor_time = 0.0
|
|
119
107
|
|
|
108
|
+
def _reset_perk_prompt(self) -> None:
|
|
109
|
+
if int(self._state.perk_selection.pending_count) > 0:
|
|
110
|
+
# Reset the prompt swing so each pending perk replays the intro.
|
|
111
|
+
self._perk_prompt_timer_ms = 0.0
|
|
112
|
+
self._perk_prompt_hover = False
|
|
113
|
+
self._perk_prompt_pulse = 0.0
|
|
114
|
+
|
|
115
|
+
def _perk_menu_context(self) -> PerkMenuContext:
|
|
116
|
+
fx_toggle = int(self._config.data.get("fx_toggle", 0) or 0) if self._config is not None else 0
|
|
117
|
+
fx_detail = bool(int(self._config.data.get("fx_detail_0", 0) or 0)) if self._config is not None else False
|
|
118
|
+
players = self._world.players
|
|
119
|
+
return PerkMenuContext(
|
|
120
|
+
state=self._state,
|
|
121
|
+
perk_state=self._state.perk_selection,
|
|
122
|
+
players=players,
|
|
123
|
+
creatures=self._creatures.entries,
|
|
124
|
+
player=self._player,
|
|
125
|
+
game_mode=int(GameMode.SURVIVAL),
|
|
126
|
+
player_count=len(players),
|
|
127
|
+
fx_toggle=fx_toggle,
|
|
128
|
+
fx_detail=fx_detail,
|
|
129
|
+
font=self._small,
|
|
130
|
+
assets=self._perk_menu_assets,
|
|
131
|
+
mouse=self._ui_mouse_pos(),
|
|
132
|
+
play_sfx=self._world.audio_router.play_sfx,
|
|
133
|
+
)
|
|
134
|
+
|
|
120
135
|
def _wrap_ui_text(self, text: str, *, max_width: float, scale: float = UI_TEXT_SCALE) -> list[str]:
|
|
121
136
|
lines: list[str] = []
|
|
122
137
|
for raw in text.splitlines() or [""]:
|
|
@@ -148,8 +163,7 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
148
163
|
self._perk_menu_assets = load_perk_menu_assets(self._assets_root)
|
|
149
164
|
if self._perk_menu_assets.missing:
|
|
150
165
|
self._missing_assets.extend(self._perk_menu_assets.missing)
|
|
151
|
-
self.
|
|
152
|
-
self._perk_cancel_button = UiButtonState("Cancel")
|
|
166
|
+
self._perk_menu.reset()
|
|
153
167
|
self._cursor_time = 0.0
|
|
154
168
|
self._cursor_pulse_time = 0.0
|
|
155
169
|
self._survival = _SurvivalState()
|
|
@@ -157,9 +171,6 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
157
171
|
self._perk_prompt_timer_ms = 0.0
|
|
158
172
|
self._perk_prompt_hover = False
|
|
159
173
|
self._perk_prompt_pulse = 0.0
|
|
160
|
-
self._perk_menu_open = False
|
|
161
|
-
self._perk_menu_selected = 0
|
|
162
|
-
self._perk_menu_timeline_ms = 0.0
|
|
163
174
|
self._hud_fade_ms = PERK_MENU_TRANSITION_MS
|
|
164
175
|
|
|
165
176
|
def close(self) -> None:
|
|
@@ -173,22 +184,46 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
173
184
|
self._action = "back_to_menu"
|
|
174
185
|
self.close_requested = True
|
|
175
186
|
return
|
|
176
|
-
if self.
|
|
187
|
+
if self._perk_menu.open and rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
177
188
|
self._world.audio_router.play_sfx("sfx_ui_buttonclick")
|
|
178
|
-
self.
|
|
189
|
+
self._perk_menu.close()
|
|
179
190
|
return
|
|
180
191
|
|
|
181
192
|
if rl.is_key_pressed(rl.KeyboardKey.KEY_TAB):
|
|
182
193
|
self._paused = not self._paused
|
|
183
194
|
|
|
184
|
-
if debug_enabled() and
|
|
185
|
-
|
|
186
|
-
|
|
195
|
+
if debug_enabled() and (not self._perk_menu.open):
|
|
196
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_F2):
|
|
197
|
+
self._state.debug_god_mode = not bool(self._state.debug_god_mode)
|
|
198
|
+
self._world.audio_router.play_sfx("sfx_ui_buttonclick")
|
|
199
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_F3):
|
|
200
|
+
self._state.perk_selection.pending_count += 1
|
|
201
|
+
self._state.perk_selection.choices_dirty = True
|
|
202
|
+
self._world.audio_router.play_sfx("sfx_ui_levelup")
|
|
203
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_LEFT_BRACKET):
|
|
204
|
+
self._debug_cycle_weapon(-1)
|
|
205
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_RIGHT_BRACKET):
|
|
206
|
+
self._debug_cycle_weapon(1)
|
|
207
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_X):
|
|
208
|
+
self._player.experience += 5000
|
|
209
|
+
survival_check_level_up(self._player, self._state.perk_selection)
|
|
187
210
|
|
|
188
211
|
if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
189
212
|
self._action = "open_pause_menu"
|
|
190
213
|
return
|
|
191
214
|
|
|
215
|
+
def _debug_cycle_weapon(self, delta: int) -> None:
|
|
216
|
+
weapon_ids = _DEBUG_WEAPON_IDS
|
|
217
|
+
if not weapon_ids:
|
|
218
|
+
return
|
|
219
|
+
current = int(self._player.weapon_id)
|
|
220
|
+
try:
|
|
221
|
+
idx = weapon_ids.index(current)
|
|
222
|
+
except ValueError:
|
|
223
|
+
idx = 0
|
|
224
|
+
weapon_id = int(weapon_ids[(idx + int(delta)) % len(weapon_ids)])
|
|
225
|
+
weapon_assign_player(self._player, weapon_id, state=self._state)
|
|
226
|
+
|
|
192
227
|
def _build_input(self) -> PlayerInput:
|
|
193
228
|
keybinds = config_keybinds(self._config)
|
|
194
229
|
if not keybinds:
|
|
@@ -240,31 +275,18 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
240
275
|
def _enter_game_over(self) -> None:
|
|
241
276
|
if self._game_over_active:
|
|
242
277
|
return
|
|
243
|
-
|
|
244
|
-
record
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
hit = 0
|
|
252
|
-
try:
|
|
253
|
-
fired = int(self._state.shots_fired[int(self._player.index)])
|
|
254
|
-
hit = int(self._state.shots_hit[int(self._player.index)])
|
|
255
|
-
except Exception:
|
|
256
|
-
fired = 0
|
|
257
|
-
hit = 0
|
|
258
|
-
fired = max(0, int(fired))
|
|
259
|
-
hit = max(0, min(int(hit), fired))
|
|
260
|
-
record.shots_fired = fired
|
|
261
|
-
record.shots_hit = hit
|
|
262
|
-
|
|
263
|
-
record.game_mode_id = int(self._config.data.get("game_mode", 1)) if self._config is not None else 1
|
|
278
|
+
game_mode_id = int(self._config.data.get("game_mode", 1)) if self._config is not None else 1
|
|
279
|
+
record = build_highscore_record_for_game_over(
|
|
280
|
+
state=self._state,
|
|
281
|
+
player=self._player,
|
|
282
|
+
survival_elapsed_ms=int(self._survival.elapsed_ms),
|
|
283
|
+
creature_kill_count=int(self._creatures.kill_count),
|
|
284
|
+
game_mode_id=game_mode_id,
|
|
285
|
+
)
|
|
264
286
|
self._game_over_record = record
|
|
265
287
|
self._game_over_ui.open()
|
|
266
288
|
self._game_over_active = True
|
|
267
|
-
self.
|
|
289
|
+
self._perk_menu.close()
|
|
268
290
|
|
|
269
291
|
def _perk_prompt_label(self) -> str:
|
|
270
292
|
if self._config is not None and not bool(int(self._config.data.get("ui_info_texts", 1) or 0)):
|
|
@@ -304,138 +326,6 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
304
326
|
y = margin
|
|
305
327
|
return rl.Rectangle(x, y, text_w, text_h)
|
|
306
328
|
|
|
307
|
-
def _open_perk_menu(self) -> None:
|
|
308
|
-
if self._perk_menu_open:
|
|
309
|
-
return
|
|
310
|
-
players = self._world.players
|
|
311
|
-
choices = perk_selection_current_choices(
|
|
312
|
-
self._state,
|
|
313
|
-
players,
|
|
314
|
-
self._state.perk_selection,
|
|
315
|
-
game_mode=int(GameMode.SURVIVAL),
|
|
316
|
-
player_count=len(players),
|
|
317
|
-
)
|
|
318
|
-
if not choices:
|
|
319
|
-
self._perk_menu_open = False
|
|
320
|
-
return
|
|
321
|
-
self._world.audio_router.play_sfx("sfx_ui_panelclick")
|
|
322
|
-
self._perk_menu_open = True
|
|
323
|
-
self._perk_menu_selected = 0
|
|
324
|
-
|
|
325
|
-
def _close_perk_menu(self) -> None:
|
|
326
|
-
self._perk_menu_open = False
|
|
327
|
-
if int(self._state.perk_selection.pending_count) > 0:
|
|
328
|
-
# Reset the prompt swing so each pending perk replays the intro.
|
|
329
|
-
self._perk_prompt_timer_ms = 0.0
|
|
330
|
-
self._perk_prompt_hover = False
|
|
331
|
-
self._perk_prompt_pulse = 0.0
|
|
332
|
-
|
|
333
|
-
def _perk_menu_handle_input(self, dt_frame: float, dt_ms: float) -> None:
|
|
334
|
-
if self._perk_menu_assets is None:
|
|
335
|
-
self._close_perk_menu()
|
|
336
|
-
return
|
|
337
|
-
perk_state = self._state.perk_selection
|
|
338
|
-
players = self._world.players
|
|
339
|
-
choices = perk_selection_current_choices(
|
|
340
|
-
self._state,
|
|
341
|
-
players,
|
|
342
|
-
perk_state,
|
|
343
|
-
game_mode=int(GameMode.SURVIVAL),
|
|
344
|
-
player_count=len(players),
|
|
345
|
-
)
|
|
346
|
-
if not choices:
|
|
347
|
-
self._close_perk_menu()
|
|
348
|
-
return
|
|
349
|
-
if self._perk_menu_selected >= len(choices):
|
|
350
|
-
self._perk_menu_selected = 0
|
|
351
|
-
|
|
352
|
-
if rl.is_key_pressed(rl.KeyboardKey.KEY_DOWN):
|
|
353
|
-
self._perk_menu_selected = (self._perk_menu_selected + 1) % len(choices)
|
|
354
|
-
if rl.is_key_pressed(rl.KeyboardKey.KEY_UP):
|
|
355
|
-
self._perk_menu_selected = (self._perk_menu_selected - 1) % len(choices)
|
|
356
|
-
|
|
357
|
-
screen_w = float(rl.get_screen_width())
|
|
358
|
-
screen_h = float(rl.get_screen_height())
|
|
359
|
-
scale = ui_scale(screen_w, screen_h)
|
|
360
|
-
origin_x, origin_y = ui_origin(screen_w, screen_h, scale)
|
|
361
|
-
slide_x = perk_menu_panel_slide_x(self._perk_menu_timeline_ms, width=self._perk_ui_layout.panel_w)
|
|
362
|
-
|
|
363
|
-
mouse = self._ui_mouse_pos()
|
|
364
|
-
click = rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT)
|
|
365
|
-
|
|
366
|
-
master_owned = int(self._player.perk_counts[int(PerkId.PERK_MASTER)]) > 0
|
|
367
|
-
expert_owned = int(self._player.perk_counts[int(PerkId.PERK_EXPERT)]) > 0
|
|
368
|
-
computed = perk_menu_compute_layout(
|
|
369
|
-
self._perk_ui_layout,
|
|
370
|
-
screen_w=screen_w,
|
|
371
|
-
origin_x=origin_x,
|
|
372
|
-
origin_y=origin_y,
|
|
373
|
-
scale=scale,
|
|
374
|
-
choice_count=len(choices),
|
|
375
|
-
expert_owned=expert_owned,
|
|
376
|
-
master_owned=master_owned,
|
|
377
|
-
panel_slide_x=slide_x,
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
fx_toggle = int(self._config.data.get("fx_toggle", 0) or 0) if self._config is not None else 0
|
|
381
|
-
for idx, perk_id in enumerate(choices):
|
|
382
|
-
label = perk_display_name(int(perk_id), fx_toggle=fx_toggle)
|
|
383
|
-
item_x = computed.list_x
|
|
384
|
-
item_y = computed.list_y + float(idx) * computed.list_step_y
|
|
385
|
-
rect = menu_item_hit_rect(self._small, label, x=item_x, y=item_y, scale=scale)
|
|
386
|
-
if rl.check_collision_point_rec(mouse, rect):
|
|
387
|
-
self._perk_menu_selected = idx
|
|
388
|
-
if click:
|
|
389
|
-
self._world.audio_router.play_sfx("sfx_ui_buttonclick")
|
|
390
|
-
picked = perk_selection_pick(
|
|
391
|
-
self._state,
|
|
392
|
-
players,
|
|
393
|
-
perk_state,
|
|
394
|
-
idx,
|
|
395
|
-
game_mode=int(GameMode.SURVIVAL),
|
|
396
|
-
player_count=len(players),
|
|
397
|
-
dt=dt_frame,
|
|
398
|
-
creatures=self._creatures.entries,
|
|
399
|
-
)
|
|
400
|
-
if picked is not None:
|
|
401
|
-
self._world.audio_router.play_sfx("sfx_ui_bonus")
|
|
402
|
-
self._close_perk_menu()
|
|
403
|
-
return
|
|
404
|
-
break
|
|
405
|
-
|
|
406
|
-
cancel_w = button_width(self._small, self._perk_cancel_button.label, scale=scale, force_wide=self._perk_cancel_button.force_wide)
|
|
407
|
-
cancel_x = computed.cancel_x
|
|
408
|
-
button_y = computed.cancel_y
|
|
409
|
-
|
|
410
|
-
if button_update(
|
|
411
|
-
self._perk_cancel_button,
|
|
412
|
-
x=cancel_x,
|
|
413
|
-
y=button_y,
|
|
414
|
-
width=cancel_w,
|
|
415
|
-
dt_ms=dt_ms,
|
|
416
|
-
mouse=mouse,
|
|
417
|
-
click=click,
|
|
418
|
-
):
|
|
419
|
-
self._world.audio_router.play_sfx("sfx_ui_buttonclick")
|
|
420
|
-
self._close_perk_menu()
|
|
421
|
-
return
|
|
422
|
-
|
|
423
|
-
if rl.is_key_pressed(rl.KeyboardKey.KEY_ENTER) or rl.is_key_pressed(rl.KeyboardKey.KEY_SPACE):
|
|
424
|
-
self._world.audio_router.play_sfx("sfx_ui_buttonclick")
|
|
425
|
-
picked = perk_selection_pick(
|
|
426
|
-
self._state,
|
|
427
|
-
players,
|
|
428
|
-
perk_state,
|
|
429
|
-
self._perk_menu_selected,
|
|
430
|
-
game_mode=int(GameMode.SURVIVAL),
|
|
431
|
-
player_count=len(players),
|
|
432
|
-
dt=dt_frame,
|
|
433
|
-
creatures=self._creatures.entries,
|
|
434
|
-
)
|
|
435
|
-
if picked is not None:
|
|
436
|
-
self._world.audio_router.play_sfx("sfx_ui_bonus")
|
|
437
|
-
self._close_perk_menu()
|
|
438
|
-
|
|
439
329
|
def update(self, dt: float) -> None:
|
|
440
330
|
self._update_audio(dt)
|
|
441
331
|
|
|
@@ -446,40 +336,19 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
446
336
|
return
|
|
447
337
|
|
|
448
338
|
if self._game_over_active:
|
|
449
|
-
|
|
450
|
-
if record is None:
|
|
451
|
-
self._enter_game_over()
|
|
452
|
-
record = self._game_over_record
|
|
453
|
-
if record is not None:
|
|
454
|
-
action = self._game_over_ui.update(
|
|
455
|
-
dt,
|
|
456
|
-
record=record,
|
|
457
|
-
player_name_default=self._player_name_default(),
|
|
458
|
-
play_sfx=self._world.audio_router.play_sfx,
|
|
459
|
-
rand=self._state.rng.rand,
|
|
460
|
-
mouse=self._ui_mouse_pos(),
|
|
461
|
-
)
|
|
462
|
-
if action == "play_again":
|
|
463
|
-
self.open()
|
|
464
|
-
return
|
|
465
|
-
if action == "high_scores":
|
|
466
|
-
self._action = "open_high_scores"
|
|
467
|
-
return
|
|
468
|
-
if action == "main_menu":
|
|
469
|
-
self._action = "back_to_menu"
|
|
470
|
-
self.close_requested = True
|
|
471
|
-
return
|
|
339
|
+
self._update_game_over_ui(dt)
|
|
472
340
|
return
|
|
473
341
|
|
|
474
342
|
any_alive = any(player.health > 0.0 for player in self._world.players)
|
|
475
343
|
perk_pending = int(self._state.perk_selection.pending_count) > 0 and any_alive
|
|
476
344
|
|
|
477
345
|
self._perk_prompt_hover = False
|
|
478
|
-
|
|
479
|
-
|
|
346
|
+
perk_ctx = self._perk_menu_context()
|
|
347
|
+
if self._perk_menu.open:
|
|
348
|
+
self._perk_menu.handle_input(perk_ctx, dt_frame=dt_frame, dt_ui_ms=dt_ui_ms)
|
|
480
349
|
dt = 0.0
|
|
481
350
|
|
|
482
|
-
perk_menu_active = self.
|
|
351
|
+
perk_menu_active = self._perk_menu.active
|
|
483
352
|
|
|
484
353
|
if (not perk_menu_active) and perk_pending and (not self._paused):
|
|
485
354
|
label = self._perk_prompt_label()
|
|
@@ -499,32 +368,29 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
499
368
|
|
|
500
369
|
if input_code_is_pressed(pick_key) and (not input_code_is_down(fire_key)):
|
|
501
370
|
self._perk_prompt_pulse = 1000.0
|
|
502
|
-
self.
|
|
371
|
+
self._perk_menu.open_if_available(perk_ctx)
|
|
503
372
|
elif self._perk_prompt_hover and input_code_is_pressed(fire_key):
|
|
504
373
|
self._perk_prompt_pulse = 1000.0
|
|
505
|
-
self.
|
|
374
|
+
self._perk_menu.open_if_available(perk_ctx)
|
|
506
375
|
|
|
507
376
|
if not self._paused and not self._game_over_active:
|
|
508
377
|
pulse_delta = dt_ui_ms * (6.0 if self._perk_prompt_hover else -2.0)
|
|
509
|
-
self._perk_prompt_pulse =
|
|
378
|
+
self._perk_prompt_pulse = clamp(self._perk_prompt_pulse + pulse_delta, 0.0, 1000.0)
|
|
510
379
|
|
|
511
380
|
if self._paused or (not any_alive) or perk_menu_active:
|
|
512
381
|
dt = 0.0
|
|
513
382
|
|
|
514
383
|
prompt_active = perk_pending and (not perk_menu_active) and (not self._paused)
|
|
515
384
|
if prompt_active:
|
|
516
|
-
self._perk_prompt_timer_ms =
|
|
385
|
+
self._perk_prompt_timer_ms = clamp(self._perk_prompt_timer_ms + dt_ui_ms, 0.0, PERK_PROMPT_MAX_TIMER_MS)
|
|
517
386
|
else:
|
|
518
|
-
self._perk_prompt_timer_ms =
|
|
387
|
+
self._perk_prompt_timer_ms = clamp(self._perk_prompt_timer_ms - dt_ui_ms, 0.0, PERK_PROMPT_MAX_TIMER_MS)
|
|
519
388
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
else:
|
|
523
|
-
self._perk_menu_timeline_ms = _clamp(self._perk_menu_timeline_ms - dt_ui_ms, 0.0, PERK_MENU_TRANSITION_MS)
|
|
524
|
-
if self._perk_menu_timeline_ms > 1e-3 or self._perk_menu_open:
|
|
389
|
+
self._perk_menu.tick_timeline(dt_ui_ms)
|
|
390
|
+
if self._perk_menu.active:
|
|
525
391
|
self._hud_fade_ms = 0.0
|
|
526
392
|
else:
|
|
527
|
-
self._hud_fade_ms =
|
|
393
|
+
self._hud_fade_ms = clamp(self._hud_fade_ms + dt_ui_ms, 0.0, PERK_MENU_TRANSITION_MS)
|
|
528
394
|
|
|
529
395
|
self._survival.elapsed_ms += dt * 1000.0
|
|
530
396
|
|
|
@@ -545,6 +411,9 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
545
411
|
# Scripted milestone spawns based on level.
|
|
546
412
|
stage, milestone_calls = advance_survival_spawn_stage(self._survival.stage, player_level=self._player.level)
|
|
547
413
|
self._survival.stage = stage
|
|
414
|
+
detail_preset = 5
|
|
415
|
+
if self._world.config is not None:
|
|
416
|
+
detail_preset = int(self._world.config.data.get("detail_preset", 5) or 5)
|
|
548
417
|
for call in milestone_calls:
|
|
549
418
|
self._creatures.spawn_template(
|
|
550
419
|
int(call.template_id),
|
|
@@ -552,6 +421,8 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
552
421
|
float(call.heading),
|
|
553
422
|
self._state.rng,
|
|
554
423
|
rand=self._state.rng.rand,
|
|
424
|
+
state=self._state,
|
|
425
|
+
detail_preset=detail_preset,
|
|
555
426
|
)
|
|
556
427
|
|
|
557
428
|
# Regular wave spawns based on elapsed time.
|
|
@@ -574,7 +445,7 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
574
445
|
def _draw_perk_prompt(self) -> None:
|
|
575
446
|
if self._game_over_active:
|
|
576
447
|
return
|
|
577
|
-
if self.
|
|
448
|
+
if self._perk_menu.active:
|
|
578
449
|
return
|
|
579
450
|
if not any(player.health > 0.0 for player in self._world.players):
|
|
580
451
|
return
|
|
@@ -630,102 +501,6 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
630
501
|
rl.draw_texture_pro(tex, src, dst, origin, rot_deg, pulse_tint)
|
|
631
502
|
rl.end_blend_mode()
|
|
632
503
|
|
|
633
|
-
def _draw_perk_menu(self) -> None:
|
|
634
|
-
if self._game_over_active:
|
|
635
|
-
return
|
|
636
|
-
menu_t = _clamp(self._perk_menu_timeline_ms / PERK_MENU_TRANSITION_MS, 0.0, 1.0)
|
|
637
|
-
if menu_t <= 1e-3:
|
|
638
|
-
return
|
|
639
|
-
if self._perk_menu_assets is None:
|
|
640
|
-
return
|
|
641
|
-
|
|
642
|
-
perk_state = self._state.perk_selection
|
|
643
|
-
players = self._world.players
|
|
644
|
-
choices = perk_selection_current_choices(
|
|
645
|
-
self._state,
|
|
646
|
-
players,
|
|
647
|
-
perk_state,
|
|
648
|
-
game_mode=int(GameMode.SURVIVAL),
|
|
649
|
-
player_count=len(players),
|
|
650
|
-
)
|
|
651
|
-
if not choices:
|
|
652
|
-
return
|
|
653
|
-
screen_w = float(rl.get_screen_width())
|
|
654
|
-
screen_h = float(rl.get_screen_height())
|
|
655
|
-
scale = ui_scale(screen_w, screen_h)
|
|
656
|
-
origin_x, origin_y = ui_origin(screen_w, screen_h, scale)
|
|
657
|
-
slide_x = perk_menu_panel_slide_x(self._perk_menu_timeline_ms, width=self._perk_ui_layout.panel_w)
|
|
658
|
-
|
|
659
|
-
master_owned = int(self._player.perk_counts[int(PerkId.PERK_MASTER)]) > 0
|
|
660
|
-
expert_owned = int(self._player.perk_counts[int(PerkId.PERK_EXPERT)]) > 0
|
|
661
|
-
computed = perk_menu_compute_layout(
|
|
662
|
-
self._perk_ui_layout,
|
|
663
|
-
screen_w=screen_w,
|
|
664
|
-
origin_x=origin_x,
|
|
665
|
-
origin_y=origin_y,
|
|
666
|
-
scale=scale,
|
|
667
|
-
choice_count=len(choices),
|
|
668
|
-
expert_owned=expert_owned,
|
|
669
|
-
master_owned=master_owned,
|
|
670
|
-
panel_slide_x=slide_x,
|
|
671
|
-
)
|
|
672
|
-
|
|
673
|
-
panel_tex = self._perk_menu_assets.menu_panel
|
|
674
|
-
if panel_tex is not None:
|
|
675
|
-
fx_detail = bool(int(self._config.data.get("fx_detail_0", 0) or 0)) if self._config is not None else False
|
|
676
|
-
draw_classic_menu_panel(panel_tex, dst=computed.panel, shadow=fx_detail)
|
|
677
|
-
|
|
678
|
-
title_tex = self._perk_menu_assets.title_pick_perk
|
|
679
|
-
if title_tex is not None:
|
|
680
|
-
src = rl.Rectangle(0.0, 0.0, float(title_tex.width), float(title_tex.height))
|
|
681
|
-
rl.draw_texture_pro(title_tex, src, computed.title, rl.Vector2(0.0, 0.0), 0.0, rl.WHITE)
|
|
682
|
-
|
|
683
|
-
sponsor = None
|
|
684
|
-
if master_owned:
|
|
685
|
-
sponsor = "extra perks sponsored by the Perk Master"
|
|
686
|
-
elif expert_owned:
|
|
687
|
-
sponsor = "extra perk sponsored by the Perk Expert"
|
|
688
|
-
if sponsor:
|
|
689
|
-
draw_ui_text(
|
|
690
|
-
self._small,
|
|
691
|
-
sponsor,
|
|
692
|
-
computed.sponsor_x,
|
|
693
|
-
computed.sponsor_y,
|
|
694
|
-
scale=scale,
|
|
695
|
-
color=UI_SPONSOR_COLOR,
|
|
696
|
-
)
|
|
697
|
-
|
|
698
|
-
mouse = self._ui_mouse_pos()
|
|
699
|
-
fx_toggle = int(self._config.data.get("fx_toggle", 0) or 0) if self._config is not None else 0
|
|
700
|
-
for idx, perk_id in enumerate(choices):
|
|
701
|
-
label = perk_display_name(int(perk_id), fx_toggle=fx_toggle)
|
|
702
|
-
item_x = computed.list_x
|
|
703
|
-
item_y = computed.list_y + float(idx) * computed.list_step_y
|
|
704
|
-
rect = menu_item_hit_rect(self._small, label, x=item_x, y=item_y, scale=scale)
|
|
705
|
-
hovered = rl.check_collision_point_rec(mouse, rect) or (idx == self._perk_menu_selected)
|
|
706
|
-
draw_menu_item(self._small, label, x=item_x, y=item_y, scale=scale, hovered=hovered)
|
|
707
|
-
|
|
708
|
-
selected = choices[self._perk_menu_selected]
|
|
709
|
-
desc = perk_display_description(int(selected), fx_toggle=fx_toggle)
|
|
710
|
-
desc_x = float(computed.desc.x)
|
|
711
|
-
desc_y = float(computed.desc.y)
|
|
712
|
-
desc_w = float(computed.desc.width)
|
|
713
|
-
desc_h = float(computed.desc.height)
|
|
714
|
-
desc_scale = scale * 0.85
|
|
715
|
-
desc_lines = wrap_ui_text(self._small, desc, max_width=desc_w, scale=desc_scale)
|
|
716
|
-
line_h = float(self._small.cell_size * desc_scale) if self._small is not None else float(20 * desc_scale)
|
|
717
|
-
y = desc_y
|
|
718
|
-
for line in desc_lines:
|
|
719
|
-
if y + line_h > desc_y + desc_h:
|
|
720
|
-
break
|
|
721
|
-
draw_ui_text(self._small, line, desc_x, y, scale=desc_scale, color=UI_TEXT_COLOR)
|
|
722
|
-
y += line_h
|
|
723
|
-
|
|
724
|
-
cancel_w = button_width(self._small, self._perk_cancel_button.label, scale=scale, force_wide=self._perk_cancel_button.force_wide)
|
|
725
|
-
cancel_x = computed.cancel_x
|
|
726
|
-
button_y = computed.cancel_y
|
|
727
|
-
button_draw(self._perk_menu_assets, self._small, self._perk_cancel_button, x=cancel_x, y=button_y, width=cancel_w, scale=scale)
|
|
728
|
-
|
|
729
504
|
def _draw_game_cursor(self) -> None:
|
|
730
505
|
mouse_x = float(self._ui_mouse_x)
|
|
731
506
|
mouse_y = float(self._ui_mouse_y)
|
|
@@ -745,17 +520,18 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
745
520
|
draw_aim_cursor(self._world.particles_texture, aim_tex, x=mouse_x, y=mouse_y)
|
|
746
521
|
|
|
747
522
|
def draw(self) -> None:
|
|
748
|
-
perk_menu_active = self.
|
|
523
|
+
perk_menu_active = self._perk_menu.active
|
|
749
524
|
self._world.draw(draw_aim_indicators=(not self._game_over_active) and (not perk_menu_active))
|
|
750
525
|
self._draw_screen_fade()
|
|
751
526
|
|
|
752
527
|
hud_bottom = 0.0
|
|
753
528
|
if (not self._game_over_active) and (not perk_menu_active) and self._hud_assets is not None:
|
|
754
|
-
hud_alpha =
|
|
529
|
+
hud_alpha = clamp(self._hud_fade_ms / PERK_MENU_TRANSITION_MS, 0.0, 1.0)
|
|
755
530
|
hud_flags = hud_flags_for_game_mode(self._config_game_mode_id())
|
|
756
531
|
self._draw_target_health_bar(alpha=hud_alpha)
|
|
757
532
|
hud_bottom = draw_hud_overlay(
|
|
758
533
|
self._hud_assets,
|
|
534
|
+
state=self._hud_state,
|
|
759
535
|
player=self._player,
|
|
760
536
|
players=self._world.players,
|
|
761
537
|
bonus_hud=self._state.bonus_hud,
|
|
@@ -779,10 +555,12 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
779
555
|
line = float(self._ui_line_height())
|
|
780
556
|
self._draw_ui_text(f"survival: t={self._survival.elapsed_ms/1000.0:6.1f}s stage={self._survival.stage}", x, y, UI_TEXT_COLOR)
|
|
781
557
|
self._draw_ui_text(f"xp={self._player.experience} level={self._player.level} kills={self._creatures.kill_count}", x, y + line, UI_HINT_COLOR)
|
|
558
|
+
god = "on" if self._state.debug_god_mode else "off"
|
|
559
|
+
self._draw_ui_text(f"debug: [/] weapon F3 perk+1 F2 god={god} X xp+5000", x, y + line * 2.0, UI_HINT_COLOR, scale=0.9)
|
|
782
560
|
if self._paused:
|
|
783
|
-
self._draw_ui_text("paused (TAB)", x, y + line *
|
|
561
|
+
self._draw_ui_text("paused (TAB)", x, y + line * 3.0, UI_HINT_COLOR)
|
|
784
562
|
if self._player.health <= 0.0:
|
|
785
|
-
self._draw_ui_text("game over", x, y + line *
|
|
563
|
+
self._draw_ui_text("game over", x, y + line * 3.0, UI_ERROR_COLOR)
|
|
786
564
|
warn_y = float(rl.get_screen_height()) - 28.0
|
|
787
565
|
if self._world.missing_assets:
|
|
788
566
|
warn = "Missing world assets: " + ", ".join(self._world.missing_assets)
|
|
@@ -793,7 +571,8 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
793
571
|
self._draw_ui_text(warn, 24.0, warn_y, UI_ERROR_COLOR, scale=0.8)
|
|
794
572
|
|
|
795
573
|
self._draw_perk_prompt()
|
|
796
|
-
self.
|
|
574
|
+
if not self._game_over_active:
|
|
575
|
+
self._perk_menu.draw(self._perk_menu_context())
|
|
797
576
|
if (not self._game_over_active) and perk_menu_active:
|
|
798
577
|
self._draw_game_cursor()
|
|
799
578
|
if (not self._game_over_active) and (not perk_menu_active):
|