Projektstruktur: game/-Package angelegt, README ergaenzt
Laufzeit-Code vom Dev-Werkzeug getrennt: - game/: das eigentliche Spiel, startbar per `python -m game [--once]`. - formats.py (war tools/kellogg_formats.py) -- Asset-Parser, Kern-Bibliothek - audio.py (war tools/tfmx_audio.py) -- TFMX-Musik-Wrapper - intro.py (war tools/intro_sequence.py) -- Boot-/Intro-Sequenz - tfmx_player/(war tools/tfmx_player/) -- MIT-C-Renderer - __init__.py / __main__.py fuer `python -m game` - tools/: nur noch Dev-Werkzeuge (dat_extract, pcc_to_png, render_assets), importieren jetzt aus game.formats. - README.md: Ueberblick, Start, Struktur, Voraussetzungen, Rechtliches. - .gitignore: tfmx_player-Binary-Pfad auf game/ nachgezogen. Intro laeuft unveraendert (headless-Smoke-Test `python -m game --once` exit 0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3fe92d01b8
commit
85404c91f4
+2
-2
@@ -7,8 +7,8 @@ extracted_dat/
|
|||||||
# Gerenderte Original-Musik (TFMX->WAV) -- ist Original-Spielmaterial,
|
# Gerenderte Original-Musik (TFMX->WAV) -- ist Original-Spielmaterial,
|
||||||
# wird lokal gecacht, nicht committed (wie raw/extracted_dat)
|
# wird lokal gecacht, nicht committed (wie raw/extracted_dat)
|
||||||
audio_cache/
|
audio_cache/
|
||||||
tools/tfmx_player/render_tfmx
|
game/tfmx_player/render_tfmx
|
||||||
tools/tfmx_player/*.o
|
game/tfmx_player/*.o
|
||||||
|
|
||||||
# Python-Bytecode-Cache
|
# Python-Bytecode-Cache
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# Tony & Friends in Kellogg's Land — Remake
|
||||||
|
|
||||||
|
Ein plattformunabhängiges Remake (Python / pygame) des DOS-Jump'n'Runs
|
||||||
|
**„Kellogg's Tony and Friends in Kellogg's Land"** (1994). Ziel ist eine
|
||||||
|
lauffähige Neuimplementierung auf modernem x64, ohne DOS/DOSBox/QEMU — das
|
||||||
|
Original crasht in unserer FreeDOS-VM (RTM.EXE-Engine vs. QEMU-VGA-Emulation).
|
||||||
|
|
||||||
|
Das Remake liest die Grafiken, Sprites, Level und Musik **direkt aus den
|
||||||
|
Original-Spieldateien** (`raw/PCKELL.DAT`) und dekodiert sie zur Laufzeit — es
|
||||||
|
werden keine vorab entpackten Assets benötigt.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
- ✅ **Alle Asset-Formate geknackt & verifiziert:** PCC (Bilder), BOB (Sprites),
|
||||||
|
ICO (Tilesets), MAP (Level), TFMX (Musik).
|
||||||
|
- ✅ **Boot-/Intro-Sequenz** spielbar (Rauser → Factor5 → Kellogg's-Logo →
|
||||||
|
Titelbild, mit Fades, Vorhang-Wipe und Musik).
|
||||||
|
- ⏳ **Nächster Meilenstein:** echter Gameplay-Loop — Tony auf Level `W1L0`
|
||||||
|
bewegen (Physik, Kollision, Level-Scrolling). Existiert noch nicht.
|
||||||
|
|
||||||
|
Die vollständige, chronologische Reverse-Engineering-Historie mit allen
|
||||||
|
Format-Details steht in [NOTES.md](NOTES.md).
|
||||||
|
|
||||||
|
## Rechtliches
|
||||||
|
|
||||||
|
„Kellogg's Tony and Friends in Kellogg's Land" war ein kostenloses Werbespiel
|
||||||
|
und ist innerhalb Deutschlands frei kopierbar. Die im Repo enthaltenen
|
||||||
|
gerenderten Musik-WAVs (`audio/`) wurden lediglich aus den Original-Daten
|
||||||
|
extrahiert. Die eigentlichen Original-Spieldateien (`raw/`) sind **nicht** im
|
||||||
|
Repo (siehe `.gitignore`) — sie werden lokal benötigt, um das Remake zu starten.
|
||||||
|
|
||||||
|
Der TFMX-Musik-Renderer unter [game/tfmx_player/](game/tfmx_player/) ist
|
||||||
|
Fremdcode (MIT-Lizenz, © Peter Fors) und liegt mit seiner LICENSE bei. Die
|
||||||
|
Format-Kenntnisse zu PCC/BOB/MAP/ICO stammen als Referenz aus
|
||||||
|
[movAX13h/tony-and-friends-in-kelloggs-land](https://github.com/movAX13h/tony-and-friends-in-kelloggs-land);
|
||||||
|
der Python-Code hier ist eine eigenständige Neuimplementierung.
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
|
||||||
|
- Python 3, `pygame`
|
||||||
|
- `Pillow` (PCX-Dekodierung), `numpy` (optional, für den Scanline-Effekt im Intro)
|
||||||
|
- `gcc` (einmalig, um den TFMX-Renderer zu bauen — passiert automatisch beim
|
||||||
|
ersten Start)
|
||||||
|
- Die Original-Datei `PCKELL.DAT` unter `raw/` (nicht im Repo)
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo apt install python3-pygame python3-pil python3-numpy gcc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Starten
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 -m game # Intro in Endlosschleife (wie Original-Titelbildschirm)
|
||||||
|
python3 -m game --once # ein Durchlauf, dann beenden
|
||||||
|
```
|
||||||
|
|
||||||
|
Abbruch jederzeit per `ESC`. Ohne Soundkarte (z.B. headless) läuft das Spiel
|
||||||
|
stumm weiter statt abzustürzen.
|
||||||
|
|
||||||
|
Liegt `PCKELL.DAT` woanders, kann der Pfad per Umgebungsvariable gesetzt werden:
|
||||||
|
|
||||||
|
```
|
||||||
|
KELLOGG_DAT=/pfad/zu/PCKELL.DAT python3 -m game
|
||||||
|
```
|
||||||
|
|
||||||
|
## Projektstruktur
|
||||||
|
|
||||||
|
```
|
||||||
|
game/ Das eigentliche Spiel (startbar per `python -m game`)
|
||||||
|
formats.py Parser/Decoder für alle Asset-Formate aus PCKELL.DAT
|
||||||
|
audio.py Wrapper um den TFMX-Musik-Renderer (rendert & cached WAVs)
|
||||||
|
intro.py Boot-/Intro-Sequenz
|
||||||
|
tfmx_player/ MIT-lizenzierter C-Renderer für TFMX-Musik
|
||||||
|
tools/ Reine Dev-Werkzeuge (Assets auspacken / als PNG ansehen)
|
||||||
|
dat_extract.py PCKELL.DAT → alle 178 Einzeldateien (nach extracted_dat/)
|
||||||
|
pcc_to_png.py Eine PCC-Bilddatei → PNG
|
||||||
|
render_assets.py Batch-Render aller BOB/ICO/MAP → render_out/
|
||||||
|
raw/ Original-Spieldateien (PCKELL.DAT etc.) — NICHT im Repo
|
||||||
|
audio/ Aus PCKELL.DAT gerenderte Musik-WAVs (Archiv)
|
||||||
|
png_out/ Vorab gerenderte PCC-Bilder (Dev-Ausgabe)
|
||||||
|
render_out/ Vorab gerenderte BOB/ICO/MAP (Dev-Ausgabe)
|
||||||
|
dosbox/ DOSBox-Konfiguration zum Laufenlassen des Originals (Referenz)
|
||||||
|
NOTES.md Vollständiges Reverse-Engineering-Log
|
||||||
|
```
|
||||||
|
|
||||||
|
`game/formats.py` ist das gemeinsame Fundament: sowohl das Spiel als auch die
|
||||||
|
Dev-Werkzeuge in `tools/` importieren daraus. Zur Laufzeit braucht das Spiel
|
||||||
|
**nur** `raw/PCKELL.DAT` — die Ausgaben unter `png_out/`, `render_out/` und
|
||||||
|
`audio/` sind Verifikations-/Archiv-Artefakte, keine Laufzeit-Abhängigkeit.
|
||||||
|
|
||||||
|
## Original als Referenz laufen lassen (DOSBox)
|
||||||
|
|
||||||
|
Unter [dosbox/](dosbox/) liegt eine funktionierende Konfiguration, um das
|
||||||
|
Original zum visuellen Abgleich zu starten. Wichtig: In `KELLOGG.INI` **IRQ=5**
|
||||||
|
statt 7 setzen (sonst hängt sich das Spiel nach dem Factor-5-Logo auf) und die
|
||||||
|
`[sblaster]`-IRQ in der `dosbox.conf` passend dazu — Details in
|
||||||
|
[NOTES.md](NOTES.md).
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
"""Tony & Friends in Kellogg's Land -- plattformunabhaengiges Remake.
|
||||||
|
|
||||||
|
Laufzeit-Package des Spiels. Enthaelt:
|
||||||
|
formats.py -- Parser/Decoder fuer die Original-Assets aus raw/PCKELL.DAT
|
||||||
|
(PCC-Bilder, BOB-Sprites, ICO-Tilesets, MAP-Level, Paletten).
|
||||||
|
audio.py -- Wrapper um den TFMX-Musik-Renderer (tfmx_player/).
|
||||||
|
intro.py -- die Boot-/Intro-Sequenz (Rauser -> Factor5 -> Kellogg's ->
|
||||||
|
Titelbild), erster spielbarer Meilenstein.
|
||||||
|
tfmx_player/-- MIT-lizenzierter C-Renderer fuer die TFMX-Musik.
|
||||||
|
|
||||||
|
Start: python -m game [--once]
|
||||||
|
"""
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"""Startpunkt des Spiels: `python -m game [--once]`.
|
||||||
|
|
||||||
|
Spielt aktuell die Intro-Sequenz. Hier kommt spaeter die Ablaufsteuerung
|
||||||
|
(Intro -> Hauptmenue -> Weltkarte -> Gameplay) rein; bis dahin delegiert es
|
||||||
|
direkt an die Intro-Szene.
|
||||||
|
"""
|
||||||
|
from .intro import main
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
Duenner Python-Wrapper um das MIT-lizenzierte `tfmx_player/render_tfmx`
|
Duenner Python-Wrapper um das MIT-lizenzierte `tfmx_player/render_tfmx`
|
||||||
(siehe tools/tfmx_player/LICENSE, Copyright Peter Fors) -- rendert ein
|
(siehe game/tfmx_player/LICENSE, Copyright Peter Fors) -- rendert ein
|
||||||
TFMX-Musikstueck (<NAME>.TFX + <NAME>.SAM aus PCKELL.DAT) einmalig zu einer
|
TFMX-Musikstueck (<NAME>.TFX + <NAME>.SAM aus PCKELL.DAT) einmalig zu einer
|
||||||
WAV-Datei und cached das Ergebnis, damit die Intro-Sequenz die Musik per
|
WAV-Datei und cached das Ergebnis, damit die Intro-Sequenz die Musik per
|
||||||
pygame.mixer abspielen kann.
|
pygame.mixer abspielen kann.
|
||||||
@@ -99,9 +99,9 @@ def render_wav(container, name, seconds=180, force=False, song_index=None):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Kleiner CLI-Test: python3 tfmx_audio.py <DAT_PATH> <NAME> [seconds] [song_index]
|
# Kleiner CLI-Test: python3 -m game.audio <DAT_PATH> <NAME> [seconds] [song_index]
|
||||||
sys.path.insert(0, _HERE)
|
sys.path.insert(0, _PROJECT_ROOT)
|
||||||
from kellogg_formats import DATContainer
|
from game.formats import DATContainer
|
||||||
dat_path = sys.argv[1]
|
dat_path = sys.argv[1]
|
||||||
name = sys.argv[2]
|
name = sys.argv[2]
|
||||||
seconds = int(sys.argv[3]) if len(sys.argv) > 3 else 20
|
seconds = int(sys.argv[3]) if len(sys.argv) > 3 else 20
|
||||||
@@ -105,7 +105,7 @@ Ladebildschirm (render_audio_with_loading() -- Rendering laeuft in einem
|
|||||||
Thread, Hauptthread pumpt weiter Events). run_once() greift danach nur noch
|
Thread, Hauptthread pumpt weiter Events). run_once() greift danach nur noch
|
||||||
auf fertige WAV-Pfade zu, nie mehr Live-Rendering im Hauptloop.
|
auf fertige WAV-Pfade zu, nie mehr Live-Rendering im Hauptloop.
|
||||||
|
|
||||||
Aufruf: python3 intro_sequence.py [--once] (--once: nach einem Durchlauf beenden,
|
Aufruf: python3 -m game [--once] (--once: nach einem Durchlauf beenden,
|
||||||
sonst loopt die Sequenz weiter wie im Original-Titelbildschirm)
|
sonst loopt die Sequenz weiter wie im Original-Titelbildschirm)
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
@@ -114,9 +114,8 @@ import time
|
|||||||
import threading
|
import threading
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(__file__))
|
from .formats import DATContainer, load_pcc
|
||||||
from kellogg_formats import DATContainer, load_pcc
|
from . import audio as tfmx_audio
|
||||||
import tfmx_audio
|
|
||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
+3
-3
@@ -14,13 +14,13 @@ Hand-Decoder ersetzt -- die bereits committeten PNGs in png_out/ waren davon
|
|||||||
zum Glueck nicht betroffen (die sind noch von der guten ImageMagick-Version),
|
zum Glueck nicht betroffen (die sind noch von der guten ImageMagick-Version),
|
||||||
aber ein Rerun dieses Skripts haette wieder kaputte Bilder erzeugt. Fix jetzt:
|
aber ein Rerun dieses Skripts haette wieder kaputte Bilder erzeugt. Fix jetzt:
|
||||||
Pillow statt Hand-RLE oder externem `convert`-Subprocess -- kein Format-
|
Pillow statt Hand-RLE oder externem `convert`-Subprocess -- kein Format-
|
||||||
Parsing mehr in eigenem Code, nutzt tools/kellogg_formats.load_pcc().
|
Parsing mehr in eigenem Code, nutzt game.formats.load_pcc().
|
||||||
|
|
||||||
Nutzung: python3 pcc_to_png.py <input.PCC> <output.png>
|
Nutzung: python3 pcc_to_png.py <input.PCC> <output.png>
|
||||||
"""
|
"""
|
||||||
import sys, os
|
import sys, os
|
||||||
sys.path.insert(0, os.path.dirname(__file__))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
from kellogg_formats import load_pcc
|
from game.formats import load_pcc
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import sys, os
|
import sys, os
|
||||||
sys.path.insert(0, os.path.dirname(__file__))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
from kellogg_formats import DATContainer, get_bob_palette, get_world_palette, parse_bob, parse_ico, parse_map
|
from game.formats import DATContainer, get_bob_palette, get_world_palette, parse_bob, parse_ico, parse_map
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|||||||
Reference in New Issue
Block a user