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
|
Smacks y=514 (Blatt) -- per Level-Render am Baum verifiziert
|
||||||
(apex_TONY/SMACKS.png). Sprungkraft bleibt fuer beide gleich; die
|
(apex_TONY/SMACKS.png). Sprungkraft bleibt fuer beide gleich; die
|
||||||
Zielhoehen ergeben sich rein aus den Koerpergroessen (56 vs 44 px).
|
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
|
down_gap = 0.0 # Grace gegen Taste/on_ground-Flackern
|
||||||
look_down = 0.0 # Kamera-Offset beim gehaltenen Ducken
|
look_down = 0.0 # Kamera-Offset beim gehaltenen Ducken
|
||||||
dying = None # dict(t, vy, drop_y, drop_vy) waehrend Tod-Anim
|
dying = None # dict(t, vy, drop_y, drop_vy) waehrend Tod-Anim
|
||||||
|
bubbles = [] # Smacks' Wasserblasen (dict x,y,vx,t)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
dt = min(clock.tick(60) / 1000.0, 1 / 20)
|
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 player.on_ground and abs(player.vx) < 1
|
||||||
and door_open_t is None):
|
and door_open_t is None):
|
||||||
door_open_t = 0.0
|
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()
|
keys = pygame.key.get_pressed()
|
||||||
if dying is None:
|
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))
|
canvas.blit(door_open_sprite, (door.x - cam_x, door.y - cam_y))
|
||||||
else:
|
else:
|
||||||
canvas.blit(door_sign_sprite, (door.x - cam_x, door.y - cam_y))
|
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)
|
player.draw(canvas, cam_x, cam_y)
|
||||||
if dying is not None and SLOT_DROP1 in player.slots:
|
if dying is not None and SLOT_DROP1 in player.slots:
|
||||||
# fallendes Accessoire (16x16, Slots 48/49 im Wechsel = Flattern)
|
# 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