crimsonland 0.1.0.dev12__tar.gz → 0.1.0.dev14__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/PKG-INFO +197 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/pyproject.toml +6 -1
- crimsonland-0.1.0.dev14/readme.md +184 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/assets_fetch.py +23 -8
- crimsonland-0.1.0.dev14/src/crimson/frontend/high_scores_layout.py +26 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/frontend/menu.py +22 -20
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/frontend/panels/base.py +14 -26
- crimsonland-0.1.0.dev14/src/crimson/frontend/panels/controls.py +219 -0
- crimsonland-0.1.0.dev14/src/crimson/frontend/panels/credits.py +221 -0
- crimsonland-0.1.0.dev14/src/crimson/frontend/panels/databases.py +307 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/frontend/panels/options.py +4 -3
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/frontend/panels/play_game.py +4 -4
- crimsonland-0.1.0.dev14/src/crimson/frontend/panels/stats.py +308 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/game.py +219 -81
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/modes/quest_mode.py +10 -9
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/modes/survival_mode.py +10 -9
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/modes/tutorial_mode.py +10 -4
- crimsonland-0.1.0.dev14/src/crimson/ui/menu_panel.py +127 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/ui/perk_menu.py +54 -89
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/ui/quest_results.py +24 -18
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/perk_menu_debug.py +2 -2
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/perks.py +2 -2
- crimsonland-0.1.0.dev12/PKG-INFO +0 -9
- crimsonland-0.1.0.dev12/src/crimson/frontend/panels/controls.py +0 -130
- crimsonland-0.1.0.dev12/src/crimson/frontend/panels/stats.py +0 -349
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/atlas.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/audio_router.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/bonuses.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/camera.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/cli.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/creatures/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/creatures/ai.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/creatures/anim.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/creatures/damage.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/creatures/runtime.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/creatures/spawn.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/debug.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/demo.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/demo_trial.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/effects.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/effects_atlas.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/frontend/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/frontend/assets.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/frontend/boot.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/frontend/panels/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/frontend/panels/mods.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/frontend/pause_menu.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/frontend/transitions.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/game_modes.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/game_world.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/gameplay.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/input_codes.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/modes/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/modes/base_gameplay_mode.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/modes/rush_mode.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/modes/typo_mode.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/paths.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/perks.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/persistence/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/persistence/highscores.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/persistence/save_status.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/player_damage.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/projectiles.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/quests/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/quests/helpers.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/quests/registry.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/quests/results.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/quests/runtime.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/quests/tier1.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/quests/tier2.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/quests/tier3.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/quests/tier4.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/quests/tier5.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/quests/timeline.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/quests/types.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/render/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/render/terrain_fx.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/render/world_renderer.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/sim/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/sim/world_defs.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/sim/world_state.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/terrain_assets.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/tutorial/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/tutorial/timeline.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/typo/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/typo/names.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/typo/player.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/typo/spawns.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/typo/typing.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/ui/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/ui/cursor.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/ui/demo_trial_overlay.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/ui/game_over.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/ui/hud.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/ui/shadow.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/aim_debug.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/animations.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/arsenal_debug.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/audio_bootstrap.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/bonuses.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/camera_debug.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/camera_shake.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/corpse_stamp_debug.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/decals_debug.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/empty.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/fonts.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/game_over.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/ground.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/lighting_debug.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/particles.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/player.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/player_sprite_debug.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/projectile_fx.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/projectile_render_debug.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/projectiles.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/quest_title_overlay.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/registry.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/rush.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/small_font_debug.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/spawn_plan.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/sprites.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/survival.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/terrain.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/ui.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/views/wicons.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/weapon_sfx.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/crimson/weapons.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/app.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/assets.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/audio.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/config.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/console.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/fonts/__init__.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/fonts/grim_mono.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/fonts/small.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/input.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/jaz.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/math.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/music.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/paq.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/rand.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/sfx.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/sfx_map.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/terrain_render.py +0 -0
- {crimsonland-0.1.0.dev12 → crimsonland-0.1.0.dev14}/src/grim/view.py +0 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: crimsonland
|
|
3
|
+
Version: 0.1.0.dev14
|
|
4
|
+
Requires-Dist: construct>=2.10.70
|
|
5
|
+
Requires-Dist: pillow>=12.1.0
|
|
6
|
+
Requires-Dist: platformdirs>=4.5.1
|
|
7
|
+
Requires-Dist: raylib>=5.5.0.4
|
|
8
|
+
Requires-Dist: typer>=0.21.1
|
|
9
|
+
Requires-Python: >=3.13
|
|
10
|
+
Project-URL: Documentation, https://crimson.banteg.xyz/
|
|
11
|
+
Project-URL: Repository, https://github.com/banteg/crimson
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# Crimsonland 1.9.93 decompilation + rewrite
|
|
15
|
+
|
|
16
|
+
This repository is a **reverse engineering + high‑fidelity reimplementation** of **Crimsonland 1.9.93 (2003)**.
|
|
17
|
+
|
|
18
|
+
- **Target build:** `v1.9.93` (GOG "Crimsonland Classic") — see [docs/provenance.md](docs/provenance.md) for exact hashes.
|
|
19
|
+
- **Rewrite:** a runnable reference implementation in **Python + raylib** under `src/`.
|
|
20
|
+
- **Analysis:** decompiles, name/type maps, and runtime evidence under `analysis/`.
|
|
21
|
+
- **Docs:** long-form notes and parity tracking under `docs/` (start at [docs/index.md](docs/index.md)).
|
|
22
|
+
|
|
23
|
+
The north star is **behavioral parity** with the original Windows build: timings, RNG, UI/layout quirks, asset decoding, and gameplay rules should match as closely as practical.
|
|
24
|
+
|
|
25
|
+
**[Read the full story](https://banteg.xyz/posts/crimsonland/)** of how this project came together: reverse engineering workflow, custom asset formats, AI-assisted decompilation, and game preservation philosophy.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
Install [uv](https://docs.astral.sh/uv/getting-started/installation/) package manager.
|
|
32
|
+
|
|
33
|
+
### Run the latest packaged build
|
|
34
|
+
|
|
35
|
+
If you just want to play the rewrite:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uvx crimsonland@latest
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Run from a checkout
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
gh repo clone banteg/crimson
|
|
45
|
+
cd crimson
|
|
46
|
+
uv run crimson
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Keep runtime files local to the repo
|
|
50
|
+
|
|
51
|
+
By default, runtime files (e.g. `crimson.cfg`, `game.cfg`, highscores, logs, downloaded PAQs) live in your per-user data dir.
|
|
52
|
+
To keep everything under this checkout:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
export CRIMSON_RUNTIME_DIR="$PWD/artifacts/runtime"
|
|
56
|
+
mkdir -p artifacts/runtime
|
|
57
|
+
uv run crimson
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Assets + binaries
|
|
63
|
+
|
|
64
|
+
There are two separate “inputs” to this repo:
|
|
65
|
+
|
|
66
|
+
1. **Runtime assets for the rewrite** (PAQ archives)
|
|
67
|
+
2. **Original Windows binaries for reverse engineering** (`crimsonland.exe`, `grim.dll`, …)
|
|
68
|
+
|
|
69
|
+
We keep them out of git and expect a local layout like:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
game_bins/
|
|
73
|
+
crimsonland/
|
|
74
|
+
1.9.93-gog/
|
|
75
|
+
crimsonland.exe
|
|
76
|
+
grim.dll
|
|
77
|
+
crimson.paq
|
|
78
|
+
music.paq
|
|
79
|
+
sfx.paq
|
|
80
|
+
artifacts/
|
|
81
|
+
runtime/ # optional: where you run the rewrite (cfg/status/paqs)
|
|
82
|
+
assets/ # optional: extracted PAQs for inspection/tools
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Running the rewrite
|
|
86
|
+
|
|
87
|
+
The rewrite loads the assets from original archives:
|
|
88
|
+
|
|
89
|
+
- `crimson.paq`
|
|
90
|
+
- `music.paq`
|
|
91
|
+
- `sfx.paq`
|
|
92
|
+
|
|
93
|
+
### Extracted assets
|
|
94
|
+
|
|
95
|
+
For inspection/diffs/tools, you can extract PAQs into a filesystem tree:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
uv run crimson extract crimsonland_1.9.93 artifacts/assets
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Same as the original, many loaders can work from either:
|
|
102
|
+
|
|
103
|
+
- **PAQ-backed assets** (preferred when available), or
|
|
104
|
+
- the **extracted filesystem layout** under `artifacts/assets/`.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## CLI cheat sheet
|
|
109
|
+
|
|
110
|
+
Everything is exposed via the `crimson` CLI (alias: `crimsonland`):
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
uv run crimson # run the game (default command)
|
|
114
|
+
uv run crimson view ui # debug views / sandboxes
|
|
115
|
+
uv run crimson quests 1.1 # print quest spawn script
|
|
116
|
+
uv run crimson config # inspect crimson.cfg
|
|
117
|
+
uv run crimson extract <game_dir> artifacts/assets
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Useful flags:
|
|
121
|
+
|
|
122
|
+
- `--base-dir PATH` / `CRIMSON_RUNTIME_DIR=...` — where saves/config/logs live
|
|
123
|
+
- `--assets-dir PATH` — where `.paq` archives (or extracted assets) are loaded from
|
|
124
|
+
- `--seed N` — deterministic runs for parity testing
|
|
125
|
+
- `--demo` — enable shareware/demo paths
|
|
126
|
+
- `--no-intro` — skip logos/intro music
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Docs
|
|
131
|
+
|
|
132
|
+
Docs are authored in `docs/` and built as a static site at https://crimson.banteg.xyz/
|
|
133
|
+
|
|
134
|
+
For development, it's useful to have a live local build:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
uv tool install zensical
|
|
138
|
+
zensical serve
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Development
|
|
144
|
+
|
|
145
|
+
### Tests
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
uv run pytest
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Lint / checks
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
uv run lint-imports
|
|
155
|
+
uv run python scripts/check_asset_loader_usage.py
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### `justfile` shortcuts
|
|
159
|
+
|
|
160
|
+
If you have `just` installed:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
just --list
|
|
164
|
+
just test
|
|
165
|
+
just docs-build
|
|
166
|
+
just ghidra-exe
|
|
167
|
+
just ghidra-grim
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Reverse engineering workflow
|
|
173
|
+
|
|
174
|
+
High level:
|
|
175
|
+
|
|
176
|
+
- **Static analysis is the source of truth.**
|
|
177
|
+
- Update names/types in [analysis/ghidra/maps/](analysis/ghidra/maps/).
|
|
178
|
+
- Treat [analysis/ghidra/raw/](analysis/ghidra/raw/) as generated output (regenerate; do not hand-edit).
|
|
179
|
+
- **Runtime tooling** (Frida / WinDbg) validates ambiguous behavior and captures ground truth.
|
|
180
|
+
- Evidence summaries live under [analysis/frida/](analysis/frida/).
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Contributing notes
|
|
185
|
+
|
|
186
|
+
- Keep changes small and reviewable (one subsystem/feature at a time).
|
|
187
|
+
- Prefer *measured parity* (captures/logs/deterministic tests) over “looks right”.
|
|
188
|
+
- When porting float constants from decompilation, prefer the intended value
|
|
189
|
+
(e.g. `0.6` instead of `0.6000000238418579` when it’s clearly a float32 artifact).
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Legal
|
|
194
|
+
|
|
195
|
+
This project is an independent reverse engineering and reimplementation effort for preservation, research, and compatibility.
|
|
196
|
+
|
|
197
|
+
No original assets or binaries are included. Use your own legally obtained copy.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "crimsonland"
|
|
3
|
-
version = "0.1.0.
|
|
3
|
+
version = "0.1.0.dev14"
|
|
4
|
+
readme = { file = "readme.md", content-type = "text/markdown" }
|
|
4
5
|
requires-python = ">=3.13"
|
|
5
6
|
dependencies = [
|
|
6
7
|
"construct>=2.10.70",
|
|
@@ -10,6 +11,10 @@ dependencies = [
|
|
|
10
11
|
"typer>=0.21.1",
|
|
11
12
|
]
|
|
12
13
|
|
|
14
|
+
[project.urls]
|
|
15
|
+
Documentation = "https://crimson.banteg.xyz/"
|
|
16
|
+
Repository = "https://github.com/banteg/crimson"
|
|
17
|
+
|
|
13
18
|
[project.scripts]
|
|
14
19
|
crimsonland = "crimson.cli:main"
|
|
15
20
|
crimson = "crimson.cli:main"
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Crimsonland 1.9.93 decompilation + rewrite
|
|
2
|
+
|
|
3
|
+
This repository is a **reverse engineering + high‑fidelity reimplementation** of **Crimsonland 1.9.93 (2003)**.
|
|
4
|
+
|
|
5
|
+
- **Target build:** `v1.9.93` (GOG "Crimsonland Classic") — see [docs/provenance.md](docs/provenance.md) for exact hashes.
|
|
6
|
+
- **Rewrite:** a runnable reference implementation in **Python + raylib** under `src/`.
|
|
7
|
+
- **Analysis:** decompiles, name/type maps, and runtime evidence under `analysis/`.
|
|
8
|
+
- **Docs:** long-form notes and parity tracking under `docs/` (start at [docs/index.md](docs/index.md)).
|
|
9
|
+
|
|
10
|
+
The north star is **behavioral parity** with the original Windows build: timings, RNG, UI/layout quirks, asset decoding, and gameplay rules should match as closely as practical.
|
|
11
|
+
|
|
12
|
+
**[Read the full story](https://banteg.xyz/posts/crimsonland/)** of how this project came together: reverse engineering workflow, custom asset formats, AI-assisted decompilation, and game preservation philosophy.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
Install [uv](https://docs.astral.sh/uv/getting-started/installation/) package manager.
|
|
19
|
+
|
|
20
|
+
### Run the latest packaged build
|
|
21
|
+
|
|
22
|
+
If you just want to play the rewrite:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uvx crimsonland@latest
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Run from a checkout
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
gh repo clone banteg/crimson
|
|
32
|
+
cd crimson
|
|
33
|
+
uv run crimson
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Keep runtime files local to the repo
|
|
37
|
+
|
|
38
|
+
By default, runtime files (e.g. `crimson.cfg`, `game.cfg`, highscores, logs, downloaded PAQs) live in your per-user data dir.
|
|
39
|
+
To keep everything under this checkout:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
export CRIMSON_RUNTIME_DIR="$PWD/artifacts/runtime"
|
|
43
|
+
mkdir -p artifacts/runtime
|
|
44
|
+
uv run crimson
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Assets + binaries
|
|
50
|
+
|
|
51
|
+
There are two separate “inputs” to this repo:
|
|
52
|
+
|
|
53
|
+
1. **Runtime assets for the rewrite** (PAQ archives)
|
|
54
|
+
2. **Original Windows binaries for reverse engineering** (`crimsonland.exe`, `grim.dll`, …)
|
|
55
|
+
|
|
56
|
+
We keep them out of git and expect a local layout like:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
game_bins/
|
|
60
|
+
crimsonland/
|
|
61
|
+
1.9.93-gog/
|
|
62
|
+
crimsonland.exe
|
|
63
|
+
grim.dll
|
|
64
|
+
crimson.paq
|
|
65
|
+
music.paq
|
|
66
|
+
sfx.paq
|
|
67
|
+
artifacts/
|
|
68
|
+
runtime/ # optional: where you run the rewrite (cfg/status/paqs)
|
|
69
|
+
assets/ # optional: extracted PAQs for inspection/tools
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Running the rewrite
|
|
73
|
+
|
|
74
|
+
The rewrite loads the assets from original archives:
|
|
75
|
+
|
|
76
|
+
- `crimson.paq`
|
|
77
|
+
- `music.paq`
|
|
78
|
+
- `sfx.paq`
|
|
79
|
+
|
|
80
|
+
### Extracted assets
|
|
81
|
+
|
|
82
|
+
For inspection/diffs/tools, you can extract PAQs into a filesystem tree:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
uv run crimson extract crimsonland_1.9.93 artifacts/assets
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Same as the original, many loaders can work from either:
|
|
89
|
+
|
|
90
|
+
- **PAQ-backed assets** (preferred when available), or
|
|
91
|
+
- the **extracted filesystem layout** under `artifacts/assets/`.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## CLI cheat sheet
|
|
96
|
+
|
|
97
|
+
Everything is exposed via the `crimson` CLI (alias: `crimsonland`):
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv run crimson # run the game (default command)
|
|
101
|
+
uv run crimson view ui # debug views / sandboxes
|
|
102
|
+
uv run crimson quests 1.1 # print quest spawn script
|
|
103
|
+
uv run crimson config # inspect crimson.cfg
|
|
104
|
+
uv run crimson extract <game_dir> artifacts/assets
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Useful flags:
|
|
108
|
+
|
|
109
|
+
- `--base-dir PATH` / `CRIMSON_RUNTIME_DIR=...` — where saves/config/logs live
|
|
110
|
+
- `--assets-dir PATH` — where `.paq` archives (or extracted assets) are loaded from
|
|
111
|
+
- `--seed N` — deterministic runs for parity testing
|
|
112
|
+
- `--demo` — enable shareware/demo paths
|
|
113
|
+
- `--no-intro` — skip logos/intro music
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Docs
|
|
118
|
+
|
|
119
|
+
Docs are authored in `docs/` and built as a static site at https://crimson.banteg.xyz/
|
|
120
|
+
|
|
121
|
+
For development, it's useful to have a live local build:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
uv tool install zensical
|
|
125
|
+
zensical serve
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Development
|
|
131
|
+
|
|
132
|
+
### Tests
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
uv run pytest
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Lint / checks
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
uv run lint-imports
|
|
142
|
+
uv run python scripts/check_asset_loader_usage.py
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### `justfile` shortcuts
|
|
146
|
+
|
|
147
|
+
If you have `just` installed:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
just --list
|
|
151
|
+
just test
|
|
152
|
+
just docs-build
|
|
153
|
+
just ghidra-exe
|
|
154
|
+
just ghidra-grim
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Reverse engineering workflow
|
|
160
|
+
|
|
161
|
+
High level:
|
|
162
|
+
|
|
163
|
+
- **Static analysis is the source of truth.**
|
|
164
|
+
- Update names/types in [analysis/ghidra/maps/](analysis/ghidra/maps/).
|
|
165
|
+
- Treat [analysis/ghidra/raw/](analysis/ghidra/raw/) as generated output (regenerate; do not hand-edit).
|
|
166
|
+
- **Runtime tooling** (Frida / WinDbg) validates ambiguous behavior and captures ground truth.
|
|
167
|
+
- Evidence summaries live under [analysis/frida/](analysis/frida/).
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Contributing notes
|
|
172
|
+
|
|
173
|
+
- Keep changes small and reviewable (one subsystem/feature at a time).
|
|
174
|
+
- Prefer *measured parity* (captures/logs/deterministic tests) over “looks right”.
|
|
175
|
+
- When porting float constants from decompilation, prefer the intended value
|
|
176
|
+
(e.g. `0.6` instead of `0.6000000238418579` when it’s clearly a float32 artifact).
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Legal
|
|
181
|
+
|
|
182
|
+
This project is an independent reverse engineering and reimplementation effort for preservation, research, and compatibility.
|
|
183
|
+
|
|
184
|
+
No original assets or binaries are included. Use your own legally obtained copy.
|
|
@@ -2,7 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
import os
|
|
5
6
|
import shutil
|
|
7
|
+
import tempfile
|
|
6
8
|
import urllib.request
|
|
7
9
|
|
|
8
10
|
from grim.console import ConsoleState
|
|
@@ -19,17 +21,30 @@ class DownloadResult:
|
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
def _download_file(url: str, dest: Path) -> None:
|
|
22
|
-
|
|
23
|
-
if tmp.exists():
|
|
24
|
-
tmp.unlink()
|
|
24
|
+
tmp_path: Path | None = None
|
|
25
25
|
try:
|
|
26
26
|
req = urllib.request.Request(url, headers={"User-Agent": "crimsonland-decompile"})
|
|
27
|
-
with urllib.request.urlopen(req, timeout=30) as resp
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
28
|
+
with tempfile.NamedTemporaryFile(
|
|
29
|
+
mode="wb",
|
|
30
|
+
delete=False,
|
|
31
|
+
dir=dest.parent,
|
|
32
|
+
prefix=dest.name + ".",
|
|
33
|
+
suffix=".tmp",
|
|
34
|
+
) as handle:
|
|
35
|
+
tmp_path = Path(handle.name)
|
|
36
|
+
shutil.copyfileobj(resp, handle)
|
|
37
|
+
handle.flush()
|
|
38
|
+
os.fsync(handle.fileno())
|
|
39
|
+
if tmp_path is None:
|
|
40
|
+
raise RuntimeError("assets: temporary file not created")
|
|
41
|
+
tmp_path.replace(dest)
|
|
30
42
|
finally:
|
|
31
|
-
if
|
|
32
|
-
|
|
43
|
+
if tmp_path is not None:
|
|
44
|
+
try:
|
|
45
|
+
tmp_path.unlink()
|
|
46
|
+
except FileNotFoundError:
|
|
47
|
+
pass
|
|
33
48
|
|
|
34
49
|
|
|
35
50
|
def download_missing_paqs(
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Layout constants for the classic high scores screen (state_id=14).
|
|
3
|
+
|
|
4
|
+
Measured from analysis/frida/ui_render_trace_oracle_1024x768.json.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
# Panel positions are expressed in "panel pos" (pre-offset) space, matching other menu panels:
|
|
10
|
+
# panel_top_left = (panel_pos_x + MENU_PANEL_OFFSET_X, panel_pos_y + y_shift + MENU_PANEL_OFFSET_Y)
|
|
11
|
+
|
|
12
|
+
HS_LEFT_PANEL_POS_X = -119.0
|
|
13
|
+
HS_LEFT_PANEL_POS_Y = 185.0
|
|
14
|
+
HS_LEFT_PANEL_HEIGHT = 378.0
|
|
15
|
+
|
|
16
|
+
HS_RIGHT_PANEL_POS_X = 609.0
|
|
17
|
+
HS_RIGHT_PANEL_POS_Y = 200.0
|
|
18
|
+
HS_RIGHT_PANEL_HEIGHT = 254.0
|
|
19
|
+
|
|
20
|
+
# Buttons inside the left panel (relative to the left panel top-left).
|
|
21
|
+
HS_BUTTON_X = 234.0 # x0=136 at 1024x768
|
|
22
|
+
HS_BUTTON_Y0 = 268.0 # y0=462
|
|
23
|
+
HS_BUTTON_STEP_Y = 33.0
|
|
24
|
+
|
|
25
|
+
HS_BACK_BUTTON_X = 400.0 # x0=302
|
|
26
|
+
HS_BACK_BUTTON_Y = 301.0 # y0=495
|
|
@@ -19,8 +19,8 @@ if TYPE_CHECKING:
|
|
|
19
19
|
from ..game import GameState
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
MENU_LABEL_WIDTH =
|
|
23
|
-
MENU_LABEL_HEIGHT =
|
|
22
|
+
MENU_LABEL_WIDTH = 122.0
|
|
23
|
+
MENU_LABEL_HEIGHT = 28.0
|
|
24
24
|
MENU_LABEL_ROW_HEIGHT = 32.0
|
|
25
25
|
MENU_LABEL_ROW_PLAY_GAME = 1
|
|
26
26
|
MENU_LABEL_ROW_OPTIONS = 2
|
|
@@ -31,18 +31,17 @@ MENU_LABEL_ROW_QUIT = 6
|
|
|
31
31
|
MENU_LABEL_ROW_BACK = 7
|
|
32
32
|
MENU_LABEL_BASE_X = -60.0
|
|
33
33
|
MENU_LABEL_BASE_Y = 210.0
|
|
34
|
-
MENU_LABEL_OFFSET_X =
|
|
35
|
-
MENU_LABEL_OFFSET_Y = -
|
|
34
|
+
MENU_LABEL_OFFSET_X = 271.0
|
|
35
|
+
MENU_LABEL_OFFSET_Y = -37.0
|
|
36
36
|
MENU_LABEL_STEP = 60.0
|
|
37
|
-
MENU_ITEM_OFFSET_X = -
|
|
38
|
-
MENU_ITEM_OFFSET_Y = -
|
|
39
|
-
MENU_PANEL_WIDTH =
|
|
40
|
-
MENU_PANEL_HEIGHT =
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
MENU_PANEL_OFFSET_Y = -82.0
|
|
37
|
+
MENU_ITEM_OFFSET_X = -71.0
|
|
38
|
+
MENU_ITEM_OFFSET_Y = -59.0
|
|
39
|
+
MENU_PANEL_WIDTH = 510.0
|
|
40
|
+
MENU_PANEL_HEIGHT = 254.0
|
|
41
|
+
# Measured from ui_render_trace at 1024x768 (stable timeline):
|
|
42
|
+
# panel top-left is (pos_x + 21, pos_y - 81) and size is 510x254, plus a shadow pass at +7,+7.
|
|
43
|
+
MENU_PANEL_OFFSET_X = 21.0
|
|
44
|
+
MENU_PANEL_OFFSET_Y = -81.0
|
|
46
45
|
MENU_PANEL_BASE_X = -45.0
|
|
47
46
|
MENU_PANEL_BASE_Y = 210.0
|
|
48
47
|
MENU_SCALE_SMALL_THRESHOLD = 640
|
|
@@ -52,10 +51,10 @@ MENU_SCALE_SMALL = 0.8
|
|
|
52
51
|
MENU_SCALE_LARGE = 1.2
|
|
53
52
|
MENU_SCALE_SHIFT = 10.0
|
|
54
53
|
|
|
55
|
-
MENU_SIGN_WIDTH =
|
|
56
|
-
MENU_SIGN_HEIGHT =
|
|
57
|
-
MENU_SIGN_OFFSET_X = -
|
|
58
|
-
MENU_SIGN_OFFSET_Y = -
|
|
54
|
+
MENU_SIGN_WIDTH = 571.44
|
|
55
|
+
MENU_SIGN_HEIGHT = 141.36
|
|
56
|
+
MENU_SIGN_OFFSET_X = -576.44
|
|
57
|
+
MENU_SIGN_OFFSET_Y = -61.0
|
|
59
58
|
MENU_SIGN_POS_Y = 70.0
|
|
60
59
|
MENU_SIGN_POS_Y_SMALL = 60.0
|
|
61
60
|
MENU_SIGN_POS_X_PAD = 4.0
|
|
@@ -584,21 +583,24 @@ class MenuView:
|
|
|
584
583
|
start_ms: int,
|
|
585
584
|
end_ms: int,
|
|
586
585
|
width: float,
|
|
586
|
+
direction_flag: int = 0,
|
|
587
587
|
) -> tuple[float, float]:
|
|
588
588
|
# Matches ui_element_update: angle lerps pi/2 -> 0 over [end_ms, start_ms].
|
|
589
|
-
#
|
|
589
|
+
# direction_flag=0 slides from left (-width -> 0)
|
|
590
|
+
# direction_flag=1 slides from right (+width -> 0)
|
|
590
591
|
if start_ms <= end_ms or width <= 0.0:
|
|
591
592
|
return 0.0, 0.0
|
|
593
|
+
dir_sign = 1.0 if int(direction_flag) else -1.0
|
|
592
594
|
t = self._timeline_ms
|
|
593
595
|
if t < end_ms:
|
|
594
596
|
angle = 1.5707964
|
|
595
|
-
offset_x =
|
|
597
|
+
offset_x = dir_sign * abs(width)
|
|
596
598
|
elif t < start_ms:
|
|
597
599
|
elapsed = t - end_ms
|
|
598
600
|
span = float(start_ms - end_ms)
|
|
599
601
|
p = float(elapsed) / span
|
|
600
602
|
angle = 1.5707964 * (1.0 - p)
|
|
601
|
-
offset_x =
|
|
603
|
+
offset_x = dir_sign * ((1.0 - p) * abs(width))
|
|
602
604
|
else:
|
|
603
605
|
angle = 0.0
|
|
604
606
|
offset_x = 0.0
|
|
@@ -8,6 +8,7 @@ from grim.assets import PaqTextureCache
|
|
|
8
8
|
from grim.audio import play_sfx, update_audio
|
|
9
9
|
from grim.terrain_render import GroundRenderer
|
|
10
10
|
|
|
11
|
+
from ...ui.menu_panel import draw_classic_menu_panel
|
|
11
12
|
from ..assets import MenuAssets, _ensure_texture_cache, load_menu_assets
|
|
12
13
|
from ..menu import (
|
|
13
14
|
MENU_ITEM_OFFSET_X,
|
|
@@ -70,6 +71,9 @@ class PanelMenuView:
|
|
|
70
71
|
body: str | None = None,
|
|
71
72
|
panel_pos_x: float = PANEL_POS_X,
|
|
72
73
|
panel_pos_y: float = PANEL_POS_Y,
|
|
74
|
+
panel_offset_x: float = MENU_PANEL_OFFSET_X,
|
|
75
|
+
panel_offset_y: float = MENU_PANEL_OFFSET_Y,
|
|
76
|
+
panel_height: float = MENU_PANEL_HEIGHT,
|
|
73
77
|
back_pos_x: float = PANEL_BACK_POS_X,
|
|
74
78
|
back_pos_y: float = PANEL_BACK_POS_Y,
|
|
75
79
|
back_action: str = "back_to_menu",
|
|
@@ -79,6 +83,9 @@ class PanelMenuView:
|
|
|
79
83
|
self._body_lines = (body or "").splitlines()
|
|
80
84
|
self._panel_pos_x = panel_pos_x
|
|
81
85
|
self._panel_pos_y = panel_pos_y
|
|
86
|
+
self._panel_offset_x = panel_offset_x
|
|
87
|
+
self._panel_offset_y = panel_offset_y
|
|
88
|
+
self._panel_height = panel_height
|
|
82
89
|
self._back_pos_x = back_pos_x
|
|
83
90
|
self._back_pos_y = back_pos_y
|
|
84
91
|
self._back_action = back_action
|
|
@@ -223,40 +230,21 @@ class PanelMenuView:
|
|
|
223
230
|
if assets is None or assets.panel is None:
|
|
224
231
|
return
|
|
225
232
|
panel = assets.panel
|
|
226
|
-
panel_w = MENU_PANEL_WIDTH
|
|
227
|
-
panel_h = MENU_PANEL_HEIGHT
|
|
228
233
|
_angle_rad, slide_x = MenuView._ui_element_anim(
|
|
229
234
|
self,
|
|
230
235
|
index=1,
|
|
231
236
|
start_ms=PANEL_TIMELINE_START_MS,
|
|
232
237
|
end_ms=PANEL_TIMELINE_END_MS,
|
|
233
|
-
width=
|
|
238
|
+
width=MENU_PANEL_WIDTH * self._menu_item_scale(0)[0],
|
|
234
239
|
)
|
|
235
240
|
item_scale, _local_y_shift = self._menu_item_scale(0)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
)
|
|
242
|
-
origin = rl.Vector2(-(MENU_PANEL_OFFSET_X * item_scale), -(MENU_PANEL_OFFSET_Y * item_scale))
|
|
241
|
+
panel_w = MENU_PANEL_WIDTH * item_scale
|
|
242
|
+
panel_h = float(self._panel_height) * item_scale
|
|
243
|
+
top_left_x = self._panel_pos_x + slide_x + self._panel_offset_x * item_scale
|
|
244
|
+
top_left_y = self._panel_pos_y + self._widescreen_y_shift + self._panel_offset_y * item_scale
|
|
245
|
+
dst = rl.Rectangle(float(top_left_x), float(top_left_y), float(panel_w), float(panel_h))
|
|
243
246
|
fx_detail = bool(self._state.config.data.get("fx_detail_0", 0))
|
|
244
|
-
|
|
245
|
-
MenuView._draw_ui_quad_shadow(
|
|
246
|
-
texture=panel,
|
|
247
|
-
src=rl.Rectangle(0.0, 0.0, float(panel.width), float(panel.height)),
|
|
248
|
-
dst=rl.Rectangle(dst.x + UI_SHADOW_OFFSET, dst.y + UI_SHADOW_OFFSET, dst.width, dst.height),
|
|
249
|
-
origin=origin,
|
|
250
|
-
rotation_deg=0.0,
|
|
251
|
-
)
|
|
252
|
-
MenuView._draw_ui_quad(
|
|
253
|
-
texture=panel,
|
|
254
|
-
src=rl.Rectangle(0.0, 0.0, float(panel.width), float(panel.height)),
|
|
255
|
-
dst=dst,
|
|
256
|
-
origin=origin,
|
|
257
|
-
rotation_deg=0.0,
|
|
258
|
-
tint=rl.WHITE,
|
|
259
|
-
)
|
|
247
|
+
draw_classic_menu_panel(panel, dst=dst, tint=rl.WHITE, shadow=fx_detail)
|
|
260
248
|
|
|
261
249
|
def _draw_entry(self, entry: MenuEntry) -> None:
|
|
262
250
|
assets = self._assets
|