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
crimson/input_codes.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
|
|
5
|
+
import pyray as rl
|
|
6
|
+
|
|
7
|
+
from grim.config import CrimsonConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
INPUT_CODE_UNBOUND = 0x17E
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _dik_to_rl_key(dik_code: int) -> int | None:
|
|
14
|
+
dik_code = int(dik_code)
|
|
15
|
+
return {
|
|
16
|
+
0x01: int(rl.KeyboardKey.KEY_ESCAPE),
|
|
17
|
+
0x0F: int(rl.KeyboardKey.KEY_TAB),
|
|
18
|
+
0x10: int(rl.KeyboardKey.KEY_Q),
|
|
19
|
+
0x11: int(rl.KeyboardKey.KEY_W),
|
|
20
|
+
0x12: int(rl.KeyboardKey.KEY_E),
|
|
21
|
+
0x13: int(rl.KeyboardKey.KEY_R),
|
|
22
|
+
0x1C: int(rl.KeyboardKey.KEY_ENTER),
|
|
23
|
+
0x1D: int(rl.KeyboardKey.KEY_LEFT_CONTROL),
|
|
24
|
+
0x1E: int(rl.KeyboardKey.KEY_A),
|
|
25
|
+
0x1F: int(rl.KeyboardKey.KEY_S),
|
|
26
|
+
0x20: int(rl.KeyboardKey.KEY_D),
|
|
27
|
+
0x2A: int(rl.KeyboardKey.KEY_LEFT_SHIFT),
|
|
28
|
+
0x36: int(rl.KeyboardKey.KEY_RIGHT_SHIFT),
|
|
29
|
+
0x38: int(rl.KeyboardKey.KEY_LEFT_ALT),
|
|
30
|
+
0x39: int(rl.KeyboardKey.KEY_SPACE),
|
|
31
|
+
0x9D: int(rl.KeyboardKey.KEY_RIGHT_CONTROL),
|
|
32
|
+
0xC8: int(rl.KeyboardKey.KEY_UP),
|
|
33
|
+
0xC9: int(rl.KeyboardKey.KEY_PAGE_UP),
|
|
34
|
+
0xCB: int(rl.KeyboardKey.KEY_LEFT),
|
|
35
|
+
0xCD: int(rl.KeyboardKey.KEY_RIGHT),
|
|
36
|
+
0xD0: int(rl.KeyboardKey.KEY_DOWN),
|
|
37
|
+
0xD1: int(rl.KeyboardKey.KEY_PAGE_DOWN),
|
|
38
|
+
0xD3: int(rl.KeyboardKey.KEY_DELETE),
|
|
39
|
+
}.get(dik_code)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _mouse_button_for_code(key_code: int) -> int | None:
|
|
43
|
+
key_code = int(key_code)
|
|
44
|
+
return {
|
|
45
|
+
0x100: int(rl.MouseButton.MOUSE_BUTTON_LEFT),
|
|
46
|
+
0x101: int(rl.MouseButton.MOUSE_BUTTON_RIGHT),
|
|
47
|
+
0x102: int(rl.MouseButton.MOUSE_BUTTON_MIDDLE),
|
|
48
|
+
0x103: int(rl.MouseButton.MOUSE_BUTTON_SIDE),
|
|
49
|
+
0x104: int(rl.MouseButton.MOUSE_BUTTON_EXTRA),
|
|
50
|
+
}.get(key_code)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def input_code_name(key_code: int) -> str:
|
|
54
|
+
key_code = int(key_code)
|
|
55
|
+
if key_code == INPUT_CODE_UNBOUND:
|
|
56
|
+
return "unbound"
|
|
57
|
+
if key_code == 0x100:
|
|
58
|
+
return "Mouse1"
|
|
59
|
+
if key_code == 0x101:
|
|
60
|
+
return "Mouse2"
|
|
61
|
+
if key_code == 0x102:
|
|
62
|
+
return "Mouse3"
|
|
63
|
+
if key_code == 0x103:
|
|
64
|
+
return "Mouse4"
|
|
65
|
+
if key_code == 0x104:
|
|
66
|
+
return "Mouse5"
|
|
67
|
+
if key_code == 0x109:
|
|
68
|
+
return "MWheelUp"
|
|
69
|
+
if key_code == 0x10A:
|
|
70
|
+
return "MWheelDown"
|
|
71
|
+
if key_code == 0x13F:
|
|
72
|
+
return "JoyAxisX"
|
|
73
|
+
if key_code == 0x140:
|
|
74
|
+
return "JoyAxisY"
|
|
75
|
+
if key_code == 0x141:
|
|
76
|
+
return "JoyAxisZ"
|
|
77
|
+
if key_code == 0x153:
|
|
78
|
+
return "JoyAxisR"
|
|
79
|
+
|
|
80
|
+
if key_code < 0x100:
|
|
81
|
+
name = {
|
|
82
|
+
0x01: "Escape",
|
|
83
|
+
0x0F: "Tab",
|
|
84
|
+
0x10: "Q",
|
|
85
|
+
0x11: "W",
|
|
86
|
+
0x12: "E",
|
|
87
|
+
0x13: "R",
|
|
88
|
+
0x1C: "Enter",
|
|
89
|
+
0x1D: "LControl",
|
|
90
|
+
0x1E: "A",
|
|
91
|
+
0x1F: "S",
|
|
92
|
+
0x20: "D",
|
|
93
|
+
0x2A: "LShift",
|
|
94
|
+
0x36: "RShift",
|
|
95
|
+
0x38: "LAlt",
|
|
96
|
+
0x39: "Space",
|
|
97
|
+
0x9D: "RControl",
|
|
98
|
+
0xC8: "Up",
|
|
99
|
+
0xC9: "PageUp",
|
|
100
|
+
0xCB: "Left",
|
|
101
|
+
0xCD: "Right",
|
|
102
|
+
0xD0: "Down",
|
|
103
|
+
0xD1: "PageDown",
|
|
104
|
+
0xD3: "Delete",
|
|
105
|
+
}.get(key_code)
|
|
106
|
+
if name is not None:
|
|
107
|
+
return name
|
|
108
|
+
return f"DIK_{key_code:02X}"
|
|
109
|
+
|
|
110
|
+
return f"KEY_{key_code:04X}"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def input_code_is_down(key_code: int) -> bool:
|
|
114
|
+
key_code = int(key_code)
|
|
115
|
+
if key_code == INPUT_CODE_UNBOUND:
|
|
116
|
+
return False
|
|
117
|
+
mouse_button = _mouse_button_for_code(key_code)
|
|
118
|
+
if mouse_button is not None:
|
|
119
|
+
return bool(rl.is_mouse_button_down(mouse_button))
|
|
120
|
+
if key_code < 0x100:
|
|
121
|
+
rl_key = _dik_to_rl_key(key_code)
|
|
122
|
+
if rl_key is None:
|
|
123
|
+
return False
|
|
124
|
+
return bool(rl.is_key_down(rl_key))
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def input_code_is_pressed(key_code: int) -> bool:
|
|
129
|
+
key_code = int(key_code)
|
|
130
|
+
if key_code == INPUT_CODE_UNBOUND:
|
|
131
|
+
return False
|
|
132
|
+
mouse_button = _mouse_button_for_code(key_code)
|
|
133
|
+
if mouse_button is not None:
|
|
134
|
+
return bool(rl.is_mouse_button_pressed(mouse_button))
|
|
135
|
+
if key_code < 0x100:
|
|
136
|
+
rl_key = _dik_to_rl_key(key_code)
|
|
137
|
+
if rl_key is None:
|
|
138
|
+
return False
|
|
139
|
+
return bool(rl.is_key_pressed(rl_key))
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _parse_keybinds_blob(blob: bytes | bytearray | None) -> tuple[int, ...]:
|
|
144
|
+
if blob is None:
|
|
145
|
+
return ()
|
|
146
|
+
if not isinstance(blob, (bytes, bytearray)):
|
|
147
|
+
return ()
|
|
148
|
+
if len(blob) != 0x80:
|
|
149
|
+
return ()
|
|
150
|
+
out: list[int] = []
|
|
151
|
+
for offset in range(0, 0x80, 4):
|
|
152
|
+
out.append(int.from_bytes(blob[offset : offset + 4], "little"))
|
|
153
|
+
return tuple(out)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def config_keybinds(config: CrimsonConfig | None) -> tuple[int, ...]:
|
|
157
|
+
if config is None:
|
|
158
|
+
return ()
|
|
159
|
+
return _parse_keybinds_blob(config.data.get("keybinds"))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def player_move_fire_binds(keybinds: Sequence[int], player_index: int) -> tuple[int, int, int, int, int]:
|
|
163
|
+
"""Return (up, down, left, right, fire) key codes for a player.
|
|
164
|
+
|
|
165
|
+
The classic config packs keybind blocks in 0x10-int strides; the first five entries
|
|
166
|
+
are used by `ui_render_keybind_help` (Up/Down/Left/Right/Fire).
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
base = int(player_index) * 0x10
|
|
170
|
+
values = [INPUT_CODE_UNBOUND, INPUT_CODE_UNBOUND, INPUT_CODE_UNBOUND, INPUT_CODE_UNBOUND, INPUT_CODE_UNBOUND]
|
|
171
|
+
for idx in range(5):
|
|
172
|
+
src = base + idx
|
|
173
|
+
if 0 <= src < len(keybinds):
|
|
174
|
+
values[idx] = int(keybinds[src])
|
|
175
|
+
return values[0], values[1], values[2], values[3], values[4]
|
|
176
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import random
|
|
5
|
+
from typing import TYPE_CHECKING, Protocol
|
|
6
|
+
|
|
7
|
+
import pyray as rl
|
|
8
|
+
|
|
9
|
+
from grim.assets import PaqTextureCache
|
|
10
|
+
from grim.audio import AudioState, update_audio
|
|
11
|
+
from grim.config import CrimsonConfig
|
|
12
|
+
from grim.fonts.small import SmallFontData, draw_small_text, load_small_font, measure_small_text_width
|
|
13
|
+
from grim.view import ViewContext
|
|
14
|
+
|
|
15
|
+
from ..game_world import GameWorld
|
|
16
|
+
from ..persistence.highscores import HighScoreRecord
|
|
17
|
+
from ..ui.game_over import GameOverUi
|
|
18
|
+
from ..ui.hud import HudAssets, load_hud_assets
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from ..persistence.save_status import GameStatus
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _clamp(value: float, lo: float, hi: float) -> float:
|
|
25
|
+
if value < lo:
|
|
26
|
+
return lo
|
|
27
|
+
if value > hi:
|
|
28
|
+
return hi
|
|
29
|
+
return value
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _ScreenFade(Protocol):
|
|
33
|
+
screen_fade_alpha: float
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BaseGameplayMode:
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
ctx: ViewContext,
|
|
40
|
+
*,
|
|
41
|
+
world_size: float,
|
|
42
|
+
default_game_mode_id: int,
|
|
43
|
+
demo_mode_active: bool = False,
|
|
44
|
+
difficulty_level: int = 0,
|
|
45
|
+
hardcore: bool = False,
|
|
46
|
+
texture_cache: PaqTextureCache | None = None,
|
|
47
|
+
config: CrimsonConfig | None = None,
|
|
48
|
+
audio: AudioState | None = None,
|
|
49
|
+
audio_rng: random.Random | None = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
self._assets_root = ctx.assets_dir
|
|
52
|
+
self._missing_assets: list[str] = []
|
|
53
|
+
self._hud_missing: list[str] = []
|
|
54
|
+
self._small: SmallFontData | None = None
|
|
55
|
+
self._hud_assets: HudAssets | None = None
|
|
56
|
+
|
|
57
|
+
self._config = config
|
|
58
|
+
self._base_dir = config.path.parent if config is not None else Path.cwd()
|
|
59
|
+
|
|
60
|
+
self.close_requested = False
|
|
61
|
+
self._action: str | None = None
|
|
62
|
+
self._paused = False
|
|
63
|
+
self._status: GameStatus | None = None
|
|
64
|
+
|
|
65
|
+
self._world = GameWorld(
|
|
66
|
+
assets_dir=ctx.assets_dir,
|
|
67
|
+
world_size=float(world_size),
|
|
68
|
+
demo_mode_active=bool(demo_mode_active),
|
|
69
|
+
difficulty_level=int(difficulty_level),
|
|
70
|
+
hardcore=bool(hardcore),
|
|
71
|
+
texture_cache=texture_cache,
|
|
72
|
+
config=config,
|
|
73
|
+
audio=audio,
|
|
74
|
+
audio_rng=audio_rng,
|
|
75
|
+
)
|
|
76
|
+
self._bind_world()
|
|
77
|
+
|
|
78
|
+
self._game_over_active = False
|
|
79
|
+
self._game_over_record: HighScoreRecord | None = None
|
|
80
|
+
self._game_over_ui = GameOverUi(
|
|
81
|
+
assets_root=self._assets_root,
|
|
82
|
+
base_dir=self._base_dir,
|
|
83
|
+
config=config or CrimsonConfig(path=self._base_dir / "crimson.cfg", data={"game_mode": int(default_game_mode_id)}),
|
|
84
|
+
)
|
|
85
|
+
self._game_over_banner = "reaper"
|
|
86
|
+
|
|
87
|
+
self._ui_mouse_x = 0.0
|
|
88
|
+
self._ui_mouse_y = 0.0
|
|
89
|
+
self._cursor_pulse_time = 0.0
|
|
90
|
+
self._last_dt_ms = 0.0
|
|
91
|
+
self._screen_fade: _ScreenFade | None = None
|
|
92
|
+
|
|
93
|
+
def _bind_world(self) -> None:
|
|
94
|
+
self._state = self._world.state
|
|
95
|
+
self._creatures = self._world.creatures
|
|
96
|
+
self._player = self._world.players[0]
|
|
97
|
+
self._state.status = self._status
|
|
98
|
+
|
|
99
|
+
def bind_status(self, status: GameStatus | None) -> None:
|
|
100
|
+
self._status = status
|
|
101
|
+
self._state.status = status
|
|
102
|
+
|
|
103
|
+
def bind_screen_fade(self, fade: _ScreenFade | None) -> None:
|
|
104
|
+
self._screen_fade = fade
|
|
105
|
+
|
|
106
|
+
def bind_audio(self, audio: AudioState | None, audio_rng: random.Random | None) -> None:
|
|
107
|
+
self._world.audio = audio
|
|
108
|
+
self._world.audio_rng = audio_rng
|
|
109
|
+
|
|
110
|
+
def _update_audio(self, dt: float) -> None:
|
|
111
|
+
if self._world.audio is not None:
|
|
112
|
+
update_audio(self._world.audio, dt)
|
|
113
|
+
|
|
114
|
+
def _ui_line_height(self, scale: float = 1.0) -> int:
|
|
115
|
+
if self._small is not None:
|
|
116
|
+
return int(self._small.cell_size * scale)
|
|
117
|
+
return int(20 * scale)
|
|
118
|
+
|
|
119
|
+
def _ui_text_width(self, text: str, scale: float = 1.0) -> int:
|
|
120
|
+
if self._small is not None:
|
|
121
|
+
return int(measure_small_text_width(self._small, text, scale))
|
|
122
|
+
return int(rl.measure_text(text, int(20 * scale)))
|
|
123
|
+
|
|
124
|
+
def _draw_ui_text(self, text: str, x: float, y: float, color: rl.Color, scale: float = 1.0) -> None:
|
|
125
|
+
if self._small is not None:
|
|
126
|
+
draw_small_text(self._small, text, x, y, scale, color)
|
|
127
|
+
else:
|
|
128
|
+
rl.draw_text(text, int(x), int(y), int(20 * scale), color)
|
|
129
|
+
|
|
130
|
+
def _ui_mouse_pos(self) -> rl.Vector2:
|
|
131
|
+
return rl.Vector2(float(self._ui_mouse_x), float(self._ui_mouse_y))
|
|
132
|
+
|
|
133
|
+
def _update_ui_mouse(self) -> None:
|
|
134
|
+
mouse = rl.get_mouse_position()
|
|
135
|
+
screen_w = float(rl.get_screen_width())
|
|
136
|
+
screen_h = float(rl.get_screen_height())
|
|
137
|
+
self._ui_mouse_x = _clamp(float(mouse.x), 0.0, max(0.0, screen_w - 1.0))
|
|
138
|
+
self._ui_mouse_y = _clamp(float(mouse.y), 0.0, max(0.0, screen_h - 1.0))
|
|
139
|
+
|
|
140
|
+
def _tick_frame(self, dt: float, *, clamp_cursor_pulse: bool = False) -> tuple[float, float]:
|
|
141
|
+
dt_frame = float(dt)
|
|
142
|
+
dt_ui_ms = float(min(dt_frame, 0.1) * 1000.0)
|
|
143
|
+
self._last_dt_ms = dt_ui_ms
|
|
144
|
+
|
|
145
|
+
self._update_ui_mouse()
|
|
146
|
+
|
|
147
|
+
pulse_dt = float(min(dt_frame, 0.1)) if clamp_cursor_pulse else dt_frame
|
|
148
|
+
self._cursor_pulse_time += pulse_dt * 1.1
|
|
149
|
+
|
|
150
|
+
return dt_frame, dt_ui_ms
|
|
151
|
+
|
|
152
|
+
def _player_name_default(self) -> str:
|
|
153
|
+
config = self._config
|
|
154
|
+
if config is None:
|
|
155
|
+
return ""
|
|
156
|
+
raw = config.data.get("player_name")
|
|
157
|
+
if isinstance(raw, (bytes, bytearray)):
|
|
158
|
+
return bytes(raw).split(b"\x00", 1)[0].decode("latin-1", errors="ignore")
|
|
159
|
+
if isinstance(raw, str):
|
|
160
|
+
return raw
|
|
161
|
+
return ""
|
|
162
|
+
|
|
163
|
+
def open(self) -> None:
|
|
164
|
+
self.close_requested = False
|
|
165
|
+
self._action = None
|
|
166
|
+
self._paused = False
|
|
167
|
+
self._missing_assets.clear()
|
|
168
|
+
self._hud_missing.clear()
|
|
169
|
+
try:
|
|
170
|
+
self._small = load_small_font(self._assets_root, self._missing_assets)
|
|
171
|
+
except Exception:
|
|
172
|
+
self._small = None
|
|
173
|
+
|
|
174
|
+
self._hud_assets = load_hud_assets(self._assets_root)
|
|
175
|
+
if self._hud_assets.missing:
|
|
176
|
+
self._hud_missing = list(self._hud_assets.missing)
|
|
177
|
+
|
|
178
|
+
self._game_over_active = False
|
|
179
|
+
self._game_over_record = None
|
|
180
|
+
self._game_over_banner = "reaper"
|
|
181
|
+
self._game_over_ui.close()
|
|
182
|
+
|
|
183
|
+
player_count = 1
|
|
184
|
+
config = self._config
|
|
185
|
+
if config is not None:
|
|
186
|
+
try:
|
|
187
|
+
player_count = int(config.data.get("player_count", 1) or 1)
|
|
188
|
+
except Exception:
|
|
189
|
+
player_count = 1
|
|
190
|
+
seed = random.getrandbits(32)
|
|
191
|
+
self._world.reset(seed=seed, player_count=max(1, min(4, player_count)))
|
|
192
|
+
self._world.open()
|
|
193
|
+
self._bind_world()
|
|
194
|
+
|
|
195
|
+
self._ui_mouse_x = float(rl.get_screen_width()) * 0.5
|
|
196
|
+
self._ui_mouse_y = float(rl.get_screen_height()) * 0.5
|
|
197
|
+
self._cursor_pulse_time = 0.0
|
|
198
|
+
|
|
199
|
+
def close(self) -> None:
|
|
200
|
+
self._game_over_ui.close()
|
|
201
|
+
if self._small is not None:
|
|
202
|
+
rl.unload_texture(self._small.texture)
|
|
203
|
+
self._small = None
|
|
204
|
+
self._hud_assets = None
|
|
205
|
+
self._world.close()
|
|
206
|
+
|
|
207
|
+
def take_action(self) -> str | None:
|
|
208
|
+
action = self._action
|
|
209
|
+
self._action = None
|
|
210
|
+
return action
|
|
211
|
+
|
|
212
|
+
def _draw_screen_fade(self) -> None:
|
|
213
|
+
fade_alpha = 0.0
|
|
214
|
+
if self._screen_fade is not None:
|
|
215
|
+
fade_alpha = float(self._screen_fade.screen_fade_alpha)
|
|
216
|
+
if fade_alpha <= 0.0:
|
|
217
|
+
return
|
|
218
|
+
alpha = int(255 * max(0.0, min(1.0, fade_alpha)))
|
|
219
|
+
rl.draw_rectangle(0, 0, int(rl.get_screen_width()), int(rl.get_screen_height()), rl.Color(0, 0, 0, alpha))
|