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:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user