crimsonland 0.1.0.dev5__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/__init__.py +24 -0
- crimson/assets_fetch.py +60 -0
- crimson/atlas.py +92 -0
- crimson/audio_router.py +155 -0
- crimson/bonuses.py +167 -0
- crimson/camera.py +75 -0
- crimson/cli.py +380 -0
- crimson/creatures/__init__.py +8 -0
- crimson/creatures/ai.py +186 -0
- crimson/creatures/anim.py +173 -0
- crimson/creatures/damage.py +103 -0
- crimson/creatures/runtime.py +1019 -0
- crimson/creatures/spawn.py +2871 -0
- crimson/debug.py +7 -0
- crimson/demo.py +1360 -0
- crimson/demo_trial.py +140 -0
- crimson/effects.py +1086 -0
- crimson/effects_atlas.py +73 -0
- crimson/frontend/__init__.py +1 -0
- crimson/frontend/assets.py +43 -0
- crimson/frontend/boot.py +424 -0
- crimson/frontend/menu.py +700 -0
- crimson/frontend/panels/__init__.py +1 -0
- crimson/frontend/panels/base.py +410 -0
- crimson/frontend/panels/controls.py +132 -0
- crimson/frontend/panels/mods.py +128 -0
- crimson/frontend/panels/options.py +409 -0
- crimson/frontend/panels/play_game.py +627 -0
- crimson/frontend/panels/stats.py +351 -0
- crimson/frontend/transitions.py +31 -0
- crimson/game.py +2533 -0
- crimson/game_modes.py +15 -0
- crimson/game_world.py +652 -0
- crimson/gameplay.py +2467 -0
- crimson/input_codes.py +176 -0
- crimson/modes/__init__.py +1 -0
- crimson/modes/base_gameplay_mode.py +219 -0
- crimson/modes/quest_mode.py +502 -0
- crimson/modes/rush_mode.py +300 -0
- crimson/modes/survival_mode.py +792 -0
- crimson/modes/tutorial_mode.py +648 -0
- crimson/modes/typo_mode.py +472 -0
- crimson/paths.py +23 -0
- crimson/perks.py +828 -0
- crimson/persistence/__init__.py +1 -0
- crimson/persistence/highscores.py +385 -0
- crimson/persistence/save_status.py +245 -0
- crimson/player_damage.py +77 -0
- crimson/projectiles.py +1133 -0
- crimson/quests/__init__.py +18 -0
- crimson/quests/helpers.py +147 -0
- crimson/quests/registry.py +49 -0
- crimson/quests/results.py +164 -0
- crimson/quests/runtime.py +91 -0
- crimson/quests/tier1.py +620 -0
- crimson/quests/tier2.py +652 -0
- crimson/quests/tier3.py +579 -0
- crimson/quests/tier4.py +721 -0
- crimson/quests/tier5.py +886 -0
- crimson/quests/timeline.py +115 -0
- crimson/quests/types.py +70 -0
- crimson/render/__init__.py +1 -0
- crimson/render/terrain_fx.py +88 -0
- crimson/render/world_renderer.py +1941 -0
- crimson/sim/__init__.py +1 -0
- crimson/sim/world_defs.py +67 -0
- crimson/sim/world_state.py +422 -0
- crimson/terrain_assets.py +19 -0
- crimson/tutorial/__init__.py +12 -0
- crimson/tutorial/timeline.py +291 -0
- crimson/typo/__init__.py +2 -0
- crimson/typo/names.py +233 -0
- crimson/typo/player.py +43 -0
- crimson/typo/spawns.py +73 -0
- crimson/typo/typing.py +52 -0
- crimson/ui/__init__.py +3 -0
- crimson/ui/cursor.py +95 -0
- crimson/ui/demo_trial_overlay.py +235 -0
- crimson/ui/game_over.py +660 -0
- crimson/ui/hud.py +601 -0
- crimson/ui/perk_menu.py +388 -0
- crimson/views/__init__.py +40 -0
- crimson/views/aim_debug.py +276 -0
- crimson/views/animations.py +274 -0
- crimson/views/arsenal_debug.py +404 -0
- crimson/views/audio_bootstrap.py +47 -0
- crimson/views/bonuses.py +201 -0
- crimson/views/camera_debug.py +359 -0
- crimson/views/camera_shake.py +229 -0
- crimson/views/corpse_stamp_debug.py +324 -0
- crimson/views/decals_debug.py +739 -0
- crimson/views/empty.py +19 -0
- crimson/views/fonts.py +114 -0
- crimson/views/game_over.py +117 -0
- crimson/views/ground.py +259 -0
- crimson/views/lighting_debug.py +1166 -0
- crimson/views/particles.py +293 -0
- crimson/views/perk_menu_debug.py +430 -0
- crimson/views/perks.py +398 -0
- crimson/views/player.py +434 -0
- crimson/views/player_sprite_debug.py +314 -0
- crimson/views/projectile_fx.py +609 -0
- crimson/views/projectile_render_debug.py +393 -0
- crimson/views/projectiles.py +221 -0
- crimson/views/quest_title_overlay.py +108 -0
- crimson/views/registry.py +34 -0
- crimson/views/rush.py +16 -0
- crimson/views/small_font_debug.py +204 -0
- crimson/views/spawn_plan.py +363 -0
- crimson/views/sprites.py +214 -0
- crimson/views/survival.py +15 -0
- crimson/views/terrain.py +132 -0
- crimson/views/ui.py +123 -0
- crimson/views/wicons.py +166 -0
- crimson/weapon_sfx.py +63 -0
- crimson/weapons.py +860 -0
- crimsonland-0.1.0.dev5.dist-info/METADATA +9 -0
- crimsonland-0.1.0.dev5.dist-info/RECORD +139 -0
- crimsonland-0.1.0.dev5.dist-info/WHEEL +4 -0
- crimsonland-0.1.0.dev5.dist-info/entry_points.txt +4 -0
- grim/__init__.py +20 -0
- grim/app.py +92 -0
- grim/assets.py +231 -0
- grim/audio.py +106 -0
- grim/config.py +294 -0
- grim/console.py +737 -0
- grim/fonts/__init__.py +7 -0
- grim/fonts/grim_mono.py +111 -0
- grim/fonts/small.py +120 -0
- grim/input.py +44 -0
- grim/jaz.py +103 -0
- grim/math.py +17 -0
- grim/music.py +403 -0
- grim/paq.py +76 -0
- grim/rand.py +37 -0
- grim/sfx.py +276 -0
- grim/sfx_map.py +103 -0
- grim/terrain_render.py +840 -0
- grim/view.py +16 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import pyray as rl
|
|
6
|
+
|
|
7
|
+
from grim.audio import play_music, stop_music
|
|
8
|
+
from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
|
|
9
|
+
|
|
10
|
+
from ..menu import (
|
|
11
|
+
MENU_LABEL_ROW_HEIGHT,
|
|
12
|
+
MENU_LABEL_ROW_STATISTICS,
|
|
13
|
+
MENU_LABEL_WIDTH,
|
|
14
|
+
MENU_PANEL_HEIGHT,
|
|
15
|
+
MENU_PANEL_OFFSET_X,
|
|
16
|
+
MENU_PANEL_OFFSET_Y,
|
|
17
|
+
MENU_PANEL_WIDTH,
|
|
18
|
+
MenuView,
|
|
19
|
+
_draw_menu_cursor,
|
|
20
|
+
)
|
|
21
|
+
from ..transitions import _draw_screen_fade
|
|
22
|
+
from .base import PANEL_TIMELINE_END_MS, PANEL_TIMELINE_START_MS, PanelMenuView
|
|
23
|
+
|
|
24
|
+
from ...persistence.save_status import MODE_COUNT_ORDER
|
|
25
|
+
from ...weapons import WEAPON_BY_ID, WeaponId
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from ...game import GameState
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class StatisticsMenuView(PanelMenuView):
|
|
32
|
+
_PAGES = ("Summary", "Weapons", "Quests")
|
|
33
|
+
|
|
34
|
+
def __init__(self, state: GameState) -> None:
|
|
35
|
+
super().__init__(state, title="Statistics")
|
|
36
|
+
self._small_font: SmallFontData | None = None
|
|
37
|
+
self._page_index = 0
|
|
38
|
+
self._scroll_index = 0
|
|
39
|
+
self._page_lines: list[list[str]] = []
|
|
40
|
+
|
|
41
|
+
def open(self) -> None:
|
|
42
|
+
super().open()
|
|
43
|
+
self._page_index = 0
|
|
44
|
+
self._scroll_index = 0
|
|
45
|
+
self._page_lines = self._build_pages()
|
|
46
|
+
if self._state.audio is not None:
|
|
47
|
+
if self._state.audio.music.active_track != "shortie_monk":
|
|
48
|
+
stop_music(self._state.audio)
|
|
49
|
+
play_music(self._state.audio, "shortie_monk")
|
|
50
|
+
|
|
51
|
+
def draw(self) -> None:
|
|
52
|
+
rl.clear_background(rl.BLACK)
|
|
53
|
+
if self._ground is not None:
|
|
54
|
+
self._ground.draw(0.0, 0.0)
|
|
55
|
+
_draw_screen_fade(self._state)
|
|
56
|
+
assets = self._assets
|
|
57
|
+
entry = self._entry
|
|
58
|
+
if assets is None or entry is None:
|
|
59
|
+
return
|
|
60
|
+
self._draw_panel()
|
|
61
|
+
self._draw_entry(entry)
|
|
62
|
+
self._draw_sign()
|
|
63
|
+
self._draw_stats_contents()
|
|
64
|
+
_draw_menu_cursor(self._state, pulse_time=self._cursor_pulse_time)
|
|
65
|
+
|
|
66
|
+
def update(self, dt: float) -> None:
|
|
67
|
+
super().update(dt)
|
|
68
|
+
if self._closing:
|
|
69
|
+
return
|
|
70
|
+
entry = self._entry
|
|
71
|
+
if entry is None or not self._entry_enabled(entry):
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_LEFT):
|
|
75
|
+
self._switch_page(-1)
|
|
76
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_RIGHT):
|
|
77
|
+
self._switch_page(1)
|
|
78
|
+
|
|
79
|
+
font = self._ensure_small_font()
|
|
80
|
+
layout = self._content_layout()
|
|
81
|
+
rows = self._visible_rows(font, layout)
|
|
82
|
+
max_scroll = max(0, len(self._active_page_lines()) - rows)
|
|
83
|
+
|
|
84
|
+
wheel = int(rl.get_mouse_wheel_move())
|
|
85
|
+
if wheel:
|
|
86
|
+
self._scroll_index = max(0, min(max_scroll, int(self._scroll_index) - wheel))
|
|
87
|
+
|
|
88
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_UP):
|
|
89
|
+
self._scroll_index = max(0, int(self._scroll_index) - 1)
|
|
90
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_DOWN):
|
|
91
|
+
self._scroll_index = min(max_scroll, int(self._scroll_index) + 1)
|
|
92
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_PAGE_UP):
|
|
93
|
+
self._scroll_index = max(0, int(self._scroll_index) - rows)
|
|
94
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_PAGE_DOWN):
|
|
95
|
+
self._scroll_index = min(max_scroll, int(self._scroll_index) + rows)
|
|
96
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_HOME):
|
|
97
|
+
self._scroll_index = 0
|
|
98
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_END):
|
|
99
|
+
self._scroll_index = max_scroll
|
|
100
|
+
|
|
101
|
+
def _ensure_small_font(self) -> SmallFontData:
|
|
102
|
+
if self._small_font is not None:
|
|
103
|
+
return self._small_font
|
|
104
|
+
missing_assets: list[str] = []
|
|
105
|
+
self._small_font = load_small_font(self._state.assets_dir, missing_assets)
|
|
106
|
+
return self._small_font
|
|
107
|
+
|
|
108
|
+
def _content_layout(self) -> dict[str, float]:
|
|
109
|
+
panel_scale, _local_shift = self._menu_item_scale(0)
|
|
110
|
+
panel_w = MENU_PANEL_WIDTH * panel_scale
|
|
111
|
+
_angle_rad, slide_x = MenuView._ui_element_anim(
|
|
112
|
+
self,
|
|
113
|
+
index=1,
|
|
114
|
+
start_ms=PANEL_TIMELINE_START_MS,
|
|
115
|
+
end_ms=PANEL_TIMELINE_END_MS,
|
|
116
|
+
width=panel_w,
|
|
117
|
+
)
|
|
118
|
+
panel_x = self._panel_pos_x + slide_x
|
|
119
|
+
panel_y = self._panel_pos_y + self._widescreen_y_shift
|
|
120
|
+
origin_x = -(MENU_PANEL_OFFSET_X * panel_scale)
|
|
121
|
+
origin_y = -(MENU_PANEL_OFFSET_Y * panel_scale)
|
|
122
|
+
panel_left = panel_x - origin_x
|
|
123
|
+
panel_top = panel_y - origin_y
|
|
124
|
+
base_x = panel_left + 212.0 * panel_scale
|
|
125
|
+
base_y = panel_top + 32.0 * panel_scale
|
|
126
|
+
label_x = base_x + 8.0 * panel_scale
|
|
127
|
+
return {
|
|
128
|
+
"panel_left": panel_left,
|
|
129
|
+
"panel_top": panel_top,
|
|
130
|
+
"base_x": base_x,
|
|
131
|
+
"base_y": base_y,
|
|
132
|
+
"label_x": label_x,
|
|
133
|
+
"scale": panel_scale,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
def _switch_page(self, delta: int) -> None:
|
|
137
|
+
if not self._page_lines:
|
|
138
|
+
return
|
|
139
|
+
count = len(self._page_lines)
|
|
140
|
+
self._page_index = (int(self._page_index) + int(delta)) % count
|
|
141
|
+
self._scroll_index = 0
|
|
142
|
+
|
|
143
|
+
def _active_page_lines(self) -> list[str]:
|
|
144
|
+
if not self._page_lines:
|
|
145
|
+
return []
|
|
146
|
+
idx = int(self._page_index)
|
|
147
|
+
if idx < 0:
|
|
148
|
+
idx = 0
|
|
149
|
+
if idx >= len(self._page_lines):
|
|
150
|
+
idx = len(self._page_lines) - 1
|
|
151
|
+
return self._page_lines[idx]
|
|
152
|
+
|
|
153
|
+
def _build_pages(self) -> list[list[str]]:
|
|
154
|
+
return [
|
|
155
|
+
self._build_summary_lines(),
|
|
156
|
+
self._build_weapon_usage_lines(),
|
|
157
|
+
self._build_quest_progress_lines(),
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
def _build_summary_lines(self) -> list[str]:
|
|
161
|
+
status = self._state.status
|
|
162
|
+
mode_counts = {name: status.mode_play_count(name) for name, _offset in MODE_COUNT_ORDER}
|
|
163
|
+
quest_counts = status.data.get("quest_play_counts", [])
|
|
164
|
+
if isinstance(quest_counts, list):
|
|
165
|
+
quest_total = int(sum(int(v) for v in quest_counts[:40]))
|
|
166
|
+
else:
|
|
167
|
+
quest_total = 0
|
|
168
|
+
|
|
169
|
+
checksum_text = "unknown"
|
|
170
|
+
try:
|
|
171
|
+
from ...persistence.save_status import load_status
|
|
172
|
+
|
|
173
|
+
blob = load_status(status.path)
|
|
174
|
+
ok = "ok" if blob.checksum_valid else "BAD"
|
|
175
|
+
checksum_text = f"0x{blob.checksum:08x} ({ok})"
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
checksum_text = f"error: {type(exc).__name__}"
|
|
178
|
+
|
|
179
|
+
playtime_ms = int(status.game_sequence_id)
|
|
180
|
+
seconds = max(0, playtime_ms // 1000)
|
|
181
|
+
minutes = seconds // 60
|
|
182
|
+
hours = minutes // 60
|
|
183
|
+
minutes %= 60
|
|
184
|
+
seconds %= 60
|
|
185
|
+
|
|
186
|
+
lines = [
|
|
187
|
+
f"Played for: {hours}h {minutes:02d}m {seconds:02d}s",
|
|
188
|
+
f"Quest unlock: {status.quest_unlock_index} (full {status.quest_unlock_index_full})",
|
|
189
|
+
f"Quest plays (1-40): {quest_total}",
|
|
190
|
+
f"Mode plays: surv {mode_counts['survival']} rush {mode_counts['rush']}",
|
|
191
|
+
f" typo {mode_counts['typo']} other {mode_counts['other']}",
|
|
192
|
+
f"Playtime ms: {int(status.game_sequence_id)}",
|
|
193
|
+
f"Checksum: {checksum_text}",
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
usage = status.data.get("weapon_usage_counts", [])
|
|
197
|
+
top_weapons: list[tuple[int, int]] = []
|
|
198
|
+
if isinstance(usage, list):
|
|
199
|
+
for idx, count in enumerate(usage):
|
|
200
|
+
count = int(count)
|
|
201
|
+
if count > 0:
|
|
202
|
+
top_weapons.append((idx, count))
|
|
203
|
+
top_weapons.sort(key=lambda item: (-item[1], item[0]))
|
|
204
|
+
top_weapons = top_weapons[:4]
|
|
205
|
+
|
|
206
|
+
if top_weapons:
|
|
207
|
+
lines.append("Top weapons:")
|
|
208
|
+
for idx, count in top_weapons:
|
|
209
|
+
weapon = WEAPON_BY_ID.get(idx)
|
|
210
|
+
name = weapon.name if weapon is not None and weapon.name else f"weapon_{idx}"
|
|
211
|
+
lines.append(f" {name}: {count}")
|
|
212
|
+
else:
|
|
213
|
+
lines.append("Top weapons: none")
|
|
214
|
+
|
|
215
|
+
return lines
|
|
216
|
+
|
|
217
|
+
def _build_weapon_usage_lines(self) -> list[str]:
|
|
218
|
+
status = self._state.status
|
|
219
|
+
usage = status.data.get("weapon_usage_counts", [])
|
|
220
|
+
if not isinstance(usage, list):
|
|
221
|
+
return ["Weapon usage: error (missing weapon_usage_counts)"]
|
|
222
|
+
|
|
223
|
+
items: list[tuple[int, int, str]] = []
|
|
224
|
+
for idx, count in enumerate(usage):
|
|
225
|
+
weapon_id = int(idx)
|
|
226
|
+
if weapon_id == WeaponId.NONE:
|
|
227
|
+
continue
|
|
228
|
+
count = int(count)
|
|
229
|
+
weapon = WEAPON_BY_ID.get(weapon_id)
|
|
230
|
+
name = weapon.name if weapon is not None and weapon.name else f"weapon_{weapon_id}"
|
|
231
|
+
items.append((weapon_id, count, name))
|
|
232
|
+
|
|
233
|
+
items.sort(key=lambda item: (-item[1], item[0]))
|
|
234
|
+
total = sum(count for _weapon_id, count, _name in items)
|
|
235
|
+
max_id_width = max(2, len(str(max((weapon_id for weapon_id, _count, _name in items), default=0))))
|
|
236
|
+
|
|
237
|
+
lines = [
|
|
238
|
+
f"Weapon uses (total {total}):",
|
|
239
|
+
"",
|
|
240
|
+
]
|
|
241
|
+
for weapon_id, count, name in items:
|
|
242
|
+
lines.append(f"{weapon_id:>{max_id_width}} {count:>8} {name}")
|
|
243
|
+
return lines
|
|
244
|
+
|
|
245
|
+
def _build_quest_progress_lines(self) -> list[str]:
|
|
246
|
+
status = self._state.status
|
|
247
|
+
completed_total = 0
|
|
248
|
+
played_total = 0
|
|
249
|
+
|
|
250
|
+
lines = [
|
|
251
|
+
"Quest progress (stages 1-4):",
|
|
252
|
+
"",
|
|
253
|
+
]
|
|
254
|
+
for global_index in range(40):
|
|
255
|
+
stage = (global_index // 10) + 1
|
|
256
|
+
row = global_index % 10
|
|
257
|
+
level = f"{stage}.{row + 1}"
|
|
258
|
+
title = "???"
|
|
259
|
+
try:
|
|
260
|
+
from ...quests import quest_by_level
|
|
261
|
+
|
|
262
|
+
quest = quest_by_level(level)
|
|
263
|
+
if quest is not None:
|
|
264
|
+
title = quest.title
|
|
265
|
+
except Exception:
|
|
266
|
+
title = "???"
|
|
267
|
+
|
|
268
|
+
count_index = global_index + 10
|
|
269
|
+
games_idx = 1 + count_index
|
|
270
|
+
completed_idx = 41 + count_index
|
|
271
|
+
games = int(status.quest_play_count(games_idx))
|
|
272
|
+
completed = int(status.quest_play_count(completed_idx))
|
|
273
|
+
|
|
274
|
+
completed_total += completed
|
|
275
|
+
played_total += games
|
|
276
|
+
lines.append(f"{level:>4} {completed:>3}/{games:<3} {title}")
|
|
277
|
+
|
|
278
|
+
lines.extend(
|
|
279
|
+
[
|
|
280
|
+
"",
|
|
281
|
+
f"Completed: {completed_total}",
|
|
282
|
+
f"Played: {played_total}",
|
|
283
|
+
]
|
|
284
|
+
)
|
|
285
|
+
return lines
|
|
286
|
+
|
|
287
|
+
def _visible_rows(self, font: SmallFontData, layout: dict[str, float]) -> int:
|
|
288
|
+
scale = float(layout["scale"])
|
|
289
|
+
line_step = (float(font.cell_size) + 4.0) * scale
|
|
290
|
+
line_y0 = float(layout["base_y"]) + 66.0 * scale
|
|
291
|
+
panel_bottom = float(layout["panel_top"]) + (MENU_PANEL_HEIGHT * scale)
|
|
292
|
+
available = max(0.0, panel_bottom - line_y0 - 8.0 * scale)
|
|
293
|
+
return max(1, int(available // line_step))
|
|
294
|
+
|
|
295
|
+
def _draw_stats_contents(self) -> None:
|
|
296
|
+
assets = self._assets
|
|
297
|
+
if assets is None:
|
|
298
|
+
return
|
|
299
|
+
labels_tex = assets.labels
|
|
300
|
+
layout = self._content_layout()
|
|
301
|
+
base_x = layout["base_x"]
|
|
302
|
+
base_y = layout["base_y"]
|
|
303
|
+
label_x = layout["label_x"]
|
|
304
|
+
scale = layout["scale"]
|
|
305
|
+
|
|
306
|
+
font = self._ensure_small_font()
|
|
307
|
+
text_scale = 1.0 * scale
|
|
308
|
+
text_color = rl.Color(255, 255, 255, int(255 * 0.8))
|
|
309
|
+
|
|
310
|
+
if labels_tex is not None:
|
|
311
|
+
src = rl.Rectangle(
|
|
312
|
+
0.0,
|
|
313
|
+
float(MENU_LABEL_ROW_STATISTICS) * MENU_LABEL_ROW_HEIGHT,
|
|
314
|
+
MENU_LABEL_WIDTH,
|
|
315
|
+
MENU_LABEL_ROW_HEIGHT,
|
|
316
|
+
)
|
|
317
|
+
dst = rl.Rectangle(
|
|
318
|
+
base_x,
|
|
319
|
+
base_y,
|
|
320
|
+
MENU_LABEL_WIDTH * scale,
|
|
321
|
+
MENU_LABEL_ROW_HEIGHT * scale,
|
|
322
|
+
)
|
|
323
|
+
MenuView._draw_ui_quad(
|
|
324
|
+
texture=labels_tex,
|
|
325
|
+
src=src,
|
|
326
|
+
dst=dst,
|
|
327
|
+
origin=rl.Vector2(0.0, 0.0),
|
|
328
|
+
rotation_deg=0.0,
|
|
329
|
+
tint=rl.WHITE,
|
|
330
|
+
)
|
|
331
|
+
else:
|
|
332
|
+
rl.draw_text(self._title, int(base_x), int(base_y), int(24 * scale), rl.WHITE)
|
|
333
|
+
|
|
334
|
+
tabs_y = base_y + 44.0 * scale
|
|
335
|
+
x = label_x
|
|
336
|
+
for idx, label in enumerate(self._PAGES):
|
|
337
|
+
active = idx == int(self._page_index)
|
|
338
|
+
color = rl.Color(255, 255, 255, 255 if active else int(255 * 0.55))
|
|
339
|
+
draw_small_text(font, label, x, tabs_y, text_scale, color)
|
|
340
|
+
x += (len(label) * font.cell_size + 18.0) * scale
|
|
341
|
+
|
|
342
|
+
lines = self._active_page_lines()
|
|
343
|
+
rows = self._visible_rows(font, layout)
|
|
344
|
+
start = max(0, int(self._scroll_index))
|
|
345
|
+
end = min(len(lines), start + rows)
|
|
346
|
+
|
|
347
|
+
line_y = base_y + 66.0 * scale
|
|
348
|
+
line_step = (font.cell_size + 4.0) * scale
|
|
349
|
+
for line in lines[start:end]:
|
|
350
|
+
draw_small_text(font, line, label_x, line_y, text_scale, text_color)
|
|
351
|
+
line_y += line_step
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import pyray as rl
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ..game import GameState
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
SCREEN_FADE_OUT_RATE = 2.0
|
|
12
|
+
SCREEN_FADE_IN_RATE = 10.0
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _update_screen_fade(state: GameState, dt: float) -> None:
|
|
16
|
+
if state.screen_fade_ramp:
|
|
17
|
+
state.screen_fade_alpha += float(dt) * SCREEN_FADE_IN_RATE
|
|
18
|
+
else:
|
|
19
|
+
state.screen_fade_alpha -= float(dt) * SCREEN_FADE_OUT_RATE
|
|
20
|
+
if state.screen_fade_alpha < 0.0:
|
|
21
|
+
state.screen_fade_alpha = 0.0
|
|
22
|
+
elif state.screen_fade_alpha > 1.0:
|
|
23
|
+
state.screen_fade_alpha = 1.0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _draw_screen_fade(state: GameState) -> None:
|
|
27
|
+
alpha = float(state.screen_fade_alpha)
|
|
28
|
+
if alpha <= 0.0:
|
|
29
|
+
return
|
|
30
|
+
shade = int(max(0.0, min(1.0, alpha)) * 255.0)
|
|
31
|
+
rl.draw_rectangle(0, 0, int(rl.get_screen_width()), int(rl.get_screen_height()), rl.Color(0, 0, 0, shade))
|