Add web-based save editor for "Reksio i Czarodzieje"
- Implemented a local web server using http.server to serve the save editor. - Created a user interface with HTML, CSS, and JavaScript for managing game slots and editing save files. - Added API endpoints for retrieving slot information, overview, and raw data for editing. - Implemented functionality for saving changes back to the game files. - Added tests for ARR and DTA formats to ensure data integrity during round-trip serialization. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
# ricsave — edytor zapisów „Reksio i Czarodzieje"
|
||||
|
||||
Narzędzie debugowe do zapisów gry z 2004 r. (silnik PIK, `PIKLIB8.dll`). Gra nie ma
|
||||
jednego pliku zapisu — stan to **76 plików na slot** w katalogu `common/`, kopiowanych
|
||||
plik-po-pliku. Ta biblioteka czyta oba formaty, waliduje niezmienniki i daje UI.
|
||||
|
||||
Rozpoznanie formatu: **[docs/format-zapisow.md](docs/format-zapisow.md)** — to jest
|
||||
właściwa dokumentacja, tu tylko obsługa.
|
||||
|
||||
## Start
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
|
||||
export RIC_GAME="$HOME/Reksio/Reksio i Czarodzieje"
|
||||
|
||||
ricsave info # przegląd slotów
|
||||
ricsave serve # edytor w przeglądarce (127.0.0.1:8765)
|
||||
```
|
||||
|
||||
Rdzeń (`ricsave/`) nie ma żadnych zależności — serwer chodzi na `http.server`
|
||||
ze stdliba, front to trzy pliki statyczne bez frameworka.
|
||||
|
||||
**Zrób kopię zanim cokolwiek zapiszesz:** `ricsave backup ~/kopia-common`.
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
ricsave info # które sloty zajęte, gdzie stoisz
|
||||
ricsave validate 1 # niezmienniki, które gra połyka po cichu
|
||||
ricsave gamesets 1 # GAME1.ARR z rozwiniętymi nazwami pól
|
||||
ricsave scene 1 GULDRYK --explain # tabela obiektów lokacji + legenda TYPE
|
||||
ricsave items 1 # ekwipunek
|
||||
ricsave dump 1 --key scene:PIWNICA # JSON
|
||||
ricsave diff 0 1 -v # co się różni między slotami
|
||||
ricsave set 0 GULDRYK KULA_SEN IPARAM0=1 # punktowa zmiana pola
|
||||
ricsave copy 1 0 # slot 1 → żywy stan (to samo co LOADGAME)
|
||||
ricsave newgame # reset żywego stanu z szablonów _DEF
|
||||
```
|
||||
|
||||
`diff` jest tu główną bronią: zapisz przed i po jakiejś akcji w grze, porównaj sloty
|
||||
i od razu widzisz, które pole za nią odpowiada.
|
||||
|
||||
## Biblioteka
|
||||
|
||||
```python
|
||||
from ricsave import Game
|
||||
|
||||
game = Game("~/Reksio/Reksio i Czarodzieje")
|
||||
slot = game.slot(0) # 0 = żywy stan, 1..4 = sloty menu
|
||||
|
||||
slot.summary()
|
||||
slot.validate() # [] gdy OK
|
||||
|
||||
scene = slot.scene("PIWNICA")
|
||||
i = scene.find("GARNEK") # po IDNAME
|
||||
scene.set(i, "IPARAM0", 1)
|
||||
slot.touch("scene:PIWNICA")
|
||||
slot.flush() # zapisuje tylko dotknięte pliki
|
||||
```
|
||||
|
||||
## Model slotów — jedyna rzecz, którą trzeba zrozumieć
|
||||
|
||||
```
|
||||
slot 0 ←→ slot 1..4
|
||||
↑ ↑
|
||||
| └─ cztery pozycje z menu gry
|
||||
└─ ŻYWY STAN bieżącej rozgrywki
|
||||
```
|
||||
|
||||
Gra w trakcie grania czyta i pisze **wyłącznie slot 0**. Menu „Zapisz"/„Wczytaj" to
|
||||
zwykłe kopiowanie plików między 0 a n. Praktyczny wniosek: edycja slotu 3 nie zrobi
|
||||
nic, dopóki nie wczytasz go w grze (albo nie zrobisz `ricsave copy 3 0`).
|
||||
|
||||
## Czego pilnować przy edycji
|
||||
|
||||
- **Nie dodawaj ani nie usuwaj wierszy `.DTA`.** Skrypty scen adresują je po indeksie
|
||||
(`DBOBJECTS^SELECT(10)`), więc przesunięcie rozjeżdża logikę. `ricsave validate`
|
||||
i UI blokują zmianę liczby wierszy.
|
||||
- **Za krótkie `GAME<n>.ARR` lub `SPELLS<n>.ARR` gra połknie po cichu** i zresetuje
|
||||
do domyślnych — nie zobaczysz błędu, tylko utratę pozycji.
|
||||
- **`EMPTY = 1` w `ITEMS` oznacza slot ZAJĘTY**, nie pusty.
|
||||
- Edytuj przy zamkniętej grze — `SAVEGAME` nadpisuje slot bez pytania.
|
||||
|
||||
## Testy
|
||||
|
||||
```bash
|
||||
RIC_GAME="$HOME/Reksio/Reksio i Czarodzieje" .venv/bin/python -m pytest
|
||||
```
|
||||
|
||||
Kluczowe są testy korpusowe: przejeżdżają po wszystkich plikach `.ARR`/`.DTA`
|
||||
w `common/` i sprawdzają round-trip **bajt w bajt**. Jeśli parser i writer się
|
||||
rozjadą, edytor po cichu zepsułby zapis — to ta siatka bezpieczeństwa.
|
||||
|
||||
## Układ
|
||||
|
||||
```
|
||||
ricsave/
|
||||
arr.py ARRAY (.ARR/.SAV) — binarny, int32 + string
|
||||
dta.py DATABASE (.DTA) — CRLF + pionowa kreska
|
||||
catalog.py manifest slotu: 52 lokacje, 11 postaci, 6 minigier
|
||||
schema.py kolumny STRUCT + znaczenia pól ARR
|
||||
slot.py Game / Slot / widoki / operacje SAVEGAME-LOADGAME-NEWGAME
|
||||
cli.py
|
||||
webapp/
|
||||
server.py http.server + JSON API
|
||||
static/ index.html, app.js, style.css
|
||||
docs/
|
||||
format-zapisow.md
|
||||
```
|
||||
|
||||
## Co jeszcze nie jest zdekodowane
|
||||
|
||||
- Znaczenie 17 pól `SPELLS<n>.ARR` poza indeksami 12 i 15
|
||||
- `INVEST<n>.ARR` — karta śledztwa, ≥8 elementów (`INVESTIGATION.class:622`)
|
||||
- `IPARAM0/1/2` dla obiektów scen z `TYPE < 100` innym niż 2 — znaczą co innego
|
||||
w każdej lokacji, trzeba czytać `.cnv` sceny
|
||||
- `G_ITKTPM` — losowane 0..7 przy nowej grze, rola nieustalona
|
||||
- Element 0 w `SETTINGS.ARR` (element 1 to głośność 0..800)
|
||||
Reference in New Issue
Block a user