crimsonland 0.1.0.dev14__tar.gz → 0.1.0.dev15__tar.gz
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.
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/PKG-INFO +1 -1
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/pyproject.toml +1 -1
- crimsonland-0.1.0.dev15/src/crimson/.DS_Store +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/cli.py +2 -0
- crimsonland-0.1.0.dev15/src/crimson/creatures/.DS_Store +0 -0
- crimsonland-0.1.0.dev15/src/crimson/debug.py +16 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/game.py +248 -2
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/gameplay.py +16 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/modes/quest_mode.py +37 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/modes/survival_mode.py +42 -6
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/player_damage.py +2 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/ui/quest_results.py +16 -6
- crimsonland-0.1.0.dev15/src/grim/.DS_Store +0 -0
- crimsonland-0.1.0.dev14/src/crimson/debug.py +0 -7
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/readme.md +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/assets_fetch.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/atlas.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/audio_router.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/bonuses.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/camera.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/creatures/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/creatures/ai.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/creatures/anim.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/creatures/damage.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/creatures/runtime.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/creatures/spawn.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/demo.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/demo_trial.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/effects.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/effects_atlas.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/assets.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/boot.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/high_scores_layout.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/menu.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/panels/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/panels/base.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/panels/controls.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/panels/credits.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/panels/databases.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/panels/mods.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/panels/options.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/panels/play_game.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/panels/stats.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/pause_menu.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/frontend/transitions.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/game_modes.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/game_world.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/input_codes.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/modes/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/modes/base_gameplay_mode.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/modes/rush_mode.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/modes/tutorial_mode.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/modes/typo_mode.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/paths.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/perks.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/persistence/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/persistence/highscores.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/persistence/save_status.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/projectiles.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/quests/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/quests/helpers.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/quests/registry.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/quests/results.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/quests/runtime.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/quests/tier1.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/quests/tier2.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/quests/tier3.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/quests/tier4.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/quests/tier5.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/quests/timeline.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/quests/types.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/render/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/render/terrain_fx.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/render/world_renderer.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/sim/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/sim/world_defs.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/sim/world_state.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/terrain_assets.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/tutorial/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/tutorial/timeline.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/typo/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/typo/names.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/typo/player.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/typo/spawns.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/typo/typing.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/ui/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/ui/cursor.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/ui/demo_trial_overlay.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/ui/game_over.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/ui/hud.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/ui/menu_panel.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/ui/perk_menu.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/ui/shadow.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/aim_debug.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/animations.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/arsenal_debug.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/audio_bootstrap.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/bonuses.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/camera_debug.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/camera_shake.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/corpse_stamp_debug.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/decals_debug.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/empty.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/fonts.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/game_over.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/ground.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/lighting_debug.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/particles.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/perk_menu_debug.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/perks.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/player.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/player_sprite_debug.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/projectile_fx.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/projectile_render_debug.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/projectiles.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/quest_title_overlay.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/registry.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/rush.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/small_font_debug.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/spawn_plan.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/sprites.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/survival.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/terrain.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/ui.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/views/wicons.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/weapon_sfx.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/crimson/weapons.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/app.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/assets.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/audio.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/config.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/console.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/fonts/__init__.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/fonts/grim_mono.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/fonts/small.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/input.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/jaz.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/math.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/music.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/paq.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/rand.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/sfx.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/sfx_map.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/terrain_render.py +0 -0
- {crimsonland-0.1.0.dev14 → crimsonland-0.1.0.dev15}/src/grim/view.py +0 -0
|
Binary file
|
|
@@ -211,6 +211,7 @@ def cmd_game(
|
|
|
211
211
|
seed: int | None = typer.Option(None, help="rng seed"),
|
|
212
212
|
demo: bool = typer.Option(False, "--demo", help="enable shareware demo mode"),
|
|
213
213
|
no_intro: bool = typer.Option(False, "--no-intro", help="skip company splashes and intro music"),
|
|
214
|
+
debug: bool = typer.Option(False, "--debug", help="enable debug cheats and overlays"),
|
|
214
215
|
base_dir: Path = typer.Option(
|
|
215
216
|
default_runtime_dir(),
|
|
216
217
|
"--base-dir",
|
|
@@ -236,6 +237,7 @@ def cmd_game(
|
|
|
236
237
|
seed=seed,
|
|
237
238
|
demo_enabled=demo,
|
|
238
239
|
no_intro=no_intro,
|
|
240
|
+
debug=debug,
|
|
239
241
|
)
|
|
240
242
|
run_game(config)
|
|
241
243
|
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
_DEBUG_OVERRIDE: bool | None = None
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def set_debug_enabled(enabled: bool) -> None:
|
|
9
|
+
global _DEBUG_OVERRIDE
|
|
10
|
+
_DEBUG_OVERRIDE = bool(enabled)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def debug_enabled() -> bool:
|
|
14
|
+
if _DEBUG_OVERRIDE is not None:
|
|
15
|
+
return bool(_DEBUG_OVERRIDE)
|
|
16
|
+
return os.environ.get("CRIMSON_DEBUG") == "1"
|
|
@@ -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
|
|
@@ -113,6 +113,7 @@ class GameConfig:
|
|
|
113
113
|
seed: int | None = None
|
|
114
114
|
demo_enabled: bool = False
|
|
115
115
|
no_intro: bool = False
|
|
116
|
+
debug: bool = False
|
|
116
117
|
|
|
117
118
|
|
|
118
119
|
@dataclass(slots=True)
|
|
@@ -190,6 +191,25 @@ QUEST_BACK_BUTTON_X_OFFSET = 138.0
|
|
|
190
191
|
QUEST_BACK_BUTTON_Y_OFFSET = 212.0
|
|
191
192
|
QUEST_PANEL_HEIGHT = 378.0
|
|
192
193
|
|
|
194
|
+
# game_update_victory_screen (0x00406350): used as the "end note" screen after the final quest.
|
|
195
|
+
END_NOTE_PANEL_POS_X = -45.0
|
|
196
|
+
END_NOTE_PANEL_POS_Y = 110.0
|
|
197
|
+
END_NOTE_PANEL_GEOM_X0 = -63.0
|
|
198
|
+
END_NOTE_PANEL_GEOM_Y0 = -81.0
|
|
199
|
+
END_NOTE_PANEL_W = 510.0
|
|
200
|
+
END_NOTE_PANEL_H = 378.0
|
|
201
|
+
|
|
202
|
+
END_NOTE_HEADER_X_OFFSET = 214.0 # v11 + 44 - 10 in the decompile, relative to panel-left
|
|
203
|
+
END_NOTE_HEADER_Y_OFFSET = 46.0 # (base_y + 40) + 6 in the decompile, relative to panel-top
|
|
204
|
+
END_NOTE_BODY_X_OFFSET = END_NOTE_HEADER_X_OFFSET - 8.0
|
|
205
|
+
END_NOTE_BODY_Y_GAP = 32.0
|
|
206
|
+
END_NOTE_LINE_STEP_Y = 14.0
|
|
207
|
+
END_NOTE_AFTER_BODY_Y_GAP = 22.0 # 14 + 8 in the decompile
|
|
208
|
+
|
|
209
|
+
END_NOTE_BUTTON_X_OFFSET = 266.0 # (v11 + 44 + 20) - 4 + 26, relative to panel-left
|
|
210
|
+
END_NOTE_BUTTON_Y_OFFSET = 210.0 # (base_y + 40) + 170 in the decompile, relative to panel-top
|
|
211
|
+
END_NOTE_BUTTON_STEP_Y = 32.0
|
|
212
|
+
|
|
193
213
|
|
|
194
214
|
class QuestsMenuView:
|
|
195
215
|
"""Quest selection menu.
|
|
@@ -282,6 +302,7 @@ class QuestsMenuView:
|
|
|
282
302
|
self._cursor_pulse_time += min(dt, 0.1) * 1.1
|
|
283
303
|
|
|
284
304
|
config = self._state.config
|
|
305
|
+
status = self._state.status
|
|
285
306
|
|
|
286
307
|
# The original forcibly clears hardcore in the demo build.
|
|
287
308
|
if self._state.demo_enabled:
|
|
@@ -289,6 +310,14 @@ class QuestsMenuView:
|
|
|
289
310
|
config.data["hardcore_flag"] = 0
|
|
290
311
|
self._dirty = True
|
|
291
312
|
|
|
313
|
+
if debug_enabled() and rl.is_key_pressed(rl.KeyboardKey.KEY_F5):
|
|
314
|
+
unlock = 49
|
|
315
|
+
if int(status.quest_unlock_index) < unlock:
|
|
316
|
+
status.quest_unlock_index = unlock
|
|
317
|
+
if int(status.quest_unlock_index_full) < unlock:
|
|
318
|
+
status.quest_unlock_index_full = unlock
|
|
319
|
+
self._state.console.log.log("debug: unlocked all quests")
|
|
320
|
+
|
|
292
321
|
if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
293
322
|
self._action = "open_play_game"
|
|
294
323
|
return
|
|
@@ -374,7 +403,7 @@ class QuestsMenuView:
|
|
|
374
403
|
# `sub_447d40` base sums:
|
|
375
404
|
# x_sum = <ui_element_x> + <ui_element_offset_x> (x=-5)
|
|
376
405
|
# 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 +
|
|
406
|
+
x_sum = QUEST_MENU_BASE_X + QUEST_MENU_PANEL_OFFSET_X
|
|
378
407
|
y_sum = QUEST_MENU_BASE_Y + MENU_PANEL_OFFSET_Y + self._widescreen_y_shift
|
|
379
408
|
|
|
380
409
|
title_x = x_sum + QUEST_TITLE_X_OFFSET
|
|
@@ -1288,6 +1317,9 @@ class QuestResultsView:
|
|
|
1288
1317
|
self._action = "start_quest"
|
|
1289
1318
|
return
|
|
1290
1319
|
if action == "play_next":
|
|
1320
|
+
if int(self._quest_stage_major) == 5 and int(self._quest_stage_minor) == 10:
|
|
1321
|
+
self._action = "end_note"
|
|
1322
|
+
return
|
|
1291
1323
|
next_level = _next_quest_level(self._quest_level)
|
|
1292
1324
|
if next_level is not None:
|
|
1293
1325
|
self._state.pending_quest_level = next_level
|
|
@@ -1336,6 +1368,217 @@ class QuestResultsView:
|
|
|
1336
1368
|
self._action = "open_high_scores"
|
|
1337
1369
|
|
|
1338
1370
|
|
|
1371
|
+
class EndNoteView:
|
|
1372
|
+
"""Final quest "Show End Note" flow.
|
|
1373
|
+
|
|
1374
|
+
Classic:
|
|
1375
|
+
- quest_results_screen_update uses "Show End Note" instead of "Play Next" for quest 5.10
|
|
1376
|
+
- clicking it transitions to state 0x15 (game_update_victory_screen @ 0x00406350)
|
|
1377
|
+
"""
|
|
1378
|
+
|
|
1379
|
+
def __init__(self, state: GameState) -> None:
|
|
1380
|
+
self._state = state
|
|
1381
|
+
self._ground: GroundRenderer | None = None
|
|
1382
|
+
self._small_font: SmallFontData | None = None
|
|
1383
|
+
self._panel_tex: rl.Texture2D | None = None
|
|
1384
|
+
self._button_textures: UiButtonTextureSet | None = None
|
|
1385
|
+
self._action: str | None = None
|
|
1386
|
+
self._cursor_pulse_time = 0.0
|
|
1387
|
+
|
|
1388
|
+
self._survival_button = UiButtonState("Survival", force_wide=True)
|
|
1389
|
+
self._rush_button = UiButtonState(" Rush ", force_wide=True)
|
|
1390
|
+
self._typo_button = UiButtonState("Typ'o'Shooter", force_wide=True)
|
|
1391
|
+
self._main_menu_button = UiButtonState("Main Menu", force_wide=True)
|
|
1392
|
+
|
|
1393
|
+
def open(self) -> None:
|
|
1394
|
+
self._action = None
|
|
1395
|
+
self._cursor_pulse_time = 0.0
|
|
1396
|
+
self._ground = None if self._state.pause_background is not None else ensure_menu_ground(self._state)
|
|
1397
|
+
|
|
1398
|
+
cache = _ensure_texture_cache(self._state)
|
|
1399
|
+
self._panel_tex = cache.get_or_load("ui_menuPanel", "ui/ui_menuPanel.jaz").texture
|
|
1400
|
+
button_md = cache.get_or_load("ui_buttonMd", "ui/ui_button_128x32.jaz").texture
|
|
1401
|
+
button_sm = cache.get_or_load("ui_buttonSm", "ui/ui_button_64x32.jaz").texture
|
|
1402
|
+
self._button_textures = UiButtonTextureSet(button_sm=button_sm, button_md=button_md)
|
|
1403
|
+
self._small_font = None
|
|
1404
|
+
|
|
1405
|
+
def close(self) -> None:
|
|
1406
|
+
self._ground = None
|
|
1407
|
+
self._small_font = None
|
|
1408
|
+
self._panel_tex = None
|
|
1409
|
+
self._button_textures = None
|
|
1410
|
+
|
|
1411
|
+
def update(self, dt: float) -> None:
|
|
1412
|
+
if self._state.audio is not None:
|
|
1413
|
+
update_audio(self._state.audio, dt)
|
|
1414
|
+
if self._ground is not None:
|
|
1415
|
+
self._ground.process_pending()
|
|
1416
|
+
self._cursor_pulse_time += min(float(dt), 0.1) * 1.1
|
|
1417
|
+
|
|
1418
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
1419
|
+
if self._state.audio is not None:
|
|
1420
|
+
play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
|
|
1421
|
+
self._action = "back_to_menu"
|
|
1422
|
+
return
|
|
1423
|
+
|
|
1424
|
+
textures = self._button_textures
|
|
1425
|
+
if textures is None or (textures.button_sm is None and textures.button_md is None):
|
|
1426
|
+
return
|
|
1427
|
+
|
|
1428
|
+
screen_w = float(rl.get_screen_width())
|
|
1429
|
+
scale = 1.0
|
|
1430
|
+
|
|
1431
|
+
layout_w = screen_w / scale if scale else screen_w
|
|
1432
|
+
widescreen_shift_y = MenuView._menu_widescreen_y_shift(layout_w)
|
|
1433
|
+
|
|
1434
|
+
panel_left = (END_NOTE_PANEL_GEOM_X0 + END_NOTE_PANEL_POS_X) * scale
|
|
1435
|
+
panel_top = (END_NOTE_PANEL_GEOM_Y0 + END_NOTE_PANEL_POS_Y + widescreen_shift_y) * scale
|
|
1436
|
+
|
|
1437
|
+
button_x = panel_left + END_NOTE_BUTTON_X_OFFSET * scale
|
|
1438
|
+
button_y = panel_top + END_NOTE_BUTTON_Y_OFFSET * scale
|
|
1439
|
+
|
|
1440
|
+
font = self._ensure_small_font()
|
|
1441
|
+
mouse = rl.get_mouse_position()
|
|
1442
|
+
click = rl.is_mouse_button_pressed(rl.MOUSE_BUTTON_LEFT)
|
|
1443
|
+
dt_ms = min(float(dt), 0.1) * 1000.0
|
|
1444
|
+
|
|
1445
|
+
survival_w = button_width(font, self._survival_button.label, scale=scale, force_wide=self._survival_button.force_wide)
|
|
1446
|
+
if button_update(self._survival_button, x=button_x, y=button_y, width=survival_w, dt_ms=dt_ms, mouse=mouse, click=click):
|
|
1447
|
+
self._state.config.data["game_mode"] = 1
|
|
1448
|
+
if self._state.audio is not None:
|
|
1449
|
+
play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
|
|
1450
|
+
self._action = "start_survival"
|
|
1451
|
+
return
|
|
1452
|
+
|
|
1453
|
+
button_y += END_NOTE_BUTTON_STEP_Y * scale
|
|
1454
|
+
rush_w = button_width(font, self._rush_button.label, scale=scale, force_wide=self._rush_button.force_wide)
|
|
1455
|
+
if button_update(self._rush_button, x=button_x, y=button_y, width=rush_w, dt_ms=dt_ms, mouse=mouse, click=click):
|
|
1456
|
+
self._state.config.data["game_mode"] = 2
|
|
1457
|
+
if self._state.audio is not None:
|
|
1458
|
+
play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
|
|
1459
|
+
self._action = "start_rush"
|
|
1460
|
+
return
|
|
1461
|
+
|
|
1462
|
+
button_y += END_NOTE_BUTTON_STEP_Y * scale
|
|
1463
|
+
typo_w = button_width(font, self._typo_button.label, scale=scale, force_wide=self._typo_button.force_wide)
|
|
1464
|
+
if button_update(self._typo_button, x=button_x, y=button_y, width=typo_w, dt_ms=dt_ms, mouse=mouse, click=click):
|
|
1465
|
+
self._state.config.data["game_mode"] = 4
|
|
1466
|
+
self._state.screen_fade_alpha = 0.0
|
|
1467
|
+
self._state.screen_fade_ramp = True
|
|
1468
|
+
if self._state.audio is not None:
|
|
1469
|
+
play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
|
|
1470
|
+
self._action = "start_typo"
|
|
1471
|
+
return
|
|
1472
|
+
|
|
1473
|
+
button_y += END_NOTE_BUTTON_STEP_Y * scale
|
|
1474
|
+
main_w = button_width(font, self._main_menu_button.label, scale=scale, force_wide=self._main_menu_button.force_wide)
|
|
1475
|
+
if button_update(self._main_menu_button, x=button_x, y=button_y, width=main_w, dt_ms=dt_ms, mouse=mouse, click=click):
|
|
1476
|
+
if self._state.audio is not None:
|
|
1477
|
+
play_sfx(self._state.audio, "sfx_ui_buttonclick", rng=self._state.rng)
|
|
1478
|
+
self._action = "back_to_menu"
|
|
1479
|
+
return
|
|
1480
|
+
|
|
1481
|
+
def draw(self) -> None:
|
|
1482
|
+
rl.clear_background(rl.BLACK)
|
|
1483
|
+
pause_background = self._state.pause_background
|
|
1484
|
+
if pause_background is not None:
|
|
1485
|
+
pause_background.draw_pause_background()
|
|
1486
|
+
elif self._ground is not None:
|
|
1487
|
+
self._ground.draw(0.0, 0.0)
|
|
1488
|
+
_draw_screen_fade(self._state)
|
|
1489
|
+
|
|
1490
|
+
panel_tex = self._panel_tex
|
|
1491
|
+
if panel_tex is None:
|
|
1492
|
+
return
|
|
1493
|
+
|
|
1494
|
+
screen_w = float(rl.get_screen_width())
|
|
1495
|
+
scale = 1.0
|
|
1496
|
+
layout_w = screen_w / scale if scale else screen_w
|
|
1497
|
+
widescreen_shift_y = MenuView._menu_widescreen_y_shift(layout_w)
|
|
1498
|
+
|
|
1499
|
+
panel_left = (END_NOTE_PANEL_GEOM_X0 + END_NOTE_PANEL_POS_X) * scale
|
|
1500
|
+
panel_top = (END_NOTE_PANEL_GEOM_Y0 + END_NOTE_PANEL_POS_Y + widescreen_shift_y) * scale
|
|
1501
|
+
panel = rl.Rectangle(
|
|
1502
|
+
float(panel_left),
|
|
1503
|
+
float(panel_top),
|
|
1504
|
+
float(END_NOTE_PANEL_W * scale),
|
|
1505
|
+
float(END_NOTE_PANEL_H * scale),
|
|
1506
|
+
)
|
|
1507
|
+
|
|
1508
|
+
fx_detail = bool(int(self._state.config.data.get("fx_detail_0", 0) or 0))
|
|
1509
|
+
draw_classic_menu_panel(panel_tex, dst=panel, tint=rl.WHITE, shadow=fx_detail)
|
|
1510
|
+
|
|
1511
|
+
font = self._ensure_small_font()
|
|
1512
|
+
hardcore = bool(int(self._state.config.data.get("hardcore_flag", 0) or 0))
|
|
1513
|
+
header = " Incredible!" if hardcore else "Congratulations!"
|
|
1514
|
+
body_lines = (
|
|
1515
|
+
[
|
|
1516
|
+
"You've done the thing we all thought was",
|
|
1517
|
+
"virtually impossible. To reward your",
|
|
1518
|
+
"efforts a new weapon has been unlocked ",
|
|
1519
|
+
"for you: Splitter Gun.",
|
|
1520
|
+
"",
|
|
1521
|
+
"",
|
|
1522
|
+
]
|
|
1523
|
+
if hardcore
|
|
1524
|
+
else [
|
|
1525
|
+
"You've completed all the levels but the battle",
|
|
1526
|
+
"isn't over yet! With all of the unlocked perks",
|
|
1527
|
+
"and weapons your Survival is just a bit easier.",
|
|
1528
|
+
"You can also replay the quests in Hardcore.",
|
|
1529
|
+
"As an additional reward for your victorious",
|
|
1530
|
+
"playing, a completely new and different game",
|
|
1531
|
+
"mode is unlocked for you: Typ'o'Shooter.",
|
|
1532
|
+
]
|
|
1533
|
+
)
|
|
1534
|
+
|
|
1535
|
+
header_x = panel_left + END_NOTE_HEADER_X_OFFSET * scale
|
|
1536
|
+
header_y = panel_top + END_NOTE_HEADER_Y_OFFSET * scale
|
|
1537
|
+
header_color = rl.Color(255, 255, 255, int(255 * 0.8))
|
|
1538
|
+
body_color = rl.Color(255, 255, 255, int(255 * 0.5))
|
|
1539
|
+
|
|
1540
|
+
draw_small_text(font, header, header_x, header_y, 1.5 * scale, header_color)
|
|
1541
|
+
|
|
1542
|
+
body_x = panel_left + END_NOTE_BODY_X_OFFSET * scale
|
|
1543
|
+
body_y = header_y + END_NOTE_BODY_Y_GAP * scale
|
|
1544
|
+
for idx, line in enumerate(body_lines):
|
|
1545
|
+
draw_small_text(font, line, body_x, body_y, 1.0 * scale, body_color)
|
|
1546
|
+
if idx != len(body_lines) - 1:
|
|
1547
|
+
body_y += END_NOTE_LINE_STEP_Y * scale
|
|
1548
|
+
body_y += END_NOTE_AFTER_BODY_Y_GAP * scale
|
|
1549
|
+
draw_small_text(font, "Good luck with your battles, trooper!", body_x, body_y, 1.0 * scale, body_color)
|
|
1550
|
+
|
|
1551
|
+
textures = self._button_textures
|
|
1552
|
+
if textures is not None and (textures.button_sm is not None or textures.button_md is not None):
|
|
1553
|
+
button_x = panel_left + END_NOTE_BUTTON_X_OFFSET * scale
|
|
1554
|
+
button_y = panel_top + END_NOTE_BUTTON_Y_OFFSET * scale
|
|
1555
|
+
survival_w = button_width(font, self._survival_button.label, scale=scale, force_wide=self._survival_button.force_wide)
|
|
1556
|
+
button_draw(textures, font, self._survival_button, x=button_x, y=button_y, width=survival_w, scale=scale)
|
|
1557
|
+
button_y += END_NOTE_BUTTON_STEP_Y * scale
|
|
1558
|
+
rush_w = button_width(font, self._rush_button.label, scale=scale, force_wide=self._rush_button.force_wide)
|
|
1559
|
+
button_draw(textures, font, self._rush_button, x=button_x, y=button_y, width=rush_w, scale=scale)
|
|
1560
|
+
button_y += END_NOTE_BUTTON_STEP_Y * scale
|
|
1561
|
+
typo_w = button_width(font, self._typo_button.label, scale=scale, force_wide=self._typo_button.force_wide)
|
|
1562
|
+
button_draw(textures, font, self._typo_button, x=button_x, y=button_y, width=typo_w, scale=scale)
|
|
1563
|
+
button_y += END_NOTE_BUTTON_STEP_Y * scale
|
|
1564
|
+
main_w = button_width(font, self._main_menu_button.label, scale=scale, force_wide=self._main_menu_button.force_wide)
|
|
1565
|
+
button_draw(textures, font, self._main_menu_button, x=button_x, y=button_y, width=main_w, scale=scale)
|
|
1566
|
+
|
|
1567
|
+
_draw_menu_cursor(self._state, pulse_time=self._cursor_pulse_time)
|
|
1568
|
+
|
|
1569
|
+
def take_action(self) -> str | None:
|
|
1570
|
+
action = self._action
|
|
1571
|
+
self._action = None
|
|
1572
|
+
return action
|
|
1573
|
+
|
|
1574
|
+
def _ensure_small_font(self) -> SmallFontData:
|
|
1575
|
+
if self._small_font is not None:
|
|
1576
|
+
return self._small_font
|
|
1577
|
+
missing_assets: list[str] = []
|
|
1578
|
+
self._small_font = load_small_font(self._state.assets_dir, missing_assets)
|
|
1579
|
+
return self._small_font
|
|
1580
|
+
|
|
1581
|
+
|
|
1339
1582
|
class QuestFailedView:
|
|
1340
1583
|
def __init__(self, state: GameState) -> None:
|
|
1341
1584
|
self._state = state
|
|
@@ -1944,6 +2187,7 @@ class GameLoopView:
|
|
|
1944
2187
|
"start_quest": QuestGameView(state),
|
|
1945
2188
|
"quest_results": QuestResultsView(state),
|
|
1946
2189
|
"quest_failed": QuestFailedView(state),
|
|
2190
|
+
"end_note": EndNoteView(state),
|
|
1947
2191
|
"open_high_scores": HighScoresView(state),
|
|
1948
2192
|
"start_survival": SurvivalGameView(state),
|
|
1949
2193
|
"start_rush": RushGameView(state),
|
|
@@ -2474,6 +2718,8 @@ def _resolve_assets_dir(config: GameConfig) -> Path:
|
|
|
2474
2718
|
|
|
2475
2719
|
|
|
2476
2720
|
def run_game(config: GameConfig) -> None:
|
|
2721
|
+
if config.debug:
|
|
2722
|
+
set_debug_enabled(True)
|
|
2477
2723
|
base_dir = config.base_dir
|
|
2478
2724
|
base_dir.mkdir(parents=True, exist_ok=True)
|
|
2479
2725
|
crash_path = base_dir / "crash.log"
|
|
@@ -534,6 +534,7 @@ class GameplayState:
|
|
|
534
534
|
weapon_available: list[bool] = field(default_factory=lambda: [False] * WEAPON_COUNT_SIZE)
|
|
535
535
|
_weapon_available_game_mode: int = -1
|
|
536
536
|
_weapon_available_unlock_index: int = -1
|
|
537
|
+
_weapon_available_unlock_index_full: int = -1
|
|
537
538
|
friendly_fire_enabled: bool = False
|
|
538
539
|
bonus_spawn_guard: bool = False
|
|
539
540
|
bonus_hud: BonusHudState = field(default_factory=BonusHudState)
|
|
@@ -547,6 +548,7 @@ class GameplayState:
|
|
|
547
548
|
shots_fired: list[int] = field(default_factory=lambda: [0] * 4)
|
|
548
549
|
shots_hit: list[int] = field(default_factory=lambda: [0] * 4)
|
|
549
550
|
weapon_shots_fired: list[list[int]] = field(default_factory=lambda: [[0] * WEAPON_COUNT_SIZE for _ in range(4)])
|
|
551
|
+
debug_god_mode: bool = False
|
|
550
552
|
|
|
551
553
|
def __post_init__(self) -> None:
|
|
552
554
|
rand = self.rng.rand
|
|
@@ -1236,17 +1238,23 @@ def weapon_refresh_available(state: "GameplayState") -> None:
|
|
|
1236
1238
|
"""
|
|
1237
1239
|
|
|
1238
1240
|
unlock_index = 0
|
|
1241
|
+
unlock_index_full = 0
|
|
1239
1242
|
status = state.status
|
|
1240
1243
|
if status is not None:
|
|
1241
1244
|
try:
|
|
1242
1245
|
unlock_index = int(status.quest_unlock_index)
|
|
1243
1246
|
except Exception:
|
|
1244
1247
|
unlock_index = 0
|
|
1248
|
+
try:
|
|
1249
|
+
unlock_index_full = int(status.quest_unlock_index_full)
|
|
1250
|
+
except Exception:
|
|
1251
|
+
unlock_index_full = 0
|
|
1245
1252
|
|
|
1246
1253
|
game_mode = int(state.game_mode)
|
|
1247
1254
|
if (
|
|
1248
1255
|
int(state._weapon_available_game_mode) == game_mode
|
|
1249
1256
|
and int(state._weapon_available_unlock_index) == unlock_index
|
|
1257
|
+
and int(state._weapon_available_unlock_index_full) == unlock_index_full
|
|
1250
1258
|
):
|
|
1251
1259
|
return
|
|
1252
1260
|
|
|
@@ -1281,8 +1289,16 @@ def weapon_refresh_available(state: "GameplayState") -> None:
|
|
|
1281
1289
|
if 0 <= idx < len(available):
|
|
1282
1290
|
available[idx] = True
|
|
1283
1291
|
|
|
1292
|
+
# Secret unlock: Splitter Gun (weapon id 29) becomes available once the hardcore
|
|
1293
|
+
# unlock track reaches stage 5 (quest_unlock_index_full >= 40).
|
|
1294
|
+
if (not state.demo_mode_active) and unlock_index_full >= 0x28:
|
|
1295
|
+
splitter_id = int(WeaponId.SPLITTER_GUN)
|
|
1296
|
+
if 0 <= splitter_id < len(available):
|
|
1297
|
+
available[splitter_id] = True
|
|
1298
|
+
|
|
1284
1299
|
state._weapon_available_game_mode = game_mode
|
|
1285
1300
|
state._weapon_available_unlock_index = unlock_index
|
|
1301
|
+
state._weapon_available_unlock_index_full = unlock_index_full
|
|
1286
1302
|
|
|
1287
1303
|
|
|
1288
1304
|
def weapon_pick_random_available(state: "GameplayState") -> int:
|
|
@@ -12,6 +12,7 @@ from grim.config import CrimsonConfig
|
|
|
12
12
|
from grim.fonts.grim_mono import GrimMonoFont, load_grim_mono_font
|
|
13
13
|
from grim.view import ViewContext
|
|
14
14
|
|
|
15
|
+
from ..debug import debug_enabled
|
|
15
16
|
from ..game_modes import GameMode
|
|
16
17
|
from ..gameplay import most_used_weapon_id_for_player, perk_selection_current_choices, perk_selection_pick, weapon_assign_player
|
|
17
18
|
from ..input_codes import config_keybinds, input_code_is_down, input_code_is_pressed, player_move_fire_binds
|
|
@@ -44,6 +45,7 @@ from ..ui.perk_menu import (
|
|
|
44
45
|
wrap_ui_text,
|
|
45
46
|
)
|
|
46
47
|
from ..views.quest_title_overlay import draw_quest_title_overlay
|
|
48
|
+
from ..weapons import WEAPON_BY_ID
|
|
47
49
|
from .base_gameplay_mode import BaseGameplayMode, _clamp
|
|
48
50
|
|
|
49
51
|
WORLD_SIZE = 1024.0
|
|
@@ -78,6 +80,8 @@ PERK_PROMPT_LEVEL_UP_SHIFT_X = -46.0
|
|
|
78
80
|
PERK_PROMPT_LEVEL_UP_SHIFT_Y = -4.0
|
|
79
81
|
|
|
80
82
|
PERK_PROMPT_TEXT_MARGIN_X = 16.0
|
|
83
|
+
|
|
84
|
+
_DEBUG_WEAPON_IDS = tuple(sorted(WEAPON_BY_ID))
|
|
81
85
|
PERK_PROMPT_TEXT_OFFSET_Y = 8.0
|
|
82
86
|
|
|
83
87
|
|
|
@@ -305,10 +309,35 @@ class QuestMode(BaseGameplayMode):
|
|
|
305
309
|
if rl.is_key_pressed(rl.KeyboardKey.KEY_TAB):
|
|
306
310
|
self._paused = not self._paused
|
|
307
311
|
|
|
312
|
+
if debug_enabled() and (not self._perk_menu_open):
|
|
313
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_F2):
|
|
314
|
+
self._state.debug_god_mode = not bool(self._state.debug_god_mode)
|
|
315
|
+
self._world.audio_router.play_sfx("sfx_ui_buttonclick")
|
|
316
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_F3):
|
|
317
|
+
self._state.perk_selection.pending_count += 1
|
|
318
|
+
self._state.perk_selection.choices_dirty = True
|
|
319
|
+
self._world.audio_router.play_sfx("sfx_ui_levelup")
|
|
320
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_LEFT_BRACKET):
|
|
321
|
+
self._debug_cycle_weapon(-1)
|
|
322
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_RIGHT_BRACKET):
|
|
323
|
+
self._debug_cycle_weapon(1)
|
|
324
|
+
|
|
308
325
|
if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
309
326
|
self._action = "open_pause_menu"
|
|
310
327
|
return
|
|
311
328
|
|
|
329
|
+
def _debug_cycle_weapon(self, delta: int) -> None:
|
|
330
|
+
weapon_ids = _DEBUG_WEAPON_IDS
|
|
331
|
+
if not weapon_ids:
|
|
332
|
+
return
|
|
333
|
+
current = int(self._player.weapon_id)
|
|
334
|
+
try:
|
|
335
|
+
idx = weapon_ids.index(current)
|
|
336
|
+
except ValueError:
|
|
337
|
+
idx = 0
|
|
338
|
+
weapon_id = int(weapon_ids[(idx + int(delta)) % len(weapon_ids)])
|
|
339
|
+
weapon_assign_player(self._player, weapon_id, state=self._state)
|
|
340
|
+
|
|
312
341
|
def _build_input(self):
|
|
313
342
|
keybinds = config_keybinds(self._config)
|
|
314
343
|
if not keybinds:
|
|
@@ -875,6 +904,12 @@ class QuestMode(BaseGameplayMode):
|
|
|
875
904
|
small_indicators=self._hud_small_indicators(),
|
|
876
905
|
)
|
|
877
906
|
|
|
907
|
+
if debug_enabled() and (not perk_menu_active):
|
|
908
|
+
x = 18.0
|
|
909
|
+
y = max(18.0, hud_bottom + 10.0)
|
|
910
|
+
god = "on" if self._state.debug_god_mode else "off"
|
|
911
|
+
self._draw_ui_text(f"debug: [/] weapon F3 perk+1 F2 god={god}", x, y, UI_HINT_COLOR, scale=0.9)
|
|
912
|
+
|
|
878
913
|
self._draw_quest_title()
|
|
879
914
|
|
|
880
915
|
warn_y = float(rl.get_screen_height()) - 28.0
|
|
@@ -895,6 +930,8 @@ class QuestMode(BaseGameplayMode):
|
|
|
895
930
|
self._draw_game_cursor()
|
|
896
931
|
x = 18.0
|
|
897
932
|
y = max(18.0, hud_bottom + 10.0)
|
|
933
|
+
if debug_enabled() and (not perk_menu_active):
|
|
934
|
+
y += float(self._ui_line_height(scale=0.9))
|
|
898
935
|
self._draw_ui_text("paused (TAB)", x, y, UI_HINT_COLOR)
|
|
899
936
|
else:
|
|
900
937
|
self._draw_aim_cursor()
|
|
@@ -14,7 +14,14 @@ from grim.view import ViewContext
|
|
|
14
14
|
from ..creatures.spawn import advance_survival_spawn_stage, tick_survival_wave_spawns
|
|
15
15
|
from ..debug import debug_enabled
|
|
16
16
|
from ..game_modes import GameMode
|
|
17
|
-
from ..gameplay import
|
|
17
|
+
from ..gameplay import (
|
|
18
|
+
PlayerInput,
|
|
19
|
+
most_used_weapon_id_for_player,
|
|
20
|
+
perk_selection_current_choices,
|
|
21
|
+
perk_selection_pick,
|
|
22
|
+
survival_check_level_up,
|
|
23
|
+
weapon_assign_player,
|
|
24
|
+
)
|
|
18
25
|
from ..persistence.highscores import HighScoreRecord
|
|
19
26
|
from ..perks import PerkId, perk_display_description, perk_display_name
|
|
20
27
|
from ..ui.cursor import draw_aim_cursor, draw_menu_cursor
|
|
@@ -38,6 +45,7 @@ from ..ui.perk_menu import (
|
|
|
38
45
|
ui_scale,
|
|
39
46
|
wrap_ui_text,
|
|
40
47
|
)
|
|
48
|
+
from ..weapons import WEAPON_BY_ID
|
|
41
49
|
from .base_gameplay_mode import BaseGameplayMode, _clamp
|
|
42
50
|
|
|
43
51
|
WORLD_SIZE = 1024.0
|
|
@@ -71,6 +79,8 @@ PERK_PROMPT_LEVEL_UP_SHIFT_Y = -4.0
|
|
|
71
79
|
PERK_PROMPT_TEXT_MARGIN_X = 16.0
|
|
72
80
|
PERK_PROMPT_TEXT_OFFSET_Y = 8.0
|
|
73
81
|
|
|
82
|
+
_DEBUG_WEAPON_IDS = tuple(sorted(WEAPON_BY_ID))
|
|
83
|
+
|
|
74
84
|
|
|
75
85
|
@dataclass(slots=True)
|
|
76
86
|
class _SurvivalState:
|
|
@@ -181,14 +191,38 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
181
191
|
if rl.is_key_pressed(rl.KeyboardKey.KEY_TAB):
|
|
182
192
|
self._paused = not self._paused
|
|
183
193
|
|
|
184
|
-
if debug_enabled() and
|
|
185
|
-
|
|
186
|
-
|
|
194
|
+
if debug_enabled() and (not self._perk_menu_open):
|
|
195
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_F2):
|
|
196
|
+
self._state.debug_god_mode = not bool(self._state.debug_god_mode)
|
|
197
|
+
self._world.audio_router.play_sfx("sfx_ui_buttonclick")
|
|
198
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_F3):
|
|
199
|
+
self._state.perk_selection.pending_count += 1
|
|
200
|
+
self._state.perk_selection.choices_dirty = True
|
|
201
|
+
self._world.audio_router.play_sfx("sfx_ui_levelup")
|
|
202
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_LEFT_BRACKET):
|
|
203
|
+
self._debug_cycle_weapon(-1)
|
|
204
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_RIGHT_BRACKET):
|
|
205
|
+
self._debug_cycle_weapon(1)
|
|
206
|
+
if rl.is_key_pressed(rl.KeyboardKey.KEY_X):
|
|
207
|
+
self._player.experience += 5000
|
|
208
|
+
survival_check_level_up(self._player, self._state.perk_selection)
|
|
187
209
|
|
|
188
210
|
if rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
|
189
211
|
self._action = "open_pause_menu"
|
|
190
212
|
return
|
|
191
213
|
|
|
214
|
+
def _debug_cycle_weapon(self, delta: int) -> None:
|
|
215
|
+
weapon_ids = _DEBUG_WEAPON_IDS
|
|
216
|
+
if not weapon_ids:
|
|
217
|
+
return
|
|
218
|
+
current = int(self._player.weapon_id)
|
|
219
|
+
try:
|
|
220
|
+
idx = weapon_ids.index(current)
|
|
221
|
+
except ValueError:
|
|
222
|
+
idx = 0
|
|
223
|
+
weapon_id = int(weapon_ids[(idx + int(delta)) % len(weapon_ids)])
|
|
224
|
+
weapon_assign_player(self._player, weapon_id, state=self._state)
|
|
225
|
+
|
|
192
226
|
def _build_input(self) -> PlayerInput:
|
|
193
227
|
keybinds = config_keybinds(self._config)
|
|
194
228
|
if not keybinds:
|
|
@@ -779,10 +813,12 @@ class SurvivalMode(BaseGameplayMode):
|
|
|
779
813
|
line = float(self._ui_line_height())
|
|
780
814
|
self._draw_ui_text(f"survival: t={self._survival.elapsed_ms/1000.0:6.1f}s stage={self._survival.stage}", x, y, UI_TEXT_COLOR)
|
|
781
815
|
self._draw_ui_text(f"xp={self._player.experience} level={self._player.level} kills={self._creatures.kill_count}", x, y + line, UI_HINT_COLOR)
|
|
816
|
+
god = "on" if self._state.debug_god_mode else "off"
|
|
817
|
+
self._draw_ui_text(f"debug: [/] weapon F3 perk+1 F2 god={god} X xp+5000", x, y + line * 2.0, UI_HINT_COLOR, scale=0.9)
|
|
782
818
|
if self._paused:
|
|
783
|
-
self._draw_ui_text("paused (TAB)", x, y + line *
|
|
819
|
+
self._draw_ui_text("paused (TAB)", x, y + line * 3.0, UI_HINT_COLOR)
|
|
784
820
|
if self._player.health <= 0.0:
|
|
785
|
-
self._draw_ui_text("game over", x, y + line *
|
|
821
|
+
self._draw_ui_text("game over", x, y + line * 3.0, UI_ERROR_COLOR)
|
|
786
822
|
warn_y = float(rl.get_screen_height()) - 28.0
|
|
787
823
|
if self._world.missing_assets:
|
|
788
824
|
warn = "Missing world assets: " + ", ".join(self._world.missing_assets)
|