crimsonland 0.1.0.dev1__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 +153 -0
- crimson/bonuses.py +167 -0
- crimson/camera.py +75 -0
- crimson/cli.py +377 -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 +663 -0
- crimson/gameplay.py +2450 -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 +1039 -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 +1338 -0
- crimson/sim/__init__.py +1 -0
- crimson/sim/world_defs.py +56 -0
- crimson/sim/world_state.py +421 -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 +414 -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 +433 -0
- crimson/views/player_sprite_debug.py +314 -0
- crimson/views/projectile_fx.py +608 -0
- crimson/views/projectile_render_debug.py +407 -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.dev1.dist-info/METADATA +9 -0
- crimsonland-0.1.0.dev1.dist-info/RECORD +138 -0
- crimsonland-0.1.0.dev1.dist-info/WHEEL +4 -0
- crimsonland-0.1.0.dev1.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
crimson/ui/perk_menu.py
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pyray as rl
|
|
7
|
+
|
|
8
|
+
from grim.assets import TextureLoader
|
|
9
|
+
from grim.fonts.small import SmallFontData, draw_small_text, measure_small_text_width
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
UI_BASE_WIDTH = 640.0
|
|
13
|
+
UI_BASE_HEIGHT = 480.0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
MENU_PANEL_SLICE_Y1 = 130.0
|
|
17
|
+
MENU_PANEL_SLICE_Y2 = 150.0
|
|
18
|
+
|
|
19
|
+
# Layout offsets from the classic game (perk selection screen), derived from
|
|
20
|
+
# `perk_selection_screen_update` (see analysis/ghidra + BN).
|
|
21
|
+
MENU_PANEL_ANCHOR_X = 224.0
|
|
22
|
+
MENU_PANEL_ANCHOR_Y = 40.0
|
|
23
|
+
MENU_TITLE_X = 54.0
|
|
24
|
+
MENU_TITLE_Y = 6.0
|
|
25
|
+
MENU_TITLE_W = 128.0
|
|
26
|
+
MENU_TITLE_H = 32.0
|
|
27
|
+
MENU_SPONSOR_Y = -8.0
|
|
28
|
+
MENU_SPONSOR_X_EXPERT = -26.0
|
|
29
|
+
MENU_SPONSOR_X_MASTER = -28.0
|
|
30
|
+
MENU_LIST_Y_NORMAL = 50.0
|
|
31
|
+
MENU_LIST_Y_EXPERT = 40.0
|
|
32
|
+
MENU_LIST_STEP_NORMAL = 19.0
|
|
33
|
+
MENU_LIST_STEP_EXPERT = 18.0
|
|
34
|
+
MENU_DESC_X = -12.0
|
|
35
|
+
MENU_DESC_Y_AFTER_LIST = 32.0
|
|
36
|
+
MENU_DESC_Y_EXTRA_TIGHTEN = 20.0
|
|
37
|
+
MENU_BUTTON_X = 162.0
|
|
38
|
+
MENU_BUTTON_Y = 276.0
|
|
39
|
+
MENU_DESC_RIGHT_X = 480.0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(slots=True)
|
|
43
|
+
class PerkMenuLayout:
|
|
44
|
+
# Coordinates live in the original 640x480 UI space.
|
|
45
|
+
# Matches the classic menu panel: pos (-45, 110) + offset (20, -82).
|
|
46
|
+
panel_x: float = -25.0
|
|
47
|
+
panel_y: float = 28.0
|
|
48
|
+
panel_w: float = 512.0
|
|
49
|
+
panel_h: float = 379.0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(slots=True)
|
|
53
|
+
class PerkMenuComputedLayout:
|
|
54
|
+
panel: rl.Rectangle
|
|
55
|
+
title: rl.Rectangle
|
|
56
|
+
sponsor_x: float
|
|
57
|
+
sponsor_y: float
|
|
58
|
+
list_x: float
|
|
59
|
+
list_y: float
|
|
60
|
+
list_step_y: float
|
|
61
|
+
desc: rl.Rectangle
|
|
62
|
+
cancel_x: float
|
|
63
|
+
cancel_y: float
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def ui_scale(screen_w: float, screen_h: float) -> float:
|
|
67
|
+
# Classic UI renders in backbuffer pixels; keep menu scale fixed.
|
|
68
|
+
return 1.0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def ui_origin(screen_w: float, screen_h: float, scale: float) -> tuple[float, float]:
|
|
72
|
+
return 0.0, 0.0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _menu_widescreen_y_shift(layout_w: float) -> float:
|
|
76
|
+
# ui_menu_layout_init: pos_y += (screen_width / 640.0) * 150.0 - 150.0
|
|
77
|
+
return (layout_w / UI_BASE_WIDTH) * 150.0 - 150.0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def perk_menu_compute_layout(
|
|
81
|
+
layout: PerkMenuLayout,
|
|
82
|
+
*,
|
|
83
|
+
screen_w: float,
|
|
84
|
+
origin_x: float,
|
|
85
|
+
origin_y: float,
|
|
86
|
+
scale: float,
|
|
87
|
+
choice_count: int,
|
|
88
|
+
expert_owned: bool,
|
|
89
|
+
master_owned: bool,
|
|
90
|
+
panel_slide_x: float = 0.0,
|
|
91
|
+
) -> PerkMenuComputedLayout:
|
|
92
|
+
layout_w = screen_w / scale if scale else screen_w
|
|
93
|
+
widescreen_shift_y = _menu_widescreen_y_shift(layout_w)
|
|
94
|
+
panel_x = layout.panel_x + panel_slide_x
|
|
95
|
+
panel_y = layout.panel_y + widescreen_shift_y
|
|
96
|
+
panel = rl.Rectangle(
|
|
97
|
+
origin_x + panel_x * scale,
|
|
98
|
+
origin_y + panel_y * scale,
|
|
99
|
+
layout.panel_w * scale,
|
|
100
|
+
layout.panel_h * scale,
|
|
101
|
+
)
|
|
102
|
+
anchor_x = panel.x + MENU_PANEL_ANCHOR_X * scale
|
|
103
|
+
anchor_y = panel.y + MENU_PANEL_ANCHOR_Y * scale
|
|
104
|
+
|
|
105
|
+
title = rl.Rectangle(
|
|
106
|
+
anchor_x + MENU_TITLE_X * scale,
|
|
107
|
+
anchor_y + MENU_TITLE_Y * scale,
|
|
108
|
+
MENU_TITLE_W * scale,
|
|
109
|
+
MENU_TITLE_H * scale,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
sponsor_x = anchor_x + (MENU_SPONSOR_X_MASTER if master_owned else MENU_SPONSOR_X_EXPERT) * scale
|
|
113
|
+
sponsor_y = anchor_y + MENU_SPONSOR_Y * scale
|
|
114
|
+
|
|
115
|
+
list_step_y = MENU_LIST_STEP_EXPERT if expert_owned else MENU_LIST_STEP_NORMAL
|
|
116
|
+
list_x = anchor_x
|
|
117
|
+
list_y = anchor_y + (MENU_LIST_Y_EXPERT if expert_owned else MENU_LIST_Y_NORMAL) * scale
|
|
118
|
+
|
|
119
|
+
desc_x = anchor_x + MENU_DESC_X * scale
|
|
120
|
+
desc_y = list_y + float(choice_count) * list_step_y * scale + MENU_DESC_Y_AFTER_LIST * scale
|
|
121
|
+
if choice_count > 5:
|
|
122
|
+
desc_y -= MENU_DESC_Y_EXTRA_TIGHTEN * scale
|
|
123
|
+
|
|
124
|
+
# Keep the description within the monitor screen area and above the button.
|
|
125
|
+
desc_right = panel.x + MENU_DESC_RIGHT_X * scale
|
|
126
|
+
cancel_x = anchor_x + MENU_BUTTON_X * scale
|
|
127
|
+
cancel_y = anchor_y + MENU_BUTTON_Y * scale
|
|
128
|
+
desc_w = max(0.0, float(desc_right - desc_x))
|
|
129
|
+
desc_h = max(0.0, float(cancel_y - 12.0 * scale - desc_y))
|
|
130
|
+
desc = rl.Rectangle(float(desc_x), float(desc_y), float(desc_w), float(desc_h))
|
|
131
|
+
|
|
132
|
+
return PerkMenuComputedLayout(
|
|
133
|
+
panel=panel,
|
|
134
|
+
title=title,
|
|
135
|
+
sponsor_x=float(sponsor_x),
|
|
136
|
+
sponsor_y=float(sponsor_y),
|
|
137
|
+
list_x=float(list_x),
|
|
138
|
+
list_y=float(list_y),
|
|
139
|
+
list_step_y=float(list_step_y * scale),
|
|
140
|
+
desc=desc,
|
|
141
|
+
cancel_x=float(cancel_x),
|
|
142
|
+
cancel_y=float(cancel_y),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def draw_menu_panel(texture: rl.Texture, *, dst: rl.Rectangle, tint: rl.Color = rl.WHITE) -> None:
|
|
147
|
+
scale = float(dst.width) / float(texture.width)
|
|
148
|
+
top_h = MENU_PANEL_SLICE_Y1 * scale
|
|
149
|
+
bottom_h = (float(texture.height) - MENU_PANEL_SLICE_Y2) * scale
|
|
150
|
+
mid_h = float(dst.height) - top_h - bottom_h
|
|
151
|
+
if mid_h < 0.0:
|
|
152
|
+
src = rl.Rectangle(0.0, 0.0, float(texture.width), float(texture.height))
|
|
153
|
+
rl.draw_texture_pro(texture, src, dst, rl.Vector2(0.0, 0.0), 0.0, tint)
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
src_w = float(texture.width)
|
|
157
|
+
src_h = float(texture.height)
|
|
158
|
+
|
|
159
|
+
src_top = rl.Rectangle(0.0, 0.0, src_w, MENU_PANEL_SLICE_Y1)
|
|
160
|
+
src_mid = rl.Rectangle(0.0, MENU_PANEL_SLICE_Y1, src_w, MENU_PANEL_SLICE_Y2 - MENU_PANEL_SLICE_Y1)
|
|
161
|
+
src_bot = rl.Rectangle(0.0, MENU_PANEL_SLICE_Y2, src_w, src_h - MENU_PANEL_SLICE_Y2)
|
|
162
|
+
|
|
163
|
+
dst_top = rl.Rectangle(float(dst.x), float(dst.y), float(dst.width), top_h)
|
|
164
|
+
dst_mid = rl.Rectangle(float(dst.x), float(dst.y) + top_h, float(dst.width), mid_h)
|
|
165
|
+
dst_bot = rl.Rectangle(float(dst.x), float(dst.y) + top_h + mid_h, float(dst.width), bottom_h)
|
|
166
|
+
|
|
167
|
+
origin = rl.Vector2(0.0, 0.0)
|
|
168
|
+
rl.draw_texture_pro(texture, src_top, dst_top, origin, 0.0, tint)
|
|
169
|
+
rl.draw_texture_pro(texture, src_mid, dst_mid, origin, 0.0, tint)
|
|
170
|
+
rl.draw_texture_pro(texture, src_bot, dst_bot, origin, 0.0, tint)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@dataclass(slots=True)
|
|
174
|
+
class PerkMenuAssets:
|
|
175
|
+
menu_panel: rl.Texture | None
|
|
176
|
+
title_pick_perk: rl.Texture | None
|
|
177
|
+
title_level_up: rl.Texture | None
|
|
178
|
+
menu_item: rl.Texture | None
|
|
179
|
+
button_sm: rl.Texture | None
|
|
180
|
+
button_md: rl.Texture | None
|
|
181
|
+
cursor: rl.Texture | None
|
|
182
|
+
aim: rl.Texture | None
|
|
183
|
+
missing: list[str] = field(default_factory=list)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def load_perk_menu_assets(assets_root: Path) -> PerkMenuAssets:
|
|
187
|
+
loader = TextureLoader.from_assets_root(assets_root)
|
|
188
|
+
return PerkMenuAssets(
|
|
189
|
+
menu_panel=loader.get(name="ui_menuPanel", paq_rel="ui/ui_menuPanel.jaz", fs_rel="ui/ui_menuPanel.png"),
|
|
190
|
+
title_pick_perk=loader.get(
|
|
191
|
+
name="ui_textPickAPerk",
|
|
192
|
+
paq_rel="ui/ui_textPickAPerk.jaz",
|
|
193
|
+
fs_rel="ui/ui_textPickAPerk.png",
|
|
194
|
+
),
|
|
195
|
+
title_level_up=loader.get(
|
|
196
|
+
name="ui_textLevelUp",
|
|
197
|
+
paq_rel="ui/ui_textLevelUp.jaz",
|
|
198
|
+
fs_rel="ui/ui_textLevelUp.png",
|
|
199
|
+
),
|
|
200
|
+
menu_item=loader.get(name="ui_menuItem", paq_rel="ui/ui_menuItem.jaz", fs_rel="ui/ui_menuItem.png"),
|
|
201
|
+
button_sm=loader.get(name="ui_buttonSm", paq_rel="ui/ui_button_82x32.jaz", fs_rel="ui/ui_button_82x32.png"),
|
|
202
|
+
button_md=loader.get(
|
|
203
|
+
name="ui_buttonMd",
|
|
204
|
+
paq_rel="ui/ui_button_145x32.jaz",
|
|
205
|
+
fs_rel="ui/ui_button_145x32.png",
|
|
206
|
+
),
|
|
207
|
+
cursor=loader.get(name="ui_cursor", paq_rel="ui/ui_cursor.jaz", fs_rel="ui/ui_cursor.png"),
|
|
208
|
+
aim=loader.get(name="ui_aim", paq_rel="ui/ui_aim.jaz", fs_rel="ui/ui_aim.png"),
|
|
209
|
+
missing=loader.missing,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _ui_text_width(font: SmallFontData | None, text: str, scale: float) -> float:
|
|
214
|
+
if font is None:
|
|
215
|
+
return float(rl.measure_text(text, int(20 * scale)))
|
|
216
|
+
return float(measure_small_text_width(font, text, scale))
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def draw_ui_text(
|
|
220
|
+
font: SmallFontData | None,
|
|
221
|
+
text: str,
|
|
222
|
+
x: float,
|
|
223
|
+
y: float,
|
|
224
|
+
*,
|
|
225
|
+
scale: float,
|
|
226
|
+
color: rl.Color,
|
|
227
|
+
) -> None:
|
|
228
|
+
if font is not None:
|
|
229
|
+
draw_small_text(font, text, x, y, scale, color)
|
|
230
|
+
else:
|
|
231
|
+
rl.draw_text(text, int(x), int(y), int(20 * scale), color)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def wrap_ui_text(font: SmallFontData | None, text: str, *, max_width: float, scale: float) -> list[str]:
|
|
235
|
+
lines: list[str] = []
|
|
236
|
+
for raw in text.splitlines() or [""]:
|
|
237
|
+
para = raw.strip()
|
|
238
|
+
if not para:
|
|
239
|
+
lines.append("")
|
|
240
|
+
continue
|
|
241
|
+
current = ""
|
|
242
|
+
for word in para.split():
|
|
243
|
+
candidate = word if not current else f"{current} {word}"
|
|
244
|
+
if current and _ui_text_width(font, candidate, scale) > max_width:
|
|
245
|
+
lines.append(current)
|
|
246
|
+
current = word
|
|
247
|
+
else:
|
|
248
|
+
current = candidate
|
|
249
|
+
if current:
|
|
250
|
+
lines.append(current)
|
|
251
|
+
return lines
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
MENU_ITEM_RGB = (0x46, 0xB4, 0xF0) # from ui_menu_item_update: rgb(70, 180, 240)
|
|
255
|
+
MENU_ITEM_ALPHA_IDLE = 0.6
|
|
256
|
+
MENU_ITEM_ALPHA_HOVER = 1.0
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def menu_item_hit_rect(font: SmallFontData | None, label: str, *, x: float, y: float, scale: float) -> rl.Rectangle:
|
|
260
|
+
width = _ui_text_width(font, label, scale)
|
|
261
|
+
height = 16.0 * scale
|
|
262
|
+
return rl.Rectangle(float(x), float(y), float(width), float(height))
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def draw_menu_item(
|
|
266
|
+
font: SmallFontData | None,
|
|
267
|
+
label: str,
|
|
268
|
+
*,
|
|
269
|
+
x: float,
|
|
270
|
+
y: float,
|
|
271
|
+
scale: float,
|
|
272
|
+
hovered: bool,
|
|
273
|
+
) -> float:
|
|
274
|
+
alpha = MENU_ITEM_ALPHA_HOVER if hovered else MENU_ITEM_ALPHA_IDLE
|
|
275
|
+
r, g, b = MENU_ITEM_RGB
|
|
276
|
+
color = rl.Color(int(r), int(g), int(b), int(255 * alpha))
|
|
277
|
+
draw_ui_text(font, label, x, y, scale=scale, color=color)
|
|
278
|
+
width = _ui_text_width(font, label, scale)
|
|
279
|
+
line_y = y + 13.0 * scale
|
|
280
|
+
rl.draw_line(int(x), int(line_y), int(x + width), int(line_y), color)
|
|
281
|
+
return float(width)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@dataclass(slots=True)
|
|
285
|
+
class UiButtonState:
|
|
286
|
+
label: str
|
|
287
|
+
enabled: bool = True
|
|
288
|
+
hovered: bool = False
|
|
289
|
+
activated: bool = False
|
|
290
|
+
hover_t: int = 0 # 0..1000
|
|
291
|
+
press_t: int = 0 # 0..1000
|
|
292
|
+
alpha: float = 1.0
|
|
293
|
+
force_wide: bool = False
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def button_width(font: SmallFontData | None, label: str, *, scale: float, force_wide: bool) -> float:
|
|
297
|
+
text_w = _ui_text_width(font, label, scale)
|
|
298
|
+
if force_wide:
|
|
299
|
+
return 145.0 * scale
|
|
300
|
+
if text_w < 40.0 * scale:
|
|
301
|
+
return 82.0 * scale
|
|
302
|
+
return 145.0 * scale
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def button_hit_rect(*, x: float, y: float, width: float) -> rl.Rectangle:
|
|
306
|
+
# Mirrors ui_button_update: y is offset by +2, hit height is 0x1c (28).
|
|
307
|
+
return rl.Rectangle(float(x), float(y + 2.0), float(width), float(28.0))
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def button_update(
|
|
311
|
+
state: UiButtonState,
|
|
312
|
+
*,
|
|
313
|
+
x: float,
|
|
314
|
+
y: float,
|
|
315
|
+
width: float,
|
|
316
|
+
dt_ms: float,
|
|
317
|
+
mouse: rl.Vector2,
|
|
318
|
+
click: bool,
|
|
319
|
+
) -> bool:
|
|
320
|
+
if not state.enabled:
|
|
321
|
+
state.hovered = False
|
|
322
|
+
else:
|
|
323
|
+
state.hovered = rl.check_collision_point_rec(mouse, button_hit_rect(x=x, y=y, width=width))
|
|
324
|
+
|
|
325
|
+
delta = 6 if (state.enabled and state.hovered) else -4
|
|
326
|
+
state.hover_t = int(_clamp(float(state.hover_t + int(dt_ms) * delta), 0.0, 1000.0))
|
|
327
|
+
|
|
328
|
+
if state.press_t > 0:
|
|
329
|
+
state.press_t = int(_clamp(float(state.press_t - int(dt_ms) * 6), 0.0, 1000.0))
|
|
330
|
+
|
|
331
|
+
state.activated = bool(state.enabled and state.hovered and click)
|
|
332
|
+
if state.activated:
|
|
333
|
+
state.press_t = 1000
|
|
334
|
+
return state.activated
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _clamp(value: float, lo: float, hi: float) -> float:
|
|
338
|
+
if value < lo:
|
|
339
|
+
return lo
|
|
340
|
+
if value > hi:
|
|
341
|
+
return hi
|
|
342
|
+
return value
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def button_draw(
|
|
346
|
+
assets: PerkMenuAssets,
|
|
347
|
+
font: SmallFontData | None,
|
|
348
|
+
state: UiButtonState,
|
|
349
|
+
*,
|
|
350
|
+
x: float,
|
|
351
|
+
y: float,
|
|
352
|
+
width: float,
|
|
353
|
+
scale: float,
|
|
354
|
+
) -> None:
|
|
355
|
+
texture = assets.button_md if width > 120.0 * scale else assets.button_sm
|
|
356
|
+
if texture is None:
|
|
357
|
+
return
|
|
358
|
+
|
|
359
|
+
if state.hover_t > 0:
|
|
360
|
+
alpha = 0.5
|
|
361
|
+
if state.press_t > 0:
|
|
362
|
+
alpha = min(1.0, 0.5 + (float(state.press_t) * 0.0005))
|
|
363
|
+
hl = rl.Color(255, 255, 255, int(255 * alpha * 0.25 * state.alpha))
|
|
364
|
+
rl.draw_rectangle(int(x + 12.0 * scale), int(y + 5.0 * scale), int(width - 24.0 * scale), int(22.0 * scale), hl)
|
|
365
|
+
|
|
366
|
+
tint_a = state.alpha if state.hovered else state.alpha * 0.7
|
|
367
|
+
tint = rl.Color(255, 255, 255, int(255 * _clamp(tint_a, 0.0, 1.0)))
|
|
368
|
+
|
|
369
|
+
src = rl.Rectangle(0.0, 0.0, float(texture.width), float(texture.height))
|
|
370
|
+
dst = rl.Rectangle(float(x), float(y), float(width), float(32.0 * scale))
|
|
371
|
+
rl.draw_texture_pro(texture, src, dst, rl.Vector2(0.0, 0.0), 0.0, tint)
|
|
372
|
+
|
|
373
|
+
text_w = _ui_text_width(font, state.label, scale)
|
|
374
|
+
text_x = x + width * 0.5 - text_w * 0.5 + 1.0 * scale
|
|
375
|
+
text_y = y + 10.0 * scale
|
|
376
|
+
draw_ui_text(font, state.label, text_x, text_y, scale=scale, color=tint)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def cursor_draw(assets: PerkMenuAssets, *, mouse: rl.Vector2, scale: float, alpha: float = 1.0) -> None:
|
|
380
|
+
tex = assets.cursor
|
|
381
|
+
if tex is None:
|
|
382
|
+
return
|
|
383
|
+
a = int(255 * _clamp(alpha, 0.0, 1.0))
|
|
384
|
+
tint = rl.Color(255, 255, 255, a)
|
|
385
|
+
size = 32.0 * scale
|
|
386
|
+
src = rl.Rectangle(0.0, 0.0, float(tex.width), float(tex.height))
|
|
387
|
+
dst = rl.Rectangle(float(mouse.x), float(mouse.y), size, size)
|
|
388
|
+
rl.draw_texture_pro(tex, src, dst, rl.Vector2(0.0, 0.0), 0.0, tint)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .registry import all_views, view_by_name
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _register_builtin_views() -> None:
|
|
7
|
+
from . import empty as _empty # noqa: F401
|
|
8
|
+
from . import fonts as _fonts # noqa: F401
|
|
9
|
+
from . import animations as _animations # noqa: F401
|
|
10
|
+
from . import sprites as _sprites # noqa: F401
|
|
11
|
+
from . import terrain as _terrain # noqa: F401
|
|
12
|
+
from . import ground as _ground # noqa: F401
|
|
13
|
+
from . import projectiles as _projectiles # noqa: F401
|
|
14
|
+
from . import projectile_fx as _projectile_fx # noqa: F401
|
|
15
|
+
from . import bonuses as _bonuses # noqa: F401
|
|
16
|
+
from . import perks as _perks # noqa: F401
|
|
17
|
+
from . import perk_menu_debug as _perk_menu_debug # noqa: F401
|
|
18
|
+
from . import wicons as _wicons # noqa: F401
|
|
19
|
+
from . import ui as _ui # noqa: F401
|
|
20
|
+
from . import particles as _particles # noqa: F401
|
|
21
|
+
from . import spawn_plan as _spawn_plan # noqa: F401
|
|
22
|
+
from . import player as _player # noqa: F401
|
|
23
|
+
from . import survival as _survival # noqa: F401
|
|
24
|
+
from . import rush as _rush # noqa: F401
|
|
25
|
+
from . import game_over as _game_over # noqa: F401
|
|
26
|
+
from . import small_font_debug as _small_font_debug # noqa: F401
|
|
27
|
+
from . import camera_debug as _camera_debug # noqa: F401
|
|
28
|
+
from . import camera_shake as _camera_shake # noqa: F401
|
|
29
|
+
from . import decals_debug as _decals_debug # noqa: F401
|
|
30
|
+
from . import corpse_stamp_debug as _corpse_stamp_debug # noqa: F401
|
|
31
|
+
from . import player_sprite_debug as _player_sprite_debug # noqa: F401
|
|
32
|
+
from . import aim_debug as _aim_debug # noqa: F401
|
|
33
|
+
from . import projectile_render_debug as _projectile_render_debug # noqa: F401
|
|
34
|
+
from . import arsenal_debug as _arsenal_debug # noqa: F401
|
|
35
|
+
from . import lighting_debug as _lighting_debug # noqa: F401
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
_register_builtin_views()
|
|
39
|
+
|
|
40
|
+
__all__ = ["all_views", "view_by_name"]
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
import pyray as rl
|
|
6
|
+
|
|
7
|
+
from grim.config import ensure_crimson_cfg
|
|
8
|
+
from grim.fonts.small import SmallFontData, draw_small_text, load_small_font
|
|
9
|
+
from grim.view import ViewContext
|
|
10
|
+
|
|
11
|
+
from ..game_world import GameWorld
|
|
12
|
+
from ..gameplay import PlayerInput
|
|
13
|
+
from ..paths import default_runtime_dir
|
|
14
|
+
from ..ui.cursor import draw_cursor_glow
|
|
15
|
+
from .registry import register_view
|
|
16
|
+
|
|
17
|
+
WORLD_SIZE = 1024.0
|
|
18
|
+
|
|
19
|
+
UI_TEXT_SCALE = 1.0
|
|
20
|
+
UI_TEXT_COLOR = rl.Color(220, 220, 220, 255)
|
|
21
|
+
UI_HINT_COLOR = rl.Color(140, 140, 140, 255)
|
|
22
|
+
UI_ERROR_COLOR = rl.Color(240, 80, 80, 255)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _clamp(value: float, lo: float, hi: float) -> float:
|
|
26
|
+
if value < lo:
|
|
27
|
+
return lo
|
|
28
|
+
if value > hi:
|
|
29
|
+
return hi
|
|
30
|
+
return value
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AimDebugView:
|
|
34
|
+
def __init__(self, ctx: ViewContext) -> None:
|
|
35
|
+
self._assets_root = ctx.assets_dir
|
|
36
|
+
self._missing_assets: list[str] = []
|
|
37
|
+
self._small: SmallFontData | None = None
|
|
38
|
+
self._world = GameWorld(
|
|
39
|
+
assets_dir=ctx.assets_dir,
|
|
40
|
+
world_size=WORLD_SIZE,
|
|
41
|
+
demo_mode_active=False,
|
|
42
|
+
difficulty_level=0,
|
|
43
|
+
hardcore=False,
|
|
44
|
+
)
|
|
45
|
+
self._player = self._world.players[0] if self._world.players else None
|
|
46
|
+
|
|
47
|
+
self.close_requested = False
|
|
48
|
+
|
|
49
|
+
self._ui_mouse_x = 0.0
|
|
50
|
+
self._ui_mouse_y = 0.0
|
|
51
|
+
self._cursor_pulse_time = 0.0
|
|
52
|
+
|
|
53
|
+
self._simulate = False
|
|
54
|
+
self._draw_world = True
|
|
55
|
+
self._draw_world_aim = True
|
|
56
|
+
self._show_cursor_glow = False
|
|
57
|
+
self._draw_expected_overlay = True
|
|
58
|
+
self._draw_test_circle = True
|
|
59
|
+
|
|
60
|
+
self._force_heat = True
|
|
61
|
+
self._forced_heat = 0.18
|
|
62
|
+
self._test_circle_radius = 96.0
|
|
63
|
+
|
|
64
|
+
def _ui_line_height(self, scale: float = UI_TEXT_SCALE) -> int:
|
|
65
|
+
if self._small is not None:
|
|
66
|
+
return int(self._small.cell_size * scale)
|
|
67
|
+
return int(20 * scale)
|
|
68
|
+
|
|
69
|
+
def _draw_ui_text(
|
|
70
|
+
self,
|
|
71
|
+
text: str,
|
|
72
|
+
x: float,
|
|
73
|
+
y: float,
|
|
74
|
+
color: rl.Color,
|
|
75
|
+
scale: float = UI_TEXT_SCALE,
|
|
76
|
+
) -> None:
|
|
77
|
+
if self._small is not None:
|
|
78
|
+
draw_small_text(self._small, text, x, y, scale, color)
|
|
79
|
+
else:
|
|
80
|
+
rl.draw_text(text, int(x), int(y), int(20 * scale), color)
|
|
81
|
+
|
|
82
|
+
def _update_ui_mouse(self) -> None:
|
|
83
|
+
mouse = rl.get_mouse_position()
|
|
84
|
+
screen_w = float(rl.get_screen_width())
|
|
85
|
+
screen_h = float(rl.get_screen_height())
|
|
86
|
+
self._ui_mouse_x = _clamp(float(mouse.x), 0.0, max(0.0, screen_w - 1.0))
|
|
87
|
+
self._ui_mouse_y = _clamp(float(mouse.y), 0.0, max(0.0, screen_h - 1.0))
|
|
88
|
+
|
|
89
|
+
def _draw_cursor_glow(self, *, x: float, y: float) -> None:
|
|
90
|
+
draw_cursor_glow(self._world.particles_texture, x=x, y=y)
|
|
91
|
+
|
|
92
|
+
def _handle_debug_input(self) -> None:
|
|
93
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
94
|
+
self.close_requested = True
|
|
95
|
+
|
|
96
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_SPACE):
|
|
97
|
+
self._simulate = not self._simulate
|
|
98
|
+
|
|
99
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_ONE):
|
|
100
|
+
self._draw_world = not self._draw_world
|
|
101
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_TWO):
|
|
102
|
+
self._draw_world_aim = not self._draw_world_aim
|
|
103
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_THREE):
|
|
104
|
+
self._draw_expected_overlay = not self._draw_expected_overlay
|
|
105
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_FOUR):
|
|
106
|
+
self._show_cursor_glow = not self._show_cursor_glow
|
|
107
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_FIVE):
|
|
108
|
+
self._draw_test_circle = not self._draw_test_circle
|
|
109
|
+
|
|
110
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_H):
|
|
111
|
+
self._force_heat = not self._force_heat
|
|
112
|
+
|
|
113
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_LEFT_BRACKET):
|
|
114
|
+
self._forced_heat = max(0.0, self._forced_heat - 0.02)
|
|
115
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_RIGHT_BRACKET):
|
|
116
|
+
self._forced_heat = min(0.48, self._forced_heat + 0.02)
|
|
117
|
+
|
|
118
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_MINUS):
|
|
119
|
+
self._test_circle_radius = max(8.0, self._test_circle_radius - 8.0)
|
|
120
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_EQUAL):
|
|
121
|
+
self._test_circle_radius = min(512.0, self._test_circle_radius + 8.0)
|
|
122
|
+
|
|
123
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_R):
|
|
124
|
+
self._world.reset(seed=0xBEEF, player_count=1)
|
|
125
|
+
self._player = self._world.players[0] if self._world.players else None
|
|
126
|
+
self._world.update_camera(0.0)
|
|
127
|
+
|
|
128
|
+
def open(self) -> None:
|
|
129
|
+
self._missing_assets.clear()
|
|
130
|
+
try:
|
|
131
|
+
self._small = load_small_font(self._assets_root, self._missing_assets)
|
|
132
|
+
except Exception:
|
|
133
|
+
self._small = None
|
|
134
|
+
|
|
135
|
+
runtime_dir = default_runtime_dir()
|
|
136
|
+
if runtime_dir.is_dir():
|
|
137
|
+
try:
|
|
138
|
+
self._world.config = ensure_crimson_cfg(runtime_dir)
|
|
139
|
+
except Exception:
|
|
140
|
+
self._world.config = None
|
|
141
|
+
else:
|
|
142
|
+
self._world.config = None
|
|
143
|
+
|
|
144
|
+
self._world.reset(seed=0xBEEF, player_count=1)
|
|
145
|
+
self._player = self._world.players[0] if self._world.players else None
|
|
146
|
+
self._world.open()
|
|
147
|
+
self._world.update_camera(0.0)
|
|
148
|
+
self._ui_mouse_x = float(rl.get_screen_width()) * 0.5
|
|
149
|
+
self._ui_mouse_y = float(rl.get_screen_height()) * 0.5
|
|
150
|
+
self._cursor_pulse_time = 0.0
|
|
151
|
+
|
|
152
|
+
def close(self) -> None:
|
|
153
|
+
if self._small is not None:
|
|
154
|
+
rl.unload_texture(self._small.texture)
|
|
155
|
+
self._small = None
|
|
156
|
+
self._world.close()
|
|
157
|
+
|
|
158
|
+
def update(self, dt: float) -> None:
|
|
159
|
+
dt_frame = float(dt)
|
|
160
|
+
self._update_ui_mouse()
|
|
161
|
+
self._handle_debug_input()
|
|
162
|
+
self._cursor_pulse_time += dt_frame * 1.1
|
|
163
|
+
|
|
164
|
+
aim_x, aim_y = self._world.screen_to_world(self._ui_mouse_x, self._ui_mouse_y)
|
|
165
|
+
if self._player is not None:
|
|
166
|
+
self._player.aim_x = float(aim_x)
|
|
167
|
+
self._player.aim_y = float(aim_y)
|
|
168
|
+
if self._force_heat:
|
|
169
|
+
self._player.spread_heat = float(self._forced_heat)
|
|
170
|
+
|
|
171
|
+
move_x = 0.0
|
|
172
|
+
move_y = 0.0
|
|
173
|
+
if rl.is_key_down(rl.KeyboardKey.KEY_A):
|
|
174
|
+
move_x -= 1.0
|
|
175
|
+
if rl.is_key_down(rl.KeyboardKey.KEY_D):
|
|
176
|
+
move_x += 1.0
|
|
177
|
+
if rl.is_key_down(rl.KeyboardKey.KEY_W):
|
|
178
|
+
move_y -= 1.0
|
|
179
|
+
if rl.is_key_down(rl.KeyboardKey.KEY_S):
|
|
180
|
+
move_y += 1.0
|
|
181
|
+
|
|
182
|
+
dt_world = dt_frame if self._simulate else 0.0
|
|
183
|
+
self._world.update(
|
|
184
|
+
dt_world,
|
|
185
|
+
inputs=[
|
|
186
|
+
PlayerInput(
|
|
187
|
+
move_x=move_x,
|
|
188
|
+
move_y=move_y,
|
|
189
|
+
aim_x=float(aim_x),
|
|
190
|
+
aim_y=float(aim_y),
|
|
191
|
+
fire_down=False,
|
|
192
|
+
fire_pressed=False,
|
|
193
|
+
reload_pressed=False,
|
|
194
|
+
)
|
|
195
|
+
],
|
|
196
|
+
auto_pick_perks=False,
|
|
197
|
+
perk_progression_enabled=False,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if self._player is not None and self._force_heat:
|
|
201
|
+
self._player.spread_heat = float(self._forced_heat)
|
|
202
|
+
|
|
203
|
+
def draw(self) -> None:
|
|
204
|
+
if self._draw_world:
|
|
205
|
+
self._world.draw(draw_aim_indicators=self._draw_world_aim)
|
|
206
|
+
else:
|
|
207
|
+
rl.clear_background(rl.Color(10, 10, 12, 255))
|
|
208
|
+
|
|
209
|
+
mouse_x = float(self._ui_mouse_x)
|
|
210
|
+
mouse_y = float(self._ui_mouse_y)
|
|
211
|
+
|
|
212
|
+
if self._draw_test_circle:
|
|
213
|
+
cx = float(rl.get_screen_width()) * 0.5
|
|
214
|
+
cy = float(rl.get_screen_height()) * 0.5
|
|
215
|
+
self._world._draw_aim_circle(x=cx, y=cy, radius=float(self._test_circle_radius))
|
|
216
|
+
rl.draw_circle_lines(int(cx), int(cy), int(max(1.0, self._test_circle_radius)), rl.Color(255, 80, 80, 220))
|
|
217
|
+
|
|
218
|
+
if self._show_cursor_glow:
|
|
219
|
+
self._draw_cursor_glow(x=mouse_x, y=mouse_y)
|
|
220
|
+
|
|
221
|
+
mouse_world_x, mouse_world_y = self._world.screen_to_world(mouse_x, mouse_y)
|
|
222
|
+
mouse_back_x, mouse_back_y = self._world.world_to_screen(float(mouse_world_x), float(mouse_world_y))
|
|
223
|
+
|
|
224
|
+
if self._draw_expected_overlay and self._player is not None:
|
|
225
|
+
dist = math.hypot(float(self._player.aim_x) - float(self._player.pos_x), float(self._player.aim_y) - float(self._player.pos_y))
|
|
226
|
+
radius = max(6.0, dist * float(self._player.spread_heat) * 0.5)
|
|
227
|
+
cam_x, cam_y, scale_x, scale_y = self._world._world_params()
|
|
228
|
+
scale = (scale_x + scale_y) * 0.5
|
|
229
|
+
screen_radius = max(1.0, radius * scale)
|
|
230
|
+
aim_screen_x, aim_screen_y = self._world.world_to_screen(float(self._player.aim_x), float(self._player.aim_y))
|
|
231
|
+
|
|
232
|
+
rl.draw_circle_lines(
|
|
233
|
+
int(aim_screen_x),
|
|
234
|
+
int(aim_screen_y),
|
|
235
|
+
int(max(1.0, screen_radius)),
|
|
236
|
+
rl.Color(80, 220, 120, 240),
|
|
237
|
+
)
|
|
238
|
+
rl.draw_line(
|
|
239
|
+
int(mouse_x),
|
|
240
|
+
int(mouse_y),
|
|
241
|
+
int(aim_screen_x),
|
|
242
|
+
int(aim_screen_y),
|
|
243
|
+
rl.Color(80, 220, 120, 200),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
lines = [
|
|
247
|
+
"Aim debug view",
|
|
248
|
+
"SPACE simulate world update",
|
|
249
|
+
"1 world 2 aim-indicators 3 expected overlay 4 cursor glow 5 test circle",
|
|
250
|
+
f"H force_heat={self._force_heat} forced_heat={self._forced_heat:.2f} [ ] adjust",
|
|
251
|
+
f"test_circle_radius={self._test_circle_radius:.0f} -/+ adjust",
|
|
252
|
+
(
|
|
253
|
+
f"mouse=({mouse_x:.1f},{mouse_y:.1f}) -> "
|
|
254
|
+
f"world=({mouse_world_x:.1f},{mouse_world_y:.1f}) -> "
|
|
255
|
+
f"screen=({mouse_back_x:.1f},{mouse_back_y:.1f})"
|
|
256
|
+
),
|
|
257
|
+
f"player_aim_world=({float(self._player.aim_x):.1f},{float(self._player.aim_y):.1f}) "
|
|
258
|
+
f"player_aim_screen=({aim_screen_x:.1f},{aim_screen_y:.1f})",
|
|
259
|
+
f"player=({float(self._player.pos_x):.1f},{float(self._player.pos_y):.1f}) dist={dist:.1f}",
|
|
260
|
+
f"spread_heat={float(self._player.spread_heat):.3f} r_world={radius:.2f} r_screen={screen_radius:.2f}",
|
|
261
|
+
f"cam=({cam_x:.2f},{cam_y:.2f}) scale=({scale_x:.3f},{scale_y:.3f}) demo_mode={self._world.demo_mode_active}",
|
|
262
|
+
f"bulletTrail={'yes' if self._world.bullet_trail_texture is not None else 'no'} "
|
|
263
|
+
f"particles={'yes' if self._world.particles_texture is not None else 'no'}",
|
|
264
|
+
]
|
|
265
|
+
x0 = 16.0
|
|
266
|
+
y0 = 16.0
|
|
267
|
+
lh = float(self._ui_line_height())
|
|
268
|
+
for idx, line in enumerate(lines):
|
|
269
|
+
self._draw_ui_text(line, x0, y0 + lh * float(idx), UI_TEXT_COLOR if idx < 6 else UI_HINT_COLOR)
|
|
270
|
+
elif self._draw_expected_overlay and self._player is None:
|
|
271
|
+
self._draw_ui_text("Aim debug view: missing player", 16.0, 16.0, UI_ERROR_COLOR)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@register_view("aim-debug", "Aim indicator debug")
|
|
275
|
+
def _create_aim_debug_view(*, ctx: ViewContext) -> AimDebugView:
|
|
276
|
+
return AimDebugView(ctx)
|