Files

268 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Reksio i Czarodzieje — model zapisu gry
Rozpoznanie na podstawie zdekodowanych skryptów (`skrypty_zdekodowane/ric`) i plików gry
(`Reksio i Czarodzieje/`). Wersja gry: `Czarodzieje.exe` z 2004-05-19, PIKLIB8.dll.
## 1. Gdzie leży stan gry
Wszystko siedzi w jednym katalogu: `<gra>/common/` (w skryptach: `$COMMON\`).
Nie ma żadnej dedykowanej klasy `SAVEGAME` ani jednego pliku zapisu — stan to
**~76 osobnych plików na slot**, kopiowanych plik-po-pliku przez `COPYFILE`.
Sloty są zakodowane **cyfrą doklejoną do nazwy pliku**:
| sufiks | znaczenie |
|---|---|
| `_DEF` | fabryczny stan początkowy (nie ruszać — to szablon dla „Nowa gra") |
| `0` | **żywy stan bieżącej rozgrywki** — to czyta i zapisuje gra w trakcie grania |
| `1``4` | cztery sloty zapisu z menu |
Kluczowy wniosek dla edytora: **slot 0 to nie jest „pierwszy zapis", tylko working set.**
Wejście do gry po edycji slotu 1 wymaga wczytania go z menu (albo skopiowania `1 → 0`
samemu, co robi rutyna `LOADGAME`).
## 2. Dwa formaty plików
### 2.1 `.ARR` / `.SAV` — obiekt `ARRAY` (binarny)
Little-endian, bez nagłówka i bez wersji:
```
uint32 count
count × {
uint32 type // 1 = INTEGER, 2 = STRING
type==1: int32 value
type==2: uint32 len; byte[len] value // bez terminatora, cp1250
}
```
Zweryfikowane na wszystkich 66 plikach `.arr` w `common/` — round-trip bajt w bajt,
występują wyłącznie typy 1 i 2. Pliki `*.SAV` w katalogach scen (`Dragon/DRAGON_0.SAV`,
`Labirynt/LABIRYNT_0.SAV`) to **ten sam format** — to zwykłe `ARRAY^SAVE()`.
Uwaga: liczby bywają zapisane jako **string** (typ 2), bo silnik konwertuje w locie —
np. `GAME0.ARR` trzyma `G_ITKTPM` jako `"6"`. Nie zakładaj typu po znaczeniu pola.
### 2.2 `.DTA` — obiekt `DATABASE` (tekstowy)
Wiersze rozdzielone `\r\n`, kolumny `|`, brak nagłówka, brak cudzysłowów, pusta wartość
to literalne `NULL`. Kodowanie cp1250 (w praktyce w zapisach jest samo ASCII).
Drobiazg do round-tripu: pliki `*_def.dta` (autorskie) **nie mają** końcowego `\r\n`,
a te zapisane przez grę (`*0.DTA`) **mają**. Parser musi tolerować oba, writer domyślnie
emituje wariant „gry".
Schemat kolumn nie jest w pliku — siedzi w definicji `STRUCT` w `.cnv`
(`Arcade.cnv:1455-1489`):
```
SOBJECT (sceny): NAME<STRING> | IDNAME<STRING> | TYPE<INTEGER>
| SPARAM0<STRING> | SPARAM1<STRING> | SPARAM2<STRING>
| IPARAM0<INTEGER> | IPARAM1<INTEGER> | IPARAM2<INTEGER>
SITEM (ekwipunek): GUID<INTEGER> | NAME<STRING> | PARENT<INTEGER>
| BASE<STRING> | EMPTY<INTEGER>
```
## 3. Manifest slotu
Źródło: `MAINMENU.class``SAVEGAME` (linia 667) i `LOADGAME` (linia 672).
| plik | typ | liczba | co trzyma |
|---|---|---|---|
| `<SCENA><s>.DTA` | DTA/SOBJECT | 52 | stan obiektów każdej lokacji |
| `ITEMS<s>.DTA` | DTA/SITEM | 1 | ekwipunek (10 stałych slotów) |
| `GAME<s>.ARR` | ARR | 1 | globalne zmienne rozgrywki (6 pól) |
| `SPELLS<s>.ARR` | ARR | 1 | 17 intów — znane czary |
| `CUTSCENES<s>.ARR` | ARR | 1 | obejrzane przerywniki |
| `INVEST<s>.ARR` | ARR | 1 | stan karty śledztwa (`INVESTIGATION.class`) |
| `QUESTIONS<s>.ARR` | ARR | 1 | pytania, które Reksio *może* zadać |
| `QUESTIONS_ASKED<s>.ARR` | ARR | 1 | pytania już zadane (globalnie) |
| `<POSTAĆ>_HIST<s>.ARR` | ARR | 11 | historia rozmowy z daną postacią |
| `<MINIGRA><s>.ARR` | ARR | 6 | postęp minigier |
| `m_shot<s>.img` | IMG | 1 | miniaturka zapisu (tylko sloty 14) |
**52 lokacje** (kolejność z `G_ARRDATAS`, `Game.cnv:BFITMP125`):
`PODWIECZOREK1/2/3, TRZYWEJSCIA, DOMALCHOMIKA, GABINETDYR, HOLGLOWNY, UNIVFRONT,
BIALO, MIASTO, LUSTRO, TELEP_PODWORKO, TELEP_WARSZTAT, TELEP_KURNIKI, PIWNICA,
ALCHEMY, RATUSZ, LABIRYNTH, PIKLIBIA, DOMSPIEL, PRZEPASC, MIOTLISKO, KAPTUREK,
CALINECZKA, DRATEWKA, SNIEZKA, KROLEWNA, JAGA, OLBRZYM, SZREKSIO, TROL, NORASZ,
SEZAM, PRZEDSEZAMEM, KROLOWA, SMOK, RATUSZIN, GULDRYK, KORYTARZ, SALA, UNIVBACK,
UNIVPIWNICA, MIOTSHOP, PRZYSTAN, GABINETSNEJKA, BARANMIOT, WMWEJSCIE, WMWNETRZE,
PIKMIOT, SKLEPALCH, ARRAS, POMPA`
**11 postaci dialogowych** (`M_ARRCHARACTERS`):
`BUREKTOR, GULDRYK, SNEJK, WALDIMORS, SPIELMAUSTER, CHRUMBURAK, BARANDALF,
KROLOWA, KAMIEN, SMOK, GASIENICA`
**6 minigier** (`M_ARRPIOTR`): `LABIRYNT, MIOTLY, CAT, BARANDALF, DRAGON, SHOOTER`
### Czego `SAVEGAME` **nie** zapisuje
- `SETTINGS.ARR` — globalne, wspólne dla wszystkich slotów: `[?, głośność 0..800]`
- `Dane/Game/Przygoda/*/*_0.SAV` — osobne pliki postępu minigier w katalogach scen
- `SLOT_<prefix>_<n>.SAV`, `DODOS_<prefix>.SAV` — stan alchemii (`Alchemy.cnv`),
zapisywane bezpośrednio do `$COMMON`, **poza** manifestem slotu → przeżywają
wczytanie innego zapisu
## 4. `GAME<s>.ARR` — 6 pól i cała reszta
To jest najważniejszy plik: mówi gdzie stoisz. Wszystkie 6 elementów to **stringi**.
| idx | zmienna globalna | znaczenie |
|---|---|---|
| 0 | `G_SARCADEOBJECTS` | bieżąca lokacja (nazwa z listy 52) |
| 1 | `G_SLASTOBJECTS` | poprzednia lokacja (skąd przyszedłeś → punkt wejścia) |
| 2 | `G_SDIALOGCHARACTER` | z kim rozmowa |
| 3 | `G_SDIALOGRETURN` | dokąd wrócić po rozmowie |
| 4 | `G_ITKTPM` | identyfikator "Tego, Który Tak Potwornie Mąci", losowe 0..7, ustalane raz na nową grę (`DEFSETS`) |
| 5 | `G_IKRETACTIVE` | czy Kret towarzyszy Reksiowi (0/1) |
Domyślne dla nowej gry (`DEFSETS`): `["INTRO_1", "NULL", "BUREKTOR", "NULL", rand(0,7), "0"]`.
Jeśli plik ma <6 elementów, gra po cichu resetuje do domyślnych — czyli **zły rozmiar
= utrata pozycji**, a nie crash.
## 5. `<SCENA><s>.DTA` — kolumna `TYPE` (dekoder z `Arcade.cnv:__LOAD_DB__`)
| TYPE | rola wiersza |
|---|---|
| `< 100` | zwykły obiekt sceny → ładowany jako `ANNOBJECT<n>` z pliku `NAME` |
| `101` | tło statyczne (`CANVASOBSERVER^SETBACKGROUND`), `NAME` = `.IMG` |
| `102` | maska ścieżek chodzenia (`WPATH^LOAD`), `NAME` = `.SEK` |
| `103` | aktywacja przejścia: `WPATH^SETACTIVE(IPARAM0, IPARAM1, IPARAM2)` |
| `104` | Reksio; `NAME` = `.ANN`, `IPARAM0` = prędkość maks. |
| `105` / `145` | punkt startowy (zestaw 0 / 1); `IDNAME` = scena skąd, `IPARAM0/1` = X/Y |
| `106` / `146` | punkt docelowy (zestaw 0 / 1); jw. |
| `107` | tło animowane (`ANNBKG`), `IPARAM0/1` = szerokość/wysokość |
| `108` | muzyka lokacji, `IDNAME` = nazwa `.WAV` |
| `140` | Kret (`ANNKRET`), `NAME` = `.ANN` |
Dla obiektów z `TYPE == 2` `IPARAM0` jest **flagą stanu**: `1` → obiekt widoczny,
`0` → schowany/zabrany (`BFITMP1045``1043`/`1044`). Pozostałe `TYPE < 100` są zawsze
pokazywane, więc tam `IPARAM0/1/2` znaczą co innego per scena — to trzeba dekodować
scena po scenie z odpowiedniego `.cnv` (np. `Piklibia.cnv:BEHSAVEMAP` używa `IPARAM0`
jako numeru klatki, a `IPARAM1` jako stanu rycerza).
## 6. Ekwipunek — `ITEMS<s>.DTA`
Zawsze dokładnie **10 wierszy**, indeksowanych `GUID` 0..9. Pusty slot to
`n|NULL|0|NULL|0`. Pola:
- `GUID` — indeks slotu, równy numerowi wiersza
- `NAME` — identyfikator przedmiotu, np. `BUTY_BUT`; grafika ładowana po nazwie
- `PARENT` — indeks wiersza w `<SCENA>.DTA`, z którego przedmiot został podniesiony
(`-1` = znikąd). Przy odkładaniu gra ustawia temu obiektowi `IPARAM0 = 1`
- `BASE` — lokacja pochodzenia (wartość `G_SARCADEOBJECTS` w chwili podniesienia)
- `EMPTY` — mylące: **`1` = slot zajęty**, `0` = wolny (`__LOAD_ITEMS__` pokazuje
przedmiot gdy `EMPTY == 1`)
## 7. Dialogi
Trzy warstwy, dwie statyczne i jedna w zapisie:
- `common/dialogs.dta` (statyczne, 93 wiersze) — `ID | plik_pytania.WAV | plik_odpowiedzi.WAV | trigger`.
Ostatnia kolumna to ID pytania odblokowywanego po zadaniu tego.
- `common/<POSTAĆ>.arr` (statyczne, małe litery) — pełna pula pytań danej postaci,
np. `GULDRYK.arr = ["50","51","52","53"]`
- `common/<POSTAĆ>_HIST<s>.ARR` (**w zapisie**) — pytania już zadane tej postaci
- `common/QUESTIONS<s>.ARR` (**w zapisie**) — pytania, które Reksio aktualnie *ma*
(`ARRBASEQUESTIONS`); zadanie pytania usuwa je stąd i dokłada do `_HIST`
- `common/QUESTIONS_ASKED<s>.ARR` (**w zapisie**) — globalny log zadanych pytań
ID pytań to stringi, nie liczby (`"11A"`, `"11B"`) — parser nie może ich rzutować na int.
## 8. Czary i doświadczenie — `SPELLS<s>.ARR`
17 intów, jedna tablica na czary **i** na klepsydrę. Stan fabryczny (`NEWGAME`):
same zera poza indeksem 12 = 64.
| idx | znaczenie |
|---|---|
| 011 | flagi „czar znany" (0/1) |
| 12 | start 64, `SUBAT(12,3)` przy każdym awansie — **nikt tego nie czyta** |
| 13 | **doświadczenie**, 0..149 |
| 14 | licznik awansów |
| 15 | 1 → klepsydra/różdżka widoczna w menu |
| 16 | 1 → klepsydra miga, można nauczyć się czaru |
Czary to sześć kul w dwóch wariantach — zwykłym (05) i obronnym (611, sufiks
`_OBR`); czar `N` ma obronę `N+6`:
| idx | czar | idx | obrona | gabinet |
|---|---|---|---|---|
| 0 | `KULA_SEN` | 6 | `KULA_SEN_OBR` | Guldryk |
| 1 | `KULA_ZABA` | 7 | `KULA_ZABA_OBR` | Snejk / Wmwnetrze |
| 2 | `KULA_MUCHY` | 8 | `KULA_MUCHY_OBR` | Snejk |
| 3 | `KULA_BUDYN` | 9 | `KULA_BUDYN_OBR` | Guldryk |
| 4 | `KULA_WIRY` | 10 | `KULA_WIRY_OBR` | Wmwnetrze |
| 5 | `KULA_CIEMNOSC` | 11 | `KULA_CIEMNOSC_OBR` | Wmwnetrze / Snejk |
Scena `MAGIC` czyta indeksy 0..11 do `ARRACTIVESPELLS` (`Magic.cnv:1329`), a nauka
czaru to `CHANGEAT(<idx>, 1)` + natychmiastowy `SAVE("$COMMON\SPELLS0.ARR")`
(`Magic.cnv:BFITMP49`, `Guldryk.cnv:BFITMP763`).
### Mechanika klepsydry (`MAINMENU.class`)
```
ADDEXP(n) :737 G_ARRREXSPELLS^ADDAT(13, n) → odśwież klepsydrę → SAVEEXP
SAVEEXP :712 SAVE("$COMMON\SPELLS0.ARR") ← zapis NATYCHMIAST, przy każdym punkcie
BFITMP57 :1022 exp < 150 ? rysuj klatkę : miganie
BFITMP55 :1012 klatka klepsydry = exp / 10 + 1 (16 klatek na 150 exp)
BFITMP56 :1017 PLAY("MIGA") + CHANGEAT(16, 1)
LEVELUP :743 exp w [0,150) → FALSE; inaczej BFITMP61
BFITMP61 :1042 exp -= 150 (reszta przechodzi dalej); idx14 += 1; idx12 -= 3;
PLAY("PANEL"); CHANGEAT(16, 0)
```
Punkty przyznaje `G_MENU^ADDEXP(n)` rozsiane po scenach — ok. 60 wywołań, wartości
10/20/30/40/50. Rozwiązanie zagadki daje 50 (`Arcade.cnv:_SOLVED_`), rozmowa 30
(`Dialogs.cnv:BFITMP51`).
Ważne dla edytora: doświadczenie **jest zapisywane od razu do slotu 0**, przy każdym
punkcie, niezależnie od menu „Zapisz". Nie trzeba go szukać nigdzie indziej.
### Dwa błędy w oryginale
1. `BFITMP61` zapisuje przez `G_ARRREXSPELLS^SAVE("SPELLS0.DTA")` — **zła nazwa
i złe rozszerzenie**, bez `$COMMON\`. Sam awans nie trafia do `SPELLS0.ARR`
drogą; ratuje to dopiero najbliższy `ADDEXP``SAVEEXP`.
2. `SAVEEXP` sprawdza `GETSIZE() < 16`, a w ścieżce awaryjnej (`BFITMP49`) tworzy
tablicę **16-elementową**, podczas gdy reszta kodu oczekuje 17 i sięga po
indeks 16. Przy normalnej grze nie wypływa, bo `NEWGAME` tworzy 17.
## 9. Przepływ Nowa gra / Zapis / Wczytanie
```
NEWGAME (MAINMENU.class:657)
<SCENA>_DEF.DTA → <SCENA>0.DTA (×52)
ITEMS_DEF.DTA → ITEMS0.DTA
QUESTIONS_DEF.ARR → QUESTIONS0.ARR, QUESTIONS_ASKED0.ARR,
<POSTAĆ>_HIST0.ARR (×11), INVEST0.ARR
<MINIGRA>_DEF.ARR → <MINIGRA>0.ARR (×6)
CUTSCENES0.ARR := []
SPELLS0.ARR := [0×12, 64, 0×4]
GAME0.ARR := DEFSETS
SAVEGAME(n) (linia 667) LOADGAME(n) (linia 672)
0 → n dla wszystkich plików n → 0 dla wszystkich plików
+ GAME0 budowane z żywych + GAME<n> wczytane DO żywych globali
globali, potem zapisane (jeśli <6 elementów → DEFSETS)
+ SAVE.IMG → m_shot<n>.img
```
Scena wchodzi w stan po wczytaniu przez procedurę `_LOADGAME_` wystawioną w każdym
`.cnv` sceny (`Arcade.cnv:1533`) — ta tylko robi `GOTO` do `G_SARCADEOBJECTS`,
cała reszta stanu przyjeżdża z plików przy `__INIT__``__LOAD_DB__`.
## 10. Ryzyka przy edycji
- **Liczba wierszy `.DTA` musi się zgadzać** — kod scen adresuje wiersze po indeksie
(`DBOBJECTS^SELECT(10)`, `BEHSELECTOBJ^RUN(23)`). Dodanie/usunięcie wiersza przesunie
wszystko i rozjedzie logikę. Edytuj wartości, nie strukturę.
- **`ITEMS` zawsze 10 wierszy** — pętle są zahardkodowane na `0..10`.
- **Za krótkie `GAME`/`SPELLS`** → cichy reset do domyślnych, nie błąd.
- Gra trzyma pliki otwarte tylko na czas operacji, ale i tak edytuj przy zamkniętej grze —
`SAVEGAME` nadpisze slot bez pytania.
- Alchemia (`SLOT_*.SAV`, `DODOS_*.SAV`) jest poza slotami — po wczytaniu starego zapisu
zostaje stan z najnowszej rozgrywki. To bug w grze, nie w edytorze.