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/game.py
CHANGED
|
@@ -38,7 +38,7 @@ from grim.terrain_render import GroundRenderer
|
|
|
38
38
|
from grim.view import View, ViewContext
|
|
39
39
|
from grim.fonts.small import SmallFontData, draw_small_text, load_small_font, measure_small_text_width
|
|
40
40
|
|
|
41
|
-
from .debug import debug_enabled
|
|
41
|
+
from .debug import debug_enabled, set_debug_enabled
|
|
42
42
|
from grim import music
|
|
43
43
|
|
|
44
44
|
from .demo import DemoView
|
|
@@ -57,12 +57,78 @@ from .frontend.high_scores_layout import (
|
|
|
57
57
|
HS_BUTTON_STEP_Y,
|
|
58
58
|
HS_BUTTON_X,
|
|
59
59
|
HS_BUTTON_Y0,
|
|
60
|
+
HS_LOCAL_DATE_X,
|
|
61
|
+
HS_LOCAL_DATE_Y,
|
|
62
|
+
HS_LOCAL_FRAGS_X,
|
|
63
|
+
HS_LOCAL_FRAGS_Y,
|
|
64
|
+
HS_LOCAL_HIT_X,
|
|
65
|
+
HS_LOCAL_HIT_Y,
|
|
66
|
+
HS_LOCAL_LABEL_X,
|
|
67
|
+
HS_LOCAL_LABEL_Y,
|
|
68
|
+
HS_LOCAL_NAME_X,
|
|
69
|
+
HS_LOCAL_NAME_Y,
|
|
70
|
+
HS_LOCAL_RANK_X,
|
|
71
|
+
HS_LOCAL_RANK_Y,
|
|
72
|
+
HS_LOCAL_SCORE_LABEL_X,
|
|
73
|
+
HS_LOCAL_SCORE_LABEL_Y,
|
|
74
|
+
HS_LOCAL_SCORE_VALUE_X,
|
|
75
|
+
HS_LOCAL_SCORE_VALUE_Y,
|
|
76
|
+
HS_LOCAL_TIME_LABEL_X,
|
|
77
|
+
HS_LOCAL_TIME_LABEL_Y,
|
|
78
|
+
HS_LOCAL_TIME_VALUE_X,
|
|
79
|
+
HS_LOCAL_TIME_VALUE_Y,
|
|
80
|
+
HS_LOCAL_WEAPON_X,
|
|
81
|
+
HS_LOCAL_WEAPON_Y,
|
|
82
|
+
HS_LOCAL_WICON_X,
|
|
83
|
+
HS_LOCAL_WICON_Y,
|
|
60
84
|
HS_LEFT_PANEL_HEIGHT,
|
|
61
85
|
HS_LEFT_PANEL_POS_X,
|
|
62
86
|
HS_LEFT_PANEL_POS_Y,
|
|
87
|
+
HS_QUEST_ARROW_X,
|
|
88
|
+
HS_QUEST_ARROW_Y,
|
|
89
|
+
HS_RIGHT_CHECK_X,
|
|
90
|
+
HS_RIGHT_CHECK_Y,
|
|
63
91
|
HS_RIGHT_PANEL_HEIGHT,
|
|
64
92
|
HS_RIGHT_PANEL_POS_X,
|
|
65
93
|
HS_RIGHT_PANEL_POS_Y,
|
|
94
|
+
HS_RIGHT_GAME_MODE_DROP_X,
|
|
95
|
+
HS_RIGHT_GAME_MODE_DROP_Y,
|
|
96
|
+
HS_RIGHT_GAME_MODE_VALUE_X,
|
|
97
|
+
HS_RIGHT_GAME_MODE_VALUE_Y,
|
|
98
|
+
HS_RIGHT_GAME_MODE_WIDGET_W,
|
|
99
|
+
HS_RIGHT_GAME_MODE_WIDGET_X,
|
|
100
|
+
HS_RIGHT_GAME_MODE_WIDGET_Y,
|
|
101
|
+
HS_RIGHT_GAME_MODE_X,
|
|
102
|
+
HS_RIGHT_GAME_MODE_Y,
|
|
103
|
+
HS_RIGHT_NUMBER_PLAYERS_X,
|
|
104
|
+
HS_RIGHT_NUMBER_PLAYERS_Y,
|
|
105
|
+
HS_RIGHT_PLAYER_COUNT_DROP_X,
|
|
106
|
+
HS_RIGHT_PLAYER_COUNT_DROP_Y,
|
|
107
|
+
HS_RIGHT_PLAYER_COUNT_VALUE_X,
|
|
108
|
+
HS_RIGHT_PLAYER_COUNT_VALUE_Y,
|
|
109
|
+
HS_RIGHT_PLAYER_COUNT_WIDGET_W,
|
|
110
|
+
HS_RIGHT_PLAYER_COUNT_WIDGET_X,
|
|
111
|
+
HS_RIGHT_PLAYER_COUNT_WIDGET_Y,
|
|
112
|
+
HS_RIGHT_SCORE_LIST_DROP_X,
|
|
113
|
+
HS_RIGHT_SCORE_LIST_DROP_Y,
|
|
114
|
+
HS_RIGHT_SCORE_LIST_VALUE_X,
|
|
115
|
+
HS_RIGHT_SCORE_LIST_VALUE_Y,
|
|
116
|
+
HS_RIGHT_SCORE_LIST_WIDGET_W,
|
|
117
|
+
HS_RIGHT_SCORE_LIST_WIDGET_X,
|
|
118
|
+
HS_RIGHT_SCORE_LIST_WIDGET_Y,
|
|
119
|
+
HS_RIGHT_SCORE_LIST_X,
|
|
120
|
+
HS_RIGHT_SCORE_LIST_Y,
|
|
121
|
+
HS_RIGHT_SHOW_INTERNET_X,
|
|
122
|
+
HS_RIGHT_SHOW_INTERNET_Y,
|
|
123
|
+
HS_RIGHT_SHOW_SCORES_DROP_X,
|
|
124
|
+
HS_RIGHT_SHOW_SCORES_DROP_Y,
|
|
125
|
+
HS_RIGHT_SHOW_SCORES_VALUE_X,
|
|
126
|
+
HS_RIGHT_SHOW_SCORES_VALUE_Y,
|
|
127
|
+
HS_RIGHT_SHOW_SCORES_WIDGET_W,
|
|
128
|
+
HS_RIGHT_SHOW_SCORES_WIDGET_X,
|
|
129
|
+
HS_RIGHT_SHOW_SCORES_WIDGET_Y,
|
|
130
|
+
HS_RIGHT_SHOW_SCORES_X,
|
|
131
|
+
HS_RIGHT_SHOW_SCORES_Y,
|
|
66
132
|
)
|
|
67
133
|
from .frontend.menu import (
|
|
68
134
|
MENU_PANEL_HEIGHT,
|
|
@@ -113,6 +179,7 @@ class GameConfig:
|
|
|
113
179
|
seed: int | None = None
|
|
114
180
|
demo_enabled: bool = False
|
|
115
181
|
no_intro: bool = False
|
|
182
|
+
debug: bool = False
|
|
116
183
|
|
|
117
184
|
|
|
118
185
|
@dataclass(slots=True)
|
|
@@ -190,6 +257,25 @@ QUEST_BACK_BUTTON_X_OFFSET = 138.0
|
|
|
190
257
|
QUEST_BACK_BUTTON_Y_OFFSET = 212.0
|
|
191
258
|
QUEST_PANEL_HEIGHT = 378.0
|
|
192
259
|
|
|
260
|
+
# game_update_victory_screen (0x00406350): used as the "end note" screen after the final quest.
|
|
261
|
+
END_NOTE_PANEL_POS_X = -45.0
|
|
262
|
+
END_NOTE_PANEL_POS_Y = 110.0
|
|
263
|
+
END_NOTE_PANEL_GEOM_X0 = -63.0
|
|
264
|
+
END_NOTE_PANEL_GEOM_Y0 = -81.0
|
|
265
|
+
END_NOTE_PANEL_W = 510.0
|
|
266
|
+
END_NOTE_PANEL_H = 378.0
|
|
267
|
+
|
|
268
|
+
END_NOTE_HEADER_X_OFFSET = 214.0 # v11 + 44 - 10 in the decompile, relative to panel-left
|
|
269
|
+
END_NOTE_HEADER_Y_OFFSET = 46.0 # (base_y + 40) + 6 in the decompile, relative to panel-top
|
|
270
|
+
END_NOTE_BODY_X_OFFSET = END_NOTE_HEADER_X_OFFSET - 8.0
|
|
271
|
+
END_NOTE_BODY_Y_GAP = 32.0
|
|
272
|
+
END_NOTE_LINE_STEP_Y = 14.0
|
|
273
|
+
END_NOTE_AFTER_BODY_Y_GAP = 22.0 # 14 + 8 in the decompile
|
|
274
|
+
|
|
275
|
+
END_NOTE_BUTTON_X_OFFSET = 266.0 # (v11 + 44 + 20) - 4 + 26, relative to panel-left
|
|
276
|
+
END_NOTE_BUTTON_Y_OFFSET = 210.0 # (base_y + 40) + 170 in the decompile, relative to panel-top
|
|
277
|
+
END_NOTE_BUTTON_STEP_Y = 32.0
|
|
278
|
+
|
|
193
279
|
|
|
194
280
|
class QuestsMenuView:
|
|
195
281
|
"""Quest selection menu.
|
|
@@ -282,6 +368,7 @@ class QuestsMenuView:
|
|
|
282
368
|
self._cursor_pulse_time += min(dt, 0.1) * 1.1
|
|
283
369
|
|
|
284
370
|
config = self._state.config
|
|
371
|
+
status = self._state.status
|
|
285
372
|
|
|
286
373
|
# The original forcibly clears hardcore in the demo build.
|
|
287
374
|
if self._state.demo_enabled:
|
|
@@ -289,6 +376,14 @@ class QuestsMenuView:
|
|
|
289
376
|
config.data["hardcore_flag"] = 0
|
|
290
377
|
self._dirty = True
|
|
291
378
|
|
|
379
|
+
if debug_enabled() and rl.is_key_pressed(rl.KeyboardKey.KEY_F5):
|
|
380
|
+
unlock = 49
|
|
381
|
+
if int(status.quest_unlock_index) < unlock:
|
|
382
|
+
status.quest_unlock_index = unlock
|
|
383
|
+
if int(status.quest_unlock_index_full) < unlock:
|
|
384
|
+
status.quest_unlock_index_full = unlock
|
|
385
|
+
self._state.console.log.log("debug: unlocked all quests")
|
|
386
|
+
|
|
292
387
|
if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
293
388
|
self._action = "open_play_game"
|
|
294
389
|
return
|
|
@@ -374,7 +469,7 @@ class QuestsMenuView:
|
|
|
374
469
|
# `sub_447d40` base sums:
|
|
375
470
|
# x_sum = <ui_element_x> + <ui_element_offset_x> (x=-5)
|
|
376
471
|
# y_sum = <ui_element_y> + <ui_element_offset_y> (y=185 + widescreen shift via ui_menu_layout_init)
|
|
377
|
-
x_sum = QUEST_MENU_BASE_X +
|
|
472
|
+
x_sum = QUEST_MENU_BASE_X + QUEST_MENU_PANEL_OFFSET_X
|
|
378
473
|
y_sum = QUEST_MENU_BASE_Y + MENU_PANEL_OFFSET_Y + self._widescreen_y_shift
|
|
379
474
|
|
|
380
475
|
title_x = x_sum + QUEST_TITLE_X_OFFSET
|
|
@@ -1288,6 +1383,9 @@ class QuestResultsView:
|
|
|
1288
1383
|
self._action = "start_quest"
|
|
1289
1384
|
return
|
|
1290
1385
|
if action == "play_next":
|
|
1386
|
+
if int(self._quest_stage_major) == 5 and int(self._quest_stage_minor) == 10:
|
|
1387
|
+
self._action = "end_note"
|
|
1388
|
+
return
|
|
1291
1389
|
next_level = _next_quest_level(self._quest_level)
|
|
1292
1390
|
if next_level is not None:
|
|
1293
1391
|
self._state.pending_quest_level = next_level
|
|
@@ -1336,6 +1434,217 @@ class QuestResultsView:
|
|
|
1336
1434
|
self._action = "open_high_scores"
|
|
1337
1435
|
|
|
1338
1436
|
|
|
1437
|
+
class EndNoteView:
|
|
1438
|
+
"""Final quest "Show End Note" flow.
|
|
1439
|
+
|
|
1440
|
+
Classic:
|
|
1441
|
+
- quest_results_screen_update uses "Show End Note" instead of "Play Next" for quest 5.10
|
|
1442
|
+
- clicking it transitions to state 0x15 (game_update_victory_screen @ 0x00406350)
|
|
1443
|
+
"""
|
|
1444
|
+
|
|
1445
|
+
def __init__(self, state: GameState) -> None:
|
|
1446
|
+
self._state = state
|
|
1447
|
+
self._ground: GroundRenderer | None = None
|
|
1448
|
+
self._small_font: SmallFontData | None = None
|
|
1449
|
+
self._panel_tex: rl.Texture2D | None = None
|
|
1450
|
+
self._button_textures: UiButtonTextureSet | None = None
|
|
1451
|
+
self._action: str | None = None
|
|
1452
|
+
self._cursor_pulse_time = 0.0
|
|
1453
|
+
|
|
1454
|
+
self._survival_button = UiButtonState("Survival", force_wide=True)
|
|
1455
|
+
self._rush_button = UiButtonState(" Rush ", force_wide=True)
|
|
1456
|
+
self._typo_button = UiButtonState("Typ'o'Shooter", force_wide=True)
|
|
1457
|
+
self._main_menu_button = UiButtonState("Main Menu", force_wide=True)
|
|
1458
|
+
|
|
1459
|
+
def open(self) -> None:
|
|
1460
|
+
self._action = None
|
|
1461
|
+
self._cursor_pulse_time = 0.0
|
|
1462
|
+
self._ground = None if self._state.pause_background is not None else ensure_menu_ground(self._state)
|
|
1463
|
+
|
|
1464
|
+
cache = _ensure_texture_cache(self._state)
|
|
1465
|
+
self._panel_tex = cache.get_or_load("ui_menuPanel", "ui/ui_menuPanel.jaz").texture
|
|
1466
|
+
button_md = cache.get_or_load("ui_buttonMd", "ui/ui_button_128x32.jaz").texture
|
|
1467
|
+
button_sm = cache.get_or_load("ui_buttonSm", "ui/ui_button_64x32.jaz").texture
|
|
1468
|
+
self._button_textures = UiButtonTextureSet(button_sm=button_sm, button_md=button_md)
|
|
1469
|
+
self._small_font = None
|
|
1470
|
+
|
|
1471
|
+
def close(self) -> None:
|
|
1472
|
+
self._ground = None
|
|
1473
|
+
self._small_font = None
|
|
1474
|
+
self._panel_tex = None
|
|
1475
|
+
self._button_textures = None
|
|
1476
|
+
|
|
1477
|
+
def update(self, dt: float) -> None:
|
|
1478
|
+
if self._state.audio is not None:
|
|
1479
|
+
update_audio(self._state.audio, dt)
|
|
1480
|
+
if self._ground is not None:
|
|
1481
|
+
self._ground.process_pending()
|
|
1482
|
+
self._cursor_pulse_time += min(float(dt), 0.1) * 1.1
|
|
1483
|
+
|
|
1484
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
1485
|
+
if self._state.audio is not None:
|
|
1486
|
+
play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
|
|
1487
|
+
self._action = "back_to_menu"
|
|
1488
|
+
return
|
|
1489
|
+
|
|
1490
|
+
textures = self._button_textures
|
|
1491
|
+
if textures is None or (textures.button_sm is None and textures.button_md is None):
|
|
1492
|
+
return
|
|
1493
|
+
|
|
1494
|
+
screen_w = float(rl.get_screen_width())
|
|
1495
|
+
scale = 1.0
|
|
1496
|
+
|
|
1497
|
+
layout_w = screen_w / scale if scale else screen_w
|
|
1498
|
+
widescreen_shift_y = MenuView._menu_widescreen_y_shift(layout_w)
|
|
1499
|
+
|
|
1500
|
+
panel_left = (END_NOTE_PANEL_GEOM_X0 + END_NOTE_PANEL_POS_X) * scale
|
|
1501
|
+
panel_top = (END_NOTE_PANEL_GEOM_Y0 + END_NOTE_PANEL_POS_Y + widescreen_shift_y) * scale
|
|
1502
|
+
|
|
1503
|
+
button_x = panel_left + END_NOTE_BUTTON_X_OFFSET * scale
|
|
1504
|
+
button_y = panel_top + END_NOTE_BUTTON_Y_OFFSET * scale
|
|
1505
|
+
|
|
1506
|
+
font = self._ensure_small_font()
|
|
1507
|
+
mouse = rl.get_mouse_position()
|
|
1508
|
+
click = rl.is_mouse_button_pressed(rl.MOUSE_BUTTON_LEFT)
|
|
1509
|
+
dt_ms = min(float(dt), 0.1) * 1000.0
|
|
1510
|
+
|
|
1511
|
+
survival_w = button_width(font, self._survival_button.label, scale=scale, force_wide=self._survival_button.force_wide)
|
|
1512
|
+
if button_update(self._survival_button, x=button_x, y=button_y, width=survival_w, dt_ms=dt_ms, mouse=mouse, click=click):
|
|
1513
|
+
self._state.config.data["game_mode"] = 1
|
|
1514
|
+
if self._state.audio is not None:
|
|
1515
|
+
play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
|
|
1516
|
+
self._action = "start_survival"
|
|
1517
|
+
return
|
|
1518
|
+
|
|
1519
|
+
button_y += END_NOTE_BUTTON_STEP_Y * scale
|
|
1520
|
+
rush_w = button_width(font, self._rush_button.label, scale=scale, force_wide=self._rush_button.force_wide)
|
|
1521
|
+
if button_update(self._rush_button, x=button_x, y=button_y, width=rush_w, dt_ms=dt_ms, mouse=mouse, click=click):
|
|
1522
|
+
self._state.config.data["game_mode"] = 2
|
|
1523
|
+
if self._state.audio is not None:
|
|
1524
|
+
play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
|
|
1525
|
+
self._action = "start_rush"
|
|
1526
|
+
return
|
|
1527
|
+
|
|
1528
|
+
button_y += END_NOTE_BUTTON_STEP_Y * scale
|
|
1529
|
+
typo_w = button_width(font, self._typo_button.label, scale=scale, force_wide=self._typo_button.force_wide)
|
|
1530
|
+
if button_update(self._typo_button, x=button_x, y=button_y, width=typo_w, dt_ms=dt_ms, mouse=mouse, click=click):
|
|
1531
|
+
self._state.config.data["game_mode"] = 4
|
|
1532
|
+
self._state.screen_fade_alpha = 0.0
|
|
1533
|
+
self._state.screen_fade_ramp = True
|
|
1534
|
+
if self._state.audio is not None:
|
|
1535
|
+
play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
|
|
1536
|
+
self._action = "start_typo"
|
|
1537
|
+
return
|
|
1538
|
+
|
|
1539
|
+
button_y += END_NOTE_BUTTON_STEP_Y * scale
|
|
1540
|
+
main_w = button_width(font, self._main_menu_button.label, scale=scale, force_wide=self._main_menu_button.force_wide)
|
|
1541
|
+
if button_update(self._main_menu_button, x=button_x, y=button_y, width=main_w, dt_ms=dt_ms, mouse=mouse, click=click):
|
|
1542
|
+
if self._state.audio is not None:
|
|
1543
|
+
play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
|
|
1544
|
+
self._action = "back_to_menu"
|
|
1545
|
+
return
|
|
1546
|
+
|
|
1547
|
+
def draw(self) -> None:
|
|
1548
|
+
rl.clear_background(rl.BLACK)
|
|
1549
|
+
pause_background = self._state.pause_background
|
|
1550
|
+
if pause_background is not None:
|
|
1551
|
+
pause_background.draw_pause_background()
|
|
1552
|
+
elif self._ground is not None:
|
|
1553
|
+
self._ground.draw(0.0, 0.0)
|
|
1554
|
+
_draw_screen_fade(self._state)
|
|
1555
|
+
|
|
1556
|
+
panel_tex = self._panel_tex
|
|
1557
|
+
if panel_tex is None:
|
|
1558
|
+
return
|
|
1559
|
+
|
|
1560
|
+
screen_w = float(rl.get_screen_width())
|
|
1561
|
+
scale = 1.0
|
|
1562
|
+
layout_w = screen_w / scale if scale else screen_w
|
|
1563
|
+
widescreen_shift_y = MenuView._menu_widescreen_y_shift(layout_w)
|
|
1564
|
+
|
|
1565
|
+
panel_left = (END_NOTE_PANEL_GEOM_X0 + END_NOTE_PANEL_POS_X) * scale
|
|
1566
|
+
panel_top = (END_NOTE_PANEL_GEOM_Y0 + END_NOTE_PANEL_POS_Y + widescreen_shift_y) * scale
|
|
1567
|
+
panel = rl.Rectangle(
|
|
1568
|
+
float(panel_left),
|
|
1569
|
+
float(panel_top),
|
|
1570
|
+
float(END_NOTE_PANEL_W * scale),
|
|
1571
|
+
float(END_NOTE_PANEL_H * scale),
|
|
1572
|
+
)
|
|
1573
|
+
|
|
1574
|
+
fx_detail = bool(int(self._state.config.data.get("fx_detail_0", 0) or 0))
|
|
1575
|
+
draw_classic_menu_panel(panel_tex, dst=panel, tint=rl.WHITE, shadow=fx_detail)
|
|
1576
|
+
|
|
1577
|
+
font = self._ensure_small_font()
|
|
1578
|
+
hardcore = bool(int(self._state.config.data.get("hardcore_flag", 0) or 0))
|
|
1579
|
+
header = " Incredible!" if hardcore else "Congratulations!"
|
|
1580
|
+
body_lines = (
|
|
1581
|
+
[
|
|
1582
|
+
"You've done the thing we all thought was",
|
|
1583
|
+
"virtually impossible. To reward your",
|
|
1584
|
+
"efforts a new weapon has been unlocked ",
|
|
1585
|
+
"for you: Splitter Gun.",
|
|
1586
|
+
"",
|
|
1587
|
+
"",
|
|
1588
|
+
]
|
|
1589
|
+
if hardcore
|
|
1590
|
+
else [
|
|
1591
|
+
"You've completed all the levels but the battle",
|
|
1592
|
+
"isn't over yet! With all of the unlocked perks",
|
|
1593
|
+
"and weapons your Survival is just a bit easier.",
|
|
1594
|
+
"You can also replay the quests in Hardcore.",
|
|
1595
|
+
"As an additional reward for your victorious",
|
|
1596
|
+
"playing, a completely new and different game",
|
|
1597
|
+
"mode is unlocked for you: Typ'o'Shooter.",
|
|
1598
|
+
]
|
|
1599
|
+
)
|
|
1600
|
+
|
|
1601
|
+
header_x = panel_left + END_NOTE_HEADER_X_OFFSET * scale
|
|
1602
|
+
header_y = panel_top + END_NOTE_HEADER_Y_OFFSET * scale
|
|
1603
|
+
header_color = rl.Color(255, 255, 255, int(255 * 0.8))
|
|
1604
|
+
body_color = rl.Color(255, 255, 255, int(255 * 0.5))
|
|
1605
|
+
|
|
1606
|
+
draw_small_text(font, header, header_x, header_y, 1.5 * scale, header_color)
|
|
1607
|
+
|
|
1608
|
+
body_x = panel_left + END_NOTE_BODY_X_OFFSET * scale
|
|
1609
|
+
body_y = header_y + END_NOTE_BODY_Y_GAP * scale
|
|
1610
|
+
for idx, line in enumerate(body_lines):
|
|
1611
|
+
draw_small_text(font, line, body_x, body_y, 1.0 * scale, body_color)
|
|
1612
|
+
if idx != len(body_lines) - 1:
|
|
1613
|
+
body_y += END_NOTE_LINE_STEP_Y * scale
|
|
1614
|
+
body_y += END_NOTE_AFTER_BODY_Y_GAP * scale
|
|
1615
|
+
draw_small_text(font, "Good luck with your battles, trooper!", body_x, body_y, 1.0 * scale, body_color)
|
|
1616
|
+
|
|
1617
|
+
textures = self._button_textures
|
|
1618
|
+
if textures is not None and (textures.button_sm is not None or textures.button_md is not None):
|
|
1619
|
+
button_x = panel_left + END_NOTE_BUTTON_X_OFFSET * scale
|
|
1620
|
+
button_y = panel_top + END_NOTE_BUTTON_Y_OFFSET * scale
|
|
1621
|
+
survival_w = button_width(font, self._survival_button.label, scale=scale, force_wide=self._survival_button.force_wide)
|
|
1622
|
+
button_draw(textures, font, self._survival_button, x=button_x, y=button_y, width=survival_w, scale=scale)
|
|
1623
|
+
button_y += END_NOTE_BUTTON_STEP_Y * scale
|
|
1624
|
+
rush_w = button_width(font, self._rush_button.label, scale=scale, force_wide=self._rush_button.force_wide)
|
|
1625
|
+
button_draw(textures, font, self._rush_button, x=button_x, y=button_y, width=rush_w, scale=scale)
|
|
1626
|
+
button_y += END_NOTE_BUTTON_STEP_Y * scale
|
|
1627
|
+
typo_w = button_width(font, self._typo_button.label, scale=scale, force_wide=self._typo_button.force_wide)
|
|
1628
|
+
button_draw(textures, font, self._typo_button, x=button_x, y=button_y, width=typo_w, scale=scale)
|
|
1629
|
+
button_y += END_NOTE_BUTTON_STEP_Y * scale
|
|
1630
|
+
main_w = button_width(font, self._main_menu_button.label, scale=scale, force_wide=self._main_menu_button.force_wide)
|
|
1631
|
+
button_draw(textures, font, self._main_menu_button, x=button_x, y=button_y, width=main_w, scale=scale)
|
|
1632
|
+
|
|
1633
|
+
_draw_menu_cursor(self._state, pulse_time=self._cursor_pulse_time)
|
|
1634
|
+
|
|
1635
|
+
def take_action(self) -> str | None:
|
|
1636
|
+
action = self._action
|
|
1637
|
+
self._action = None
|
|
1638
|
+
return action
|
|
1639
|
+
|
|
1640
|
+
def _ensure_small_font(self) -> SmallFontData:
|
|
1641
|
+
if self._small_font is not None:
|
|
1642
|
+
return self._small_font
|
|
1643
|
+
missing_assets: list[str] = []
|
|
1644
|
+
self._small_font = load_small_font(self._state.assets_dir, missing_assets)
|
|
1645
|
+
return self._small_font
|
|
1646
|
+
|
|
1647
|
+
|
|
1339
1648
|
class QuestFailedView:
|
|
1340
1649
|
def __init__(self, state: GameState) -> None:
|
|
1341
1650
|
self._state = state
|
|
@@ -1558,6 +1867,10 @@ class HighScoresView:
|
|
|
1558
1867
|
self._small_font: SmallFontData | None = None
|
|
1559
1868
|
self._button_tex: rl.Texture2D | None = None
|
|
1560
1869
|
self._button_textures: UiButtonTextureSet | None = None
|
|
1870
|
+
self._check_on: rl.Texture2D | None = None
|
|
1871
|
+
self._drop_off: rl.Texture2D | None = None
|
|
1872
|
+
self._arrow_tex: rl.Texture2D | None = None
|
|
1873
|
+
self._wicons_tex: rl.Texture2D | None = None
|
|
1561
1874
|
self._update_button = UiButtonState("Update scores", force_wide=True)
|
|
1562
1875
|
self._play_button = UiButtonState("Play a game", force_wide=True)
|
|
1563
1876
|
self._back_button = UiButtonState("Back", force_wide=False)
|
|
@@ -1588,6 +1901,16 @@ class HighScoresView:
|
|
|
1588
1901
|
self._button_tex = cache.get_or_load("ui_buttonMd", "ui/ui_button_128x32.jaz").texture
|
|
1589
1902
|
button_sm = cache.get_or_load("ui_buttonSm", "ui/ui_button_64x32.jaz").texture
|
|
1590
1903
|
self._button_textures = UiButtonTextureSet(button_sm=button_sm, button_md=self._button_tex)
|
|
1904
|
+
self._check_on = cache.get_or_load("ui_checkOn", "ui/ui_checkOn.jaz").texture
|
|
1905
|
+
self._drop_off = cache.get_or_load("ui_dropOff", "ui/ui_dropDownOff.jaz").texture
|
|
1906
|
+
self._arrow_tex = cache.get_or_load("ui_arrow", "ui/ui_arrow.jaz").texture
|
|
1907
|
+
|
|
1908
|
+
if self._wicons_tex is not None:
|
|
1909
|
+
rl.unload_texture(self._wicons_tex)
|
|
1910
|
+
self._wicons_tex = None
|
|
1911
|
+
wicons_path = self._state.assets_dir / "crimson" / "ui" / "ui_wicons.png"
|
|
1912
|
+
if wicons_path.is_file():
|
|
1913
|
+
self._wicons_tex = rl.load_texture(str(wicons_path))
|
|
1591
1914
|
|
|
1592
1915
|
request = self._state.pending_high_scores
|
|
1593
1916
|
self._state.pending_high_scores = None
|
|
@@ -1623,9 +1946,15 @@ class HighScoresView:
|
|
|
1623
1946
|
if self._small_font is not None:
|
|
1624
1947
|
rl.unload_texture(self._small_font.texture)
|
|
1625
1948
|
self._small_font = None
|
|
1949
|
+
if self._wicons_tex is not None:
|
|
1950
|
+
rl.unload_texture(self._wicons_tex)
|
|
1951
|
+
self._wicons_tex = None
|
|
1626
1952
|
self._assets = None
|
|
1627
1953
|
self._button_tex = None
|
|
1628
1954
|
self._button_textures = None
|
|
1955
|
+
self._check_on = None
|
|
1956
|
+
self._drop_off = None
|
|
1957
|
+
self._arrow_tex = None
|
|
1629
1958
|
self._request = None
|
|
1630
1959
|
self._records = []
|
|
1631
1960
|
self._scroll_index = 0
|
|
@@ -1779,10 +2108,22 @@ class HighScoresView:
|
|
|
1779
2108
|
)
|
|
1780
2109
|
|
|
1781
2110
|
title = "High scores - Quests" if int(mode_id) == 3 else f"High scores - {self._mode_label(mode_id, quest_major, quest_minor)}"
|
|
1782
|
-
|
|
2111
|
+
title_x = 269.0
|
|
2112
|
+
if int(mode_id) == 1:
|
|
2113
|
+
# state_14:High scores - Survival title at x=168 (panel left_x0 is -98).
|
|
2114
|
+
title_x = 266.0
|
|
2115
|
+
draw_small_text(font, title, left_x0 + title_x * scale, left_y0 + 41.0 * scale, 1.0 * scale, rl.Color(255, 255, 255, 255))
|
|
1783
2116
|
if int(mode_id) == 3:
|
|
1784
2117
|
quest_label = f"{int(quest_major)}.{int(quest_minor)}: {self._quest_title(quest_major, quest_minor)}"
|
|
1785
2118
|
draw_small_text(font, quest_label, left_x0 + 236.0 * scale, left_y0 + 63.0 * scale, 1.0 * scale, rl.Color(255, 255, 255, 255))
|
|
2119
|
+
arrow = self._arrow_tex
|
|
2120
|
+
if arrow is not None:
|
|
2121
|
+
dst_w = float(arrow.width) * scale
|
|
2122
|
+
dst_h = float(arrow.height) * scale
|
|
2123
|
+
# state_14 draws ui_arrow.jaz flipped (uv 1..0) to point left.
|
|
2124
|
+
src = rl.Rectangle(float(arrow.width), 0.0, -float(arrow.width), float(arrow.height))
|
|
2125
|
+
dst = rl.Rectangle(left_x0 + HS_QUEST_ARROW_X * scale, left_y0 + HS_QUEST_ARROW_Y * scale, dst_w, dst_h)
|
|
2126
|
+
rl.draw_texture_pro(arrow, src, dst, rl.Vector2(0.0, 0.0), 0.0, rl.WHITE)
|
|
1786
2127
|
|
|
1787
2128
|
header_color = rl.Color(255, 255, 255, int(255 * 0.85))
|
|
1788
2129
|
row_y0 = left_y0 + 84.0 * scale
|
|
@@ -1841,9 +2182,351 @@ class HighScoresView:
|
|
|
1841
2182
|
scale=scale,
|
|
1842
2183
|
)
|
|
1843
2184
|
|
|
2185
|
+
self._draw_right_panel(font=font, right_x0=right_x0, right_y0=right_y0, scale=scale, mode_id=mode_id, highlight_rank=highlight_rank)
|
|
1844
2186
|
self._draw_sign(assets)
|
|
1845
2187
|
_draw_menu_cursor(self._state, pulse_time=self._cursor_pulse_time)
|
|
1846
2188
|
|
|
2189
|
+
def _draw_right_panel(
|
|
2190
|
+
self,
|
|
2191
|
+
*,
|
|
2192
|
+
font: SmallFontData,
|
|
2193
|
+
right_x0: float,
|
|
2194
|
+
right_y0: float,
|
|
2195
|
+
scale: float,
|
|
2196
|
+
mode_id: int,
|
|
2197
|
+
highlight_rank: int | None,
|
|
2198
|
+
) -> None:
|
|
2199
|
+
if int(mode_id) == 3:
|
|
2200
|
+
self._draw_right_panel_quest_options(font=font, right_x0=right_x0, right_y0=right_y0, scale=scale)
|
|
2201
|
+
return
|
|
2202
|
+
self._draw_right_panel_local_score(
|
|
2203
|
+
font=font,
|
|
2204
|
+
right_x0=right_x0,
|
|
2205
|
+
right_y0=right_y0,
|
|
2206
|
+
scale=scale,
|
|
2207
|
+
highlight_rank=highlight_rank,
|
|
2208
|
+
)
|
|
2209
|
+
|
|
2210
|
+
def _draw_right_panel_quest_options(self, *, font: SmallFontData, right_x0: float, right_y0: float, scale: float) -> None:
|
|
2211
|
+
text_scale = 1.0 * scale
|
|
2212
|
+
text_color = rl.Color(255, 255, 255, int(255 * 0.8))
|
|
2213
|
+
|
|
2214
|
+
check_on = self._check_on
|
|
2215
|
+
if check_on is not None:
|
|
2216
|
+
check_w = float(check_on.width) * scale
|
|
2217
|
+
check_h = float(check_on.height) * scale
|
|
2218
|
+
rl.draw_texture_pro(
|
|
2219
|
+
check_on,
|
|
2220
|
+
rl.Rectangle(0.0, 0.0, float(check_on.width), float(check_on.height)),
|
|
2221
|
+
rl.Rectangle(right_x0 + HS_RIGHT_CHECK_X * scale, right_y0 + HS_RIGHT_CHECK_Y * scale, check_w, check_h),
|
|
2222
|
+
rl.Vector2(0.0, 0.0),
|
|
2223
|
+
0.0,
|
|
2224
|
+
rl.WHITE,
|
|
2225
|
+
)
|
|
2226
|
+
draw_small_text(
|
|
2227
|
+
font,
|
|
2228
|
+
"Show internet scores",
|
|
2229
|
+
right_x0 + HS_RIGHT_SHOW_INTERNET_X * scale,
|
|
2230
|
+
right_y0 + HS_RIGHT_SHOW_INTERNET_Y * scale,
|
|
2231
|
+
text_scale,
|
|
2232
|
+
text_color,
|
|
2233
|
+
)
|
|
2234
|
+
draw_small_text(
|
|
2235
|
+
font,
|
|
2236
|
+
"Number of players",
|
|
2237
|
+
right_x0 + HS_RIGHT_NUMBER_PLAYERS_X * scale,
|
|
2238
|
+
right_y0 + HS_RIGHT_NUMBER_PLAYERS_Y * scale,
|
|
2239
|
+
text_scale,
|
|
2240
|
+
text_color,
|
|
2241
|
+
)
|
|
2242
|
+
draw_small_text(
|
|
2243
|
+
font,
|
|
2244
|
+
"Game mode",
|
|
2245
|
+
right_x0 + HS_RIGHT_GAME_MODE_X * scale,
|
|
2246
|
+
right_y0 + HS_RIGHT_GAME_MODE_Y * scale,
|
|
2247
|
+
text_scale,
|
|
2248
|
+
text_color,
|
|
2249
|
+
)
|
|
2250
|
+
draw_small_text(
|
|
2251
|
+
font,
|
|
2252
|
+
"Show scores:",
|
|
2253
|
+
right_x0 + HS_RIGHT_SHOW_SCORES_X * scale,
|
|
2254
|
+
right_y0 + HS_RIGHT_SHOW_SCORES_Y * scale,
|
|
2255
|
+
text_scale,
|
|
2256
|
+
text_color,
|
|
2257
|
+
)
|
|
2258
|
+
draw_small_text(
|
|
2259
|
+
font,
|
|
2260
|
+
"Selected score list:",
|
|
2261
|
+
right_x0 + HS_RIGHT_SCORE_LIST_X * scale,
|
|
2262
|
+
right_y0 + HS_RIGHT_SCORE_LIST_Y * scale,
|
|
2263
|
+
text_scale,
|
|
2264
|
+
text_color,
|
|
2265
|
+
)
|
|
2266
|
+
|
|
2267
|
+
# Closed list widgets (state_14 quest variant): white border + black fill.
|
|
2268
|
+
widget_h = 16.0 * scale
|
|
2269
|
+
for wx, wy, ww in (
|
|
2270
|
+
(HS_RIGHT_PLAYER_COUNT_WIDGET_X, HS_RIGHT_PLAYER_COUNT_WIDGET_Y, HS_RIGHT_PLAYER_COUNT_WIDGET_W),
|
|
2271
|
+
(HS_RIGHT_GAME_MODE_WIDGET_X, HS_RIGHT_GAME_MODE_WIDGET_Y, HS_RIGHT_GAME_MODE_WIDGET_W),
|
|
2272
|
+
(HS_RIGHT_SHOW_SCORES_WIDGET_X, HS_RIGHT_SHOW_SCORES_WIDGET_Y, HS_RIGHT_SHOW_SCORES_WIDGET_W),
|
|
2273
|
+
(HS_RIGHT_SCORE_LIST_WIDGET_X, HS_RIGHT_SCORE_LIST_WIDGET_Y, HS_RIGHT_SCORE_LIST_WIDGET_W),
|
|
2274
|
+
):
|
|
2275
|
+
x = right_x0 + float(wx) * scale
|
|
2276
|
+
y = right_y0 + float(wy) * scale
|
|
2277
|
+
w = float(ww) * scale
|
|
2278
|
+
rl.draw_rectangle(int(x), int(y), int(w), int(widget_h), rl.WHITE)
|
|
2279
|
+
rl.draw_rectangle(int(x) + 1, int(y) + 1, max(0, int(w) - 2), max(0, int(widget_h) - 2), rl.BLACK)
|
|
2280
|
+
|
|
2281
|
+
# Values (static in the oracle).
|
|
2282
|
+
draw_small_text(
|
|
2283
|
+
font,
|
|
2284
|
+
"1 player",
|
|
2285
|
+
right_x0 + HS_RIGHT_PLAYER_COUNT_VALUE_X * scale,
|
|
2286
|
+
right_y0 + HS_RIGHT_PLAYER_COUNT_VALUE_Y * scale,
|
|
2287
|
+
text_scale,
|
|
2288
|
+
text_color,
|
|
2289
|
+
)
|
|
2290
|
+
draw_small_text(
|
|
2291
|
+
font,
|
|
2292
|
+
"Quests",
|
|
2293
|
+
right_x0 + HS_RIGHT_GAME_MODE_VALUE_X * scale,
|
|
2294
|
+
right_y0 + HS_RIGHT_GAME_MODE_VALUE_Y * scale,
|
|
2295
|
+
text_scale,
|
|
2296
|
+
text_color,
|
|
2297
|
+
)
|
|
2298
|
+
draw_small_text(
|
|
2299
|
+
font,
|
|
2300
|
+
"Best of all time",
|
|
2301
|
+
right_x0 + HS_RIGHT_SHOW_SCORES_VALUE_X * scale,
|
|
2302
|
+
right_y0 + HS_RIGHT_SHOW_SCORES_VALUE_Y * scale,
|
|
2303
|
+
text_scale,
|
|
2304
|
+
text_color,
|
|
2305
|
+
)
|
|
2306
|
+
draw_small_text(
|
|
2307
|
+
font,
|
|
2308
|
+
"default",
|
|
2309
|
+
right_x0 + HS_RIGHT_SCORE_LIST_VALUE_X * scale,
|
|
2310
|
+
right_y0 + HS_RIGHT_SCORE_LIST_VALUE_Y * scale,
|
|
2311
|
+
text_scale,
|
|
2312
|
+
text_color,
|
|
2313
|
+
)
|
|
2314
|
+
|
|
2315
|
+
drop_off = self._drop_off
|
|
2316
|
+
if drop_off is None:
|
|
2317
|
+
return
|
|
2318
|
+
drop_w = float(drop_off.width) * scale
|
|
2319
|
+
drop_h = float(drop_off.height) * scale
|
|
2320
|
+
for dx, dy in (
|
|
2321
|
+
(HS_RIGHT_PLAYER_COUNT_DROP_X, HS_RIGHT_PLAYER_COUNT_DROP_Y),
|
|
2322
|
+
(HS_RIGHT_GAME_MODE_DROP_X, HS_RIGHT_GAME_MODE_DROP_Y),
|
|
2323
|
+
(HS_RIGHT_SHOW_SCORES_DROP_X, HS_RIGHT_SHOW_SCORES_DROP_Y),
|
|
2324
|
+
(HS_RIGHT_SCORE_LIST_DROP_X, HS_RIGHT_SCORE_LIST_DROP_Y),
|
|
2325
|
+
):
|
|
2326
|
+
rl.draw_texture_pro(
|
|
2327
|
+
drop_off,
|
|
2328
|
+
rl.Rectangle(0.0, 0.0, float(drop_off.width), float(drop_off.height)),
|
|
2329
|
+
rl.Rectangle(right_x0 + float(dx) * scale, right_y0 + float(dy) * scale, drop_w, drop_h),
|
|
2330
|
+
rl.Vector2(0.0, 0.0),
|
|
2331
|
+
0.0,
|
|
2332
|
+
rl.WHITE,
|
|
2333
|
+
)
|
|
2334
|
+
|
|
2335
|
+
def _draw_right_panel_local_score(
|
|
2336
|
+
self,
|
|
2337
|
+
*,
|
|
2338
|
+
font: SmallFontData,
|
|
2339
|
+
right_x0: float,
|
|
2340
|
+
right_y0: float,
|
|
2341
|
+
scale: float,
|
|
2342
|
+
highlight_rank: int | None,
|
|
2343
|
+
) -> None:
|
|
2344
|
+
if not self._records:
|
|
2345
|
+
return
|
|
2346
|
+
idx = int(highlight_rank) if highlight_rank is not None else int(self._scroll_index)
|
|
2347
|
+
if idx < 0:
|
|
2348
|
+
idx = 0
|
|
2349
|
+
if idx >= len(self._records):
|
|
2350
|
+
idx = len(self._records) - 1
|
|
2351
|
+
entry = self._records[idx]
|
|
2352
|
+
|
|
2353
|
+
text_scale = 1.0 * scale
|
|
2354
|
+
text_color = rl.Color(255, 255, 255, int(255 * 0.8))
|
|
2355
|
+
|
|
2356
|
+
name = ""
|
|
2357
|
+
try:
|
|
2358
|
+
name = str(entry.name())
|
|
2359
|
+
except Exception:
|
|
2360
|
+
name = ""
|
|
2361
|
+
if not name:
|
|
2362
|
+
name = "???"
|
|
2363
|
+
draw_small_text(font, name, right_x0 + HS_LOCAL_NAME_X * scale, right_y0 + HS_LOCAL_NAME_Y * scale, text_scale, text_color)
|
|
2364
|
+
draw_small_text(
|
|
2365
|
+
font,
|
|
2366
|
+
"Local score",
|
|
2367
|
+
right_x0 + HS_LOCAL_LABEL_X * scale,
|
|
2368
|
+
right_y0 + HS_LOCAL_LABEL_Y * scale,
|
|
2369
|
+
text_scale,
|
|
2370
|
+
text_color,
|
|
2371
|
+
)
|
|
2372
|
+
|
|
2373
|
+
date_text = self._format_score_date(entry)
|
|
2374
|
+
if date_text:
|
|
2375
|
+
draw_small_text(
|
|
2376
|
+
font,
|
|
2377
|
+
date_text,
|
|
2378
|
+
right_x0 + HS_LOCAL_DATE_X * scale,
|
|
2379
|
+
right_y0 + HS_LOCAL_DATE_Y * scale,
|
|
2380
|
+
text_scale,
|
|
2381
|
+
text_color,
|
|
2382
|
+
)
|
|
2383
|
+
|
|
2384
|
+
draw_small_text(
|
|
2385
|
+
font,
|
|
2386
|
+
"Score",
|
|
2387
|
+
right_x0 + HS_LOCAL_SCORE_LABEL_X * scale,
|
|
2388
|
+
right_y0 + HS_LOCAL_SCORE_LABEL_Y * scale,
|
|
2389
|
+
text_scale,
|
|
2390
|
+
text_color,
|
|
2391
|
+
)
|
|
2392
|
+
draw_small_text(
|
|
2393
|
+
font,
|
|
2394
|
+
"Game time",
|
|
2395
|
+
right_x0 + HS_LOCAL_TIME_LABEL_X * scale,
|
|
2396
|
+
right_y0 + HS_LOCAL_TIME_LABEL_Y * scale,
|
|
2397
|
+
text_scale,
|
|
2398
|
+
text_color,
|
|
2399
|
+
)
|
|
2400
|
+
|
|
2401
|
+
score_value = f"{int(getattr(entry, 'score_xp', 0))}"
|
|
2402
|
+
draw_small_text(
|
|
2403
|
+
font,
|
|
2404
|
+
score_value,
|
|
2405
|
+
right_x0 + HS_LOCAL_SCORE_VALUE_X * scale,
|
|
2406
|
+
right_y0 + HS_LOCAL_SCORE_VALUE_Y * scale,
|
|
2407
|
+
text_scale,
|
|
2408
|
+
text_color,
|
|
2409
|
+
)
|
|
2410
|
+
|
|
2411
|
+
elapsed_ms = int(getattr(entry, "survival_elapsed_ms", 0) or 0)
|
|
2412
|
+
draw_small_text(
|
|
2413
|
+
font,
|
|
2414
|
+
self._format_elapsed_mm_ss(elapsed_ms),
|
|
2415
|
+
right_x0 + HS_LOCAL_TIME_VALUE_X * scale,
|
|
2416
|
+
right_y0 + HS_LOCAL_TIME_VALUE_Y * scale,
|
|
2417
|
+
text_scale,
|
|
2418
|
+
text_color,
|
|
2419
|
+
)
|
|
2420
|
+
|
|
2421
|
+
draw_small_text(
|
|
2422
|
+
font,
|
|
2423
|
+
f"Rank: {self._ordinal(idx + 1)}",
|
|
2424
|
+
right_x0 + HS_LOCAL_RANK_X * scale,
|
|
2425
|
+
right_y0 + HS_LOCAL_RANK_Y * scale,
|
|
2426
|
+
text_scale,
|
|
2427
|
+
text_color,
|
|
2428
|
+
)
|
|
2429
|
+
|
|
2430
|
+
frags = int(getattr(entry, "creature_kill_count", 0) or 0)
|
|
2431
|
+
draw_small_text(
|
|
2432
|
+
font,
|
|
2433
|
+
f"Frags: {frags}",
|
|
2434
|
+
right_x0 + HS_LOCAL_FRAGS_X * scale,
|
|
2435
|
+
right_y0 + HS_LOCAL_FRAGS_Y * scale,
|
|
2436
|
+
text_scale,
|
|
2437
|
+
text_color,
|
|
2438
|
+
)
|
|
2439
|
+
|
|
2440
|
+
shots_fired = int(getattr(entry, "shots_fired", 0) or 0)
|
|
2441
|
+
shots_hit = int(getattr(entry, "shots_hit", 0) or 0)
|
|
2442
|
+
hit_pct = 0
|
|
2443
|
+
if shots_fired > 0:
|
|
2444
|
+
hit_pct = int((shots_hit * 100) // shots_fired)
|
|
2445
|
+
draw_small_text(
|
|
2446
|
+
font,
|
|
2447
|
+
f"Hit %: {hit_pct}%",
|
|
2448
|
+
right_x0 + HS_LOCAL_HIT_X * scale,
|
|
2449
|
+
right_y0 + HS_LOCAL_HIT_Y * scale,
|
|
2450
|
+
text_scale,
|
|
2451
|
+
text_color,
|
|
2452
|
+
)
|
|
2453
|
+
|
|
2454
|
+
weapon_id = int(getattr(entry, "most_used_weapon_id", 0) or 0)
|
|
2455
|
+
weapon_name, icon_index = self._weapon_label_and_icon(weapon_id)
|
|
2456
|
+
if icon_index is not None:
|
|
2457
|
+
self._draw_wicon(icon_index, x=right_x0 + HS_LOCAL_WICON_X * scale, y=right_y0 + HS_LOCAL_WICON_Y * scale, scale=scale)
|
|
2458
|
+
draw_small_text(
|
|
2459
|
+
font,
|
|
2460
|
+
weapon_name,
|
|
2461
|
+
right_x0 + HS_LOCAL_WEAPON_X * scale,
|
|
2462
|
+
right_y0 + HS_LOCAL_WEAPON_Y * scale,
|
|
2463
|
+
text_scale,
|
|
2464
|
+
text_color,
|
|
2465
|
+
)
|
|
2466
|
+
|
|
2467
|
+
def _draw_wicon(self, icon_index: int, *, x: float, y: float, scale: float) -> None:
|
|
2468
|
+
tex = self._wicons_tex
|
|
2469
|
+
if tex is None:
|
|
2470
|
+
return
|
|
2471
|
+
idx = int(icon_index)
|
|
2472
|
+
if idx < 0 or idx > 31:
|
|
2473
|
+
return
|
|
2474
|
+
cols = 4
|
|
2475
|
+
rows = 8
|
|
2476
|
+
icon_w = float(tex.width) / float(cols)
|
|
2477
|
+
icon_h = float(tex.height) / float(rows)
|
|
2478
|
+
src_x = float(idx % cols) * icon_w
|
|
2479
|
+
src_y = float(idx // cols) * icon_h
|
|
2480
|
+
rl.draw_texture_pro(
|
|
2481
|
+
tex,
|
|
2482
|
+
rl.Rectangle(src_x, src_y, icon_w, icon_h),
|
|
2483
|
+
rl.Rectangle(float(x), float(y), icon_w * scale, icon_h * scale),
|
|
2484
|
+
rl.Vector2(0.0, 0.0),
|
|
2485
|
+
0.0,
|
|
2486
|
+
rl.WHITE,
|
|
2487
|
+
)
|
|
2488
|
+
|
|
2489
|
+
@staticmethod
|
|
2490
|
+
def _ordinal(value: int) -> str:
|
|
2491
|
+
n = int(value)
|
|
2492
|
+
if 10 <= (n % 100) <= 20:
|
|
2493
|
+
return f"{n}th"
|
|
2494
|
+
suffix = {1: "st", 2: "nd", 3: "rd"}.get(n % 10, "th")
|
|
2495
|
+
return f"{n}{suffix}"
|
|
2496
|
+
|
|
2497
|
+
@staticmethod
|
|
2498
|
+
def _format_elapsed_mm_ss(value_ms: int) -> str:
|
|
2499
|
+
total = max(0, int(value_ms)) // 1000
|
|
2500
|
+
minutes, seconds = divmod(total, 60)
|
|
2501
|
+
return f"{minutes}:{seconds:02d}"
|
|
2502
|
+
|
|
2503
|
+
@staticmethod
|
|
2504
|
+
def _format_score_date(entry: object) -> str:
|
|
2505
|
+
try:
|
|
2506
|
+
day = int(getattr(entry, "day", 0) or 0)
|
|
2507
|
+
month = int(getattr(entry, "month", 0) or 0)
|
|
2508
|
+
year_off = int(getattr(entry, "year_offset", 0) or 0)
|
|
2509
|
+
except Exception:
|
|
2510
|
+
return ""
|
|
2511
|
+
if day <= 0 or month <= 0:
|
|
2512
|
+
return ""
|
|
2513
|
+
months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
|
|
2514
|
+
month_name = months[month - 1] if 1 <= month <= 12 else f"{month}"
|
|
2515
|
+
year = 2000 + year_off if year_off >= 0 else 2000
|
|
2516
|
+
return f"{day}. {month_name} {year}"
|
|
2517
|
+
|
|
2518
|
+
@staticmethod
|
|
2519
|
+
def _weapon_label_and_icon(weapon_id: int) -> tuple[str, int | None]:
|
|
2520
|
+
try:
|
|
2521
|
+
from .weapons import WEAPON_BY_ID
|
|
2522
|
+
except Exception:
|
|
2523
|
+
WEAPON_BY_ID = {}
|
|
2524
|
+
weapon = WEAPON_BY_ID.get(int(weapon_id))
|
|
2525
|
+
if weapon is None:
|
|
2526
|
+
return f"Weapon {int(weapon_id)}", None
|
|
2527
|
+
name = weapon.name or f"weapon_{int(weapon.weapon_id)}"
|
|
2528
|
+
return name, weapon.icon_index
|
|
2529
|
+
|
|
1847
2530
|
def _draw_sign(self, assets: MenuAssets) -> None:
|
|
1848
2531
|
if assets.sign is None:
|
|
1849
2532
|
return
|
|
@@ -1944,6 +2627,7 @@ class GameLoopView:
|
|
|
1944
2627
|
"start_quest": QuestGameView(state),
|
|
1945
2628
|
"quest_results": QuestResultsView(state),
|
|
1946
2629
|
"quest_failed": QuestFailedView(state),
|
|
2630
|
+
"end_note": EndNoteView(state),
|
|
1947
2631
|
"open_high_scores": HighScoresView(state),
|
|
1948
2632
|
"start_survival": SurvivalGameView(state),
|
|
1949
2633
|
"start_rush": RushGameView(state),
|
|
@@ -2474,6 +3158,8 @@ def _resolve_assets_dir(config: GameConfig) -> Path:
|
|
|
2474
3158
|
|
|
2475
3159
|
|
|
2476
3160
|
def run_game(config: GameConfig) -> None:
|
|
3161
|
+
if config.debug:
|
|
3162
|
+
set_debug_enabled(True)
|
|
2477
3163
|
base_dir = config.base_dir
|
|
2478
3164
|
base_dir.mkdir(parents=True, exist_ok=True)
|
|
2479
3165
|
crash_path = base_dir / "crash.log"
|