From 892d08f3160f122329741f5504f1f2059019a41e Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sun, 26 Jul 2026 12:05:32 +0200 Subject: [PATCH] Levelende v1: Exit-Zone -> Zusammenfassungstafel (Original-Layout) -> Kartenfortschritt; TIME-OUT/Levelende-Tafeln bleiben bis Enter/Space stehen --- NOTES.md | 20 +++++++++++ game/intro.py | 10 +++++- game/level.py | 92 +++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 111 insertions(+), 11 deletions(-) diff --git a/NOTES.md b/NOTES.md index 92aa1c8..20c869b 100644 --- a/NOTES.md +++ b/NOTES.md @@ -2089,3 +2089,23 @@ die "Absprung-Hocke" komplett entfernt (Sprung ist jetzt nur 8 hoch / (Slot 10). Slot 17 (frontal) ist damit nirgends mehr in Gebrauch; die sess_level-"Frontal am Levelstart"-Deutung aus Eintrag (26) war wohl eine Fehlinterpretation der kleinen Crops. + +## 26.07.2026 (35) -- Levelende v1 + Tafeln bleiben stehen + +Stefans Regeln: TIME-OUT-Tafel (und Levelende-Tafel) bleiben STEHEN bis +Enter/Leertaste (_wait_panel). NEU Levelende v1: +- Exit-Zone erreicht -> Zusammenfassungstafel (SCORE/MAXIMUM/GESAMMELT/ + VITAMINE/"0 X" -- nach Stefans Original-Screenshot; Werte noch 0, bis + Items gebaut sind; MAXIMUM = echte ARE-Zaehlung 151+154) -> 'completed' + -> intro.py laesst die Kartenfigur zum naechsten Punkt weiterwandern + (Punkt + Spur werden blau, Original-Regeln aus (31)/(33)). +- Exit-Zone W1L1 = SCHAETZUNG (rechtes Ende, um ARE-Objekt 194 bei + (3168,432)): exit_px=(3140,300,348,452). Echte Stelle + Aussehen des + Ausgangs von Stefan/DOSBox zu verifizieren! +- MAXIMUM-Diskrepanz: wir zaehlen 137 (151er+154er), Stefans Original- + Screen zeigte 158 -- vermutlich zaehlen ?-Box-Inhalte (71-76) und/oder + 15x-Typen mit. Offen. +- ctype-Nibble-12 (44) liegt NICHT am Levelende (2 Tiles bei (91/99,17)) + -- also kein Exit-Marker; Bedeutung offen. Bit 8 steckt zudem in + Standard-Boden (40=32+8) => Coco-Ast-Trigger (Bit 8) ist FALSCH und + muss neu bestimmt werden (aktuell greift Coco evtl. am Boden?? pruefen). diff --git a/game/intro.py b/game/intro.py index 60b4d1b..c365e67 100644 --- a/game/intro.py +++ b/game/intro.py @@ -990,7 +990,15 @@ def main(): elif state == 'level': r = level_mod.run_level(clock, assets, audio_paths, map_name=pending_map) - if r == 'map': + if r == 'completed': + # Level geschafft: Kartenfigur laeuft zum + # naechsten Punkt weiter (Punkt/Spur werden blau) + wm = map_state.get('wm') + if wm is not None and wm.current < len(wm.segments) - 1: + wm.current += 1 + play_title_music() + state = 'map' + elif r == 'map': play_title_music() # Karte/Menue laufen mit Titelmusik state = 'map' else: diff --git a/game/level.py b/game/level.py index c2f9c40..123da4f 100644 --- a/game/level.py +++ b/game/level.py @@ -144,6 +144,10 @@ LEVEL_INFO = { chars=['TONY', 'SMACKS'], # Level 1: nur die beiden (Stefan) water_y=42 * TILE, # Wasseroberflaeche unter der Bruecke door_px=(12 * TILE, 37 * TILE, 2 * TILE, 3 * TILE), # Haus-Tuer (x,y,w,h) + # Levelende-Zone (v1-SCHAETZUNG: rechtes Levelende um das letzte + # ARE-Objekt Typ 194 bei (3168,432) -- echte Stelle noch per + # Stefan/DOSBox zu verifizieren) + exit_px=(3140, 300, 348, 452), ), } WATER_TINT = (10, 20, 120, 110) # dunkelblaues Overlay @@ -741,6 +745,65 @@ def run_character_select(clock, assets, available, current): clock.tick(30) +def _wait_panel(clock, panel): + '''Tafel anzeigen, bis Enter/Leertaste gedrueckt wird (Stefans Regel + fuer TIME-OUT- und Levelende-Tafel). Rueckgabe: 'ok' | 'quit'.''' + pygame.event.clear() + while True: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return 'quit' + if event.type == pygame.KEYDOWN and event.key in ( + pygame.K_RETURN, pygame.K_SPACE): + return 'ok' + intro._blit_centered(intro.build_letterboxed(panel)) + pygame.display.flip() + clock.tick(30) + + +def _parse_are_objects(are_bytes, map_w_tiles, map_h_tiles): + '''Objektliste (x_px, y_px, typ) aus einer .ARE (Format: NOTES (24)).''' + import struct + 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): + off = struct.unpack('>H', d[352 + (sy * sec_w + sx) * 2: + 354 + (sy * sec_w + sx) * 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 _summary_panel(assets, collected, maximum, vitamins, score): + '''Levelende-Tafel nach Stefans Original-Screenshot: SCORE, MAXIMUM/ + GESAMMELT (Cerealien), VITAMINE, Bonusleben-Zeile.''' + from .menu import MenuAssets + m = MenuAssets(assets) + panel = m.menu_bg.copy() + def line(text, y): + surf = m.font_big.render(text) + panel.blit(surf, ((320 - surf.get_width()) // 2, y)) + line(f'SCORE: {score}', 48) + line(f'MAXIMUM {maximum} GESAMMELT {collected}', 66) + line(f'VITAMINE {vitamins}', 84) + line('0 X', 116) + return panel + + def run_level(clock, assets, audio_paths, map_name='W1L1'): '''Level spielen. ESC -> 'map', Fenster-X -> 'quit'.''' level = Level(assets, map_name) @@ -808,6 +871,23 @@ def run_level(clock, assets, audio_paths, map_name='W1L1'): vx=55.0 * player.facing, t=0.0)) keys = pygame.key.get_pressed() + # Levelende erreicht? -> Zusammenfassungstafel, dann Karte (Punkt + # wird blau, Figur wandert weiter -- intro.py behandelt 'completed') + exit_zone = info.get('exit_px') + if (dying is None and exit_zone + and pygame.Rect(exit_zone).collidepoint(player.x, player.y - 8)): + w, h = level.width, level.height + try: + objs = _parse_are_objects( + assets.container.entries[map_name + '.ARE'], w, h) + maximum = sum(1 for _, _, t in objs if t in (151, 154)) + except Exception: + maximum = 0 + panel = _summary_panel(assets, collected=0, maximum=maximum, + vitamins=0, score=0) + if _wait_panel(clock, panel) == 'quit': + return 'quit' + return 'completed' time_left = info['time'] - (time.monotonic() - start) # Zeit abgelaufen -> TIME-OUT-Tafel (Original-Screen, Stefans # Screenshot: Menue-Rahmen + "TIME OUT"), dann Leben -1 und Respawn @@ -817,16 +897,8 @@ def run_level(clock, assets, audio_paths, map_name='W1L1'): panel = m.menu_bg.copy() txt = m.font_big.render('TIME OUT') panel.blit(txt, ((320 - txt.get_width()) // 2, 72)) - end = time.monotonic() + 2.5 - while time.monotonic() < end: - for event in pygame.event.get(): - if event.type == pygame.QUIT: - return 'quit' - if event.type == pygame.KEYDOWN: - end = 0 - intro._blit_centered(intro.build_letterboxed(panel)) - pygame.display.flip() - clock.tick(30) + if _wait_panel(clock, panel) == 'quit': + return 'quit' lives -= 1 if lives <= 0: return 'map'