ARE-Format geknackt (Sektor-Tabelle + Objektrecords, tools/are_dump.py): echte Item-/Tuer-/Gegner-Positionen -- plus Smacks' Wasserblasen v1 (ALT beim Schwimmen)

This commit is contained in:
2026-07-26 03:10:21 +02:00
parent 5afe598337
commit 696e3c1d8e
3 changed files with 127 additions and 0 deletions
+29
View File
@@ -1936,3 +1936,32 @@ Tony-Kopf an den Ast, jeweils aus dem Stand. JUMP_VELOCITY -380 -> -400
Smacks y=514 (Blatt) -- per Level-Render am Baum verifiziert
(apex_TONY/SMACKS.png). Sprungkraft bleibt fuer beide gleich; die
Zielhoehen ergeben sich rein aus den Koerpergroessen (56 vs 44 px).
## 26.07.2026 (24) -- ARE-FORMAT GEKNACKT + Smacks' Wasserblasen (v1)
Anlass: Stefan will Blasen (Gegner fangen) + Gegner mit Sterbeanimation
-> dafuer braucht es die echten Gegner-/Item-Positionen = ARE-Format.
### ARE-Struktur (Decoder: tools/are_dump.py)
@8..48 Header: 10 BE-u16-Paare (Bedeutung teils offen)
@352 SEKTOR-TABELLE: ceil(map_px/64) x ceil(map_px_h/64) BE-u16-
Offsets (W1L1: 55x12=660), ffff = leer, sonst Offset+352 ->
Objektliste des 64x64-Sektors
Liste: ffff-Marker, dann 3xBE-u16-Records [Typ][dx][dy] (Pixeloffset
im Sektor) bis zum naechsten ffff
W1L1: 193 Objekte. Verifizierte/plausible Typen:
151 = Cerealie (x100 -- der HUD-Zaehler!), 154 = Cerealie im Wasser
(x37, liegt in den Unterwasser-Clustern)
199 = TUER (exakt auf unserer Haus-Tuer Tile 12,37!)
71/72/73/76 = ?-Box-Inhalte (sitzen auf ctype-4-Tiles => ctype 4 =
?-BOX, nicht "Item" wie bisher angenommen!)
194..208 = Level-Moebel/Deko (Lampen etc.), 101/102 = Levelende?
1/2/3/11/12/17/18/20/31 = vermutlich GEGNER-Spawns (Typ->BOB-Zuordnung
per DOSBox-Abgleich offen; Kandidaten: A-O.BOB, 'ani'-Ladeliste)
### Wasserblasen v1 (game/level.py)
ALT beim Schwimmen/Tauchen (nur mit Slots 29-31 = Smacks): Blase waechst
29->30->31, treibt in Blickrichtung (vx 55, Daempfung), steigt nach 0.5s,
platzt an der Oberflaeche oder nach 4s. Gegner-Fang (Blase + rauswerfen)
kommt zusammen mit den Gegnern, sobald die Typ-Zuordnung steht. Gegner-
Sterbeanimationen (wie bei den Spielfiguren) ebenfalls dann.
+25
View File
@@ -751,6 +751,7 @@ def run_level(clock, assets, audio_paths, map_name='W1L1'):
down_gap = 0.0 # Grace gegen Taste/on_ground-Flackern
look_down = 0.0 # Kamera-Offset beim gehaltenen Ducken
dying = None # dict(t, vy, drop_y, drop_vy) waehrend Tod-Anim
bubbles = [] # Smacks' Wasserblasen (dict x,y,vx,t)
while True:
dt = min(clock.tick(60) / 1000.0, 1 / 20)
@@ -771,6 +772,12 @@ def run_level(clock, assets, audio_paths, map_name='W1L1'):
and player.on_ground and abs(player.vx) < 1
and door_open_t is None):
door_open_t = 0.0
elif player.swimming and 29 in player.slots:
# Smacks: Wasserblase (Handbuch: ALT beim Schwimmen/
# Tauchen). Waechst 29->30->31, treibt vor und steigt.
bubbles.append(dict(x=player.x + 14 * player.facing,
y=player.y - 26,
vx=55.0 * player.facing, t=0.0))
keys = pygame.key.get_pressed()
if dying is None:
@@ -861,6 +868,24 @@ def run_level(clock, assets, audio_paths, map_name='W1L1'):
canvas.blit(door_open_sprite, (door.x - cam_x, door.y - cam_y))
else:
canvas.blit(door_sign_sprite, (door.x - cam_x, door.y - cam_y))
# Wasserblasen: wachsen (29->30->31), treiben vorwaerts, steigen,
# platzen an der Oberflaeche. (Gegner-Fang kommt mit den Gegnern.)
wy_b = info.get('water_y')
for b in bubbles[:]:
b['t'] += dt
b['x'] += b['vx'] * dt
b['vx'] *= 0.96
if b['t'] > 0.5:
b['y'] -= 28 * dt
if (wy_b is not None and b['y'] < wy_b + 8) or b['t'] > 4.0:
bubbles.remove(b)
continue
slot = 29 if b['t'] < 0.25 else (30 if b['t'] < 0.5 else 31)
entry = player.slots.get(slot)
if entry:
bs = entry[0]
canvas.blit(bs, (int(b['x'] - bs.get_width() / 2 - cam_x),
int(b['y'] - bs.get_height() / 2 - cam_y)))
player.draw(canvas, cam_x, cam_y)
if dying is not None and SLOT_DROP1 in player.slots:
# fallendes Accessoire (16x16, Slots 48/49 im Wechsel = Flattern)
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""ARE-Format-Decoder (geknackt 26.07.2026, NOTES Eintrag (24)).
Aufbau einer .ARE-Datei (alles Big-Endian):
@8..48 Header: 10 u16-Paare (u.a. Zeit?, Masse, markante Punkte --
Bedeutung teils offen; Paar 0 = (Levelzeit?, Pixelbreite?))
@60 weiteres Koordinatenpaar
@352 SEKTOR-TABELLE: (map_px_w/64 aufgerundet) x (map_px_h/64
aufgerundet) BE-u16-Offsets, Zeile fuer Zeile; 0xffff = leerer
Sektor, sonst zeigt (Offset + 352) auf die Objektliste.
Objektliste: 0xffff-Marker, dann Records aus 3 BE-u16:
[Typ] [dx] [dy] (dx/dy = Pixeloffset im 64x64-Sektor)
bis zum naechsten 0xffff (Start des naechsten Blocks).
Bekannte Typen (W1L1-Abgleich):
151 = Cerealie (Sammelitem, HUD-Zaehler), 154 = Cerealie im Wasser
199 = Tuer (liegt exakt auf der Haus-Tuer 12,37)
71/72/73/76 = ?-Box-Inhalte (liegen auf den ctype-4-Tiles = ?-Boxen)
194..208 = Level-Moebel (Lampen, Deko), 1/2/3/11/12/17/18/20/31 =
vermutlich GEGNER-Spawns (per DOSBox noch zuzuordnen), 101/102 = Levelende?
Aufruf: python3 tools/are_dump.py W1L1
"""
import os
import struct
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from game import formats # noqa: E402
def parse_are(are_bytes, map_w_tiles, map_h_tiles):
'''Liefert Liste (x_px, y_px, typ), Koordinaten in Map-Pixeln.'''
d = are_bytes
sec_w = (map_w_tiles * 16 + 63) // 64
sec_h = (map_h_tiles * 16 + 63) // 64
objs = []
for sy in range(sec_h):
for sx in range(sec_w):
idx = sy * sec_w + sx
off = struct.unpack('>H', d[352 + idx * 2:354 + idx * 2])[0]
if off == 0xffff:
continue
q = 352 + off
while q < len(d) - 1:
v = struct.unpack('>H', d[q:q + 2])[0]
if v == 0xffff:
if q == 352 + off:
q += 2
continue
break
typ, dx, dy = struct.unpack('>3H', d[q:q + 6])
objs.append((sx * 64 + dx, sy * 64 + dy, typ))
q += 6
return objs
def main():
name = sys.argv[1] if len(sys.argv) > 1 else 'W1L1'
dat = os.environ.get('KELLOGG_DAT', os.path.join(
os.path.dirname(__file__), '..', 'game', 'original', 'PCKELL.DAT'))
c = formats.DATContainer(dat)
w, h, _ = formats.parse_map(c.entries[name + '.MAP'])
objs = parse_are(c.entries[name + '.ARE'], w, h)
from collections import Counter
print(f'{name}: {len(objs)} Objekte, Map {w}x{h} Tiles')
print('Typen:', dict(sorted(Counter(t for _, _, t in objs).items())))
for x, y, t in objs:
print(f' Typ {t:3d} (0x{t:02x}) px ({x:4d},{y:4d}) Tile ({x // 16},{y // 16})')
if __name__ == '__main__':
main()