"""Weltkarte mit Levelauswahl -- Marker/Koepfe aus KARTE0-9.PCC, Pfad-, Levelpunkt- und Namensdaten fest eingebettet (siehe SEGMENTS unten). Die Daten wurden einmalig aus dem START.EXE-Binary extrahiert (NE-Datensegment, Offsets ~145114..146760; Extraktor: tools/worldmap_extract.py). Binary-Struktur dort, Fund 25.07.2026: Header, dann pro Level ein Segment aus 0xffff-terminierten u16-Records: [x y 0 25] Standpunkt (Wiederholung des vorigen Endpunkts) [x y 1 25] ... Wegpunkte (die Figur laeuft sie ab; 25 = Ticks) [x y 0 25] Endpunkt = Standpunkt an diesem Level [label_x label_y] Position des Levelpunkt-Markers auf der Karte [0] (Flag/Reserve) danach die 22 Levelnamen als Pascal-Strings (\x04W1L0...). Karten-Sprites: KARTE0/2 = grosser oranger/blauer Punkt, KARTE1/3 = kleine Varianten, KARTE4-7 = Mini-Koepfe (Tony, Smacks, Toucan, Coco) als Spielfigur-Marker. """ import time import pygame from . import intro # Mini-Kopf-Sprite je Charakter (KARTE4=Tony, 5=Smacks, 6=Toucan, 7=Coco) HEAD_SPRITE = {'TONY': 'KARTE4', 'SMACKS': 'KARTE5', 'TOUCAN': 'KARTE6', 'COCO': 'KARTE7'} STEP_SECONDS = 0.18 # Lauftempo der Kartenfigur pro Wegpunkt BLINK_SECONDS = 0.5 # Blinkrhythmus der aktiven Position # Die Binary-Koordinaten sind y-GEFLIPPT. Pixelexakte DOSBox-Nachmessung # (Template-Matching der KARTE-Sprites, 26.07.2026): Binary-Label von W1L1 # ist (53,56); im Original steht KARTE0 (10x7) mit Topleft bei (58,162) und # der Mini-Kopf KARTE4 (14x15) blinkt DARUEBER bei Topleft (53,146) -- # der Punkt bleibt also dauerhaft stehen, nur der Kopf geht an/aus. # Daraus: Basis = (x_bin, 218 - y_bin), Punkt-Topleft = Basis+(5,0), # Kopf-Topleft = Basis+(0,-16). Die Anker stehen nicht als Literal im # Binary (Code rechnet sie indirekt), daher per Messung fixiert. MAP_Y_FLIP = 218 DOT_OFF = (5, 0) # KARTE0/KARTE2 (10x7), Topleft relativ zur Basis HEAD_OFF = (0, -16) # KARTE4-7 (14x15), steht auf dem Punkt SMALL_OFF = (7, 2) # KARTE3 (6x4), zentriert auf dem grossen Punkt def _tf(p): return (p[0], MAP_Y_FLIP - p[1]) # 21 Pfadsegmente (Rohkoordinaten wie im Binary, y noch ungeflippt) -- # extrahiert am 26.07.2026 aus START.EXE mit tools/worldmap_extract.py. # Segment i gehoert zu LEVEL_NAMES[i+1]: W1L0 hat KEINEN Kartenpunkt # (Bonus-/Geheimlevel?), die 21 Punkte sind W1L1..W3L7 (3 Welten x 7). LEVEL_NAMES = ['W1L0', 'W1L1', 'W1L2', 'W1L3', 'W1L4', 'W1L5', 'W1L6', 'W1L7', 'W2L1', 'W2L2', 'W2L3', 'W2L4', 'W2L5', 'W2L6', 'W2L7', 'W3L1', 'W3L2', 'W3L3', 'W3L4', 'W3L5', 'W3L6', 'W3L7'] SEGMENTS = [ dict(waypoints=[(58, 40), (58, 40)], stand=(58, 40), label=(53, 56)), dict(waypoints=[(58, 40), (68, 37), (74, 36), (81, 36), (87, 35), (94, 35), (101, 35), (107, 35), (113, 35)], stand=(113, 35), label=(108, 51)), dict(waypoints=[(113, 35), (119, 41), (122, 47), (125, 53), (128, 58), (131, 65)], stand=(131, 65), label=(126, 81)), dict(waypoints=[(131, 65), (139, 58), (142, 52), (144, 47), (146, 42), (147, 37)], stand=(147, 37), label=(142, 53)), dict(waypoints=[(147, 37), (155, 40), (160, 47)], stand=(160, 47), label=(155, 63)), dict(waypoints=[(160, 47), (168, 44), (173, 42), (177, 42)], stand=(177, 42), label=(172, 58)), dict(waypoints=[(177, 42), (187, 39), (192, 39), (197, 39), (202, 41)], stand=(202, 41), label=(197, 57)), dict(waypoints=[(202, 41), (211, 39), (215, 42), (217, 47), (218, 53), (218, 59), (220, 65), (223, 75)], stand=(223, 75), label=(218, 91)), dict(waypoints=[(223, 75), (226, 79), (226, 85), (224, 91), (219, 93), (210, 96)], stand=(210, 96), label=(205, 112)), dict(waypoints=[(210, 96), (204, 93), (199, 93), (189, 96)], stand=(189, 96), label=(184, 112)), dict(waypoints=[(189, 96), (184, 94), (178, 92), (172, 90), (163, 91)], stand=(163, 91), label=(158, 107)), dict(waypoints=[(163, 91), (158, 89), (153, 89), (147, 89), (137, 91)], stand=(137, 91), label=(132, 107)), dict(waypoints=[(137, 91), (132, 89), (127, 89), (121, 89), (112, 91)], stand=(112, 91), label=(107, 107)), dict(waypoints=[(112, 91), (107, 89), (102, 86), (97, 84), (92, 82), (86, 81), (80, 80), (74, 79), (65, 81)], stand=(65, 81), label=(60, 97)), dict(waypoints=[(65, 81), (65, 86), (63, 92), (61, 98), (56, 103), (50, 113)], stand=(50, 113), label=(45, 129)), dict(waypoints=[(50, 113), (58, 113), (63, 116), (68, 120), (73, 123), (78, 127), (83, 130)], stand=(83, 130), label=(78, 146)), dict(waypoints=[(83, 130), (92, 129), (98, 130), (103, 130), (108, 131), (113, 132), (119, 134), (125, 135), (131, 137), (136, 139)], stand=(136, 139), label=(131, 155)), dict(waypoints=[(136, 139), (144, 138), (149, 139), (154, 140), (159, 141), (164, 142), (169, 143), (174, 144), (179, 147)], stand=(179, 147), label=(174, 163)), dict(waypoints=[(179, 147), (188, 145), (194, 145), (200, 142), (206, 138), (212, 135), (218, 135), (224, 136), (229, 139), (223, 141), (237, 143)], stand=(237, 143), label=(232, 159)), dict(waypoints=[(237, 143), (245, 138), (250, 134)], stand=(250, 134), label=(245, 150)), dict(waypoints=[(250, 134), (256, 137), (255, 144), (256, 151), (259, 158)], stand=(259, 158), label=(254, 174)), ] class WorldMap: '''Zustand + Zeichnen der Karte. current = Index des angewaehlten Levels.''' def __init__(self, assets, menu_assets): self.assets = assets self.karte = menu_assets.karte self.font = menu_assets.font_small self.segments = [dict(waypoints=list(s['waypoints']), stand=s['stand'], label=s['label']) for s in SEGMENTS] # segments[i] gehoert zu LEVEL_NAMES[i+1] (W1L0 ohne Kartenpunkt) self.level_names = LEVEL_NAMES[1:] # Positionen ins Screen-System transformieren (y-Flip) for seg in self.segments: seg['waypoints'] = [_tf(p) for p in seg['waypoints']] if seg['stand']: seg['stand'] = _tf(seg['stand']) if seg['label']: seg['label'] = _tf(seg['label']) self.dot_big_on = intro.make_border_bg_transparent(assets.pcc_surface('KARTE0')) self.dot_big_off = intro.make_border_bg_transparent(assets.pcc_surface('KARTE2')) self.dot_small = intro.make_border_bg_transparent(assets.pcc_surface('KARTE3')) self.dot_trail = intro.make_border_bg_transparent(assets.pcc_surface('KARTE1')) self.heads = {c: intro.make_border_bg_transparent(assets.pcc_surface(n)) for c, n in HEAD_SPRITE.items()} self.current = 0 # Segment 0 = W1L1 (DOSBox-Messung: Startpunkt (53,56)) def draw(self, canvas, char, t, walk_pos=None): canvas.blit(self.karte, (0, 0)) head = self.heads.get(char, self.heads['TONY']) head_phase = int(t / BLINK_SECONDS) % 2 == 0 # Original-Anzeigeregeln (Stefans Screenshot nach Durchspielen von # Level 1): bereiste Wege = Spur kleiner ORANGER Punkte (KARTE1) # entlang der Wegpunkte, erledigte/besuchte Level = BLAUER Punkt # (KARTE2), aktueller Punkt = orange + blinkender Kopf, alles # noch nicht Erreichte bleibt UNSICHTBAR. for i, seg in enumerate(self.segments): if seg['label'] is None: continue bx, by = seg['label'] if i == self.current and walk_pos is None: # Original-Verhalten (DOSBox-Messung): der grosse Punkt # steht dauerhaft, der Mini-Kopf blinkt DARUEBER an/aus canvas.blit(self.dot_big_on, (bx + DOT_OFF[0], by + DOT_OFF[1])) if head_phase: canvas.blit(head, (bx + HEAD_OFF[0], by + HEAD_OFF[1])) elif i < self.current: canvas.blit(self.dot_big_off, (bx + DOT_OFF[0], by + DOT_OFF[1])) # Punktspur auf den bereisten Pfaden (Segmente bis zum aktuellen). # Wegpunkte liegen auf der Stand-Linie; die Punktgrafik sitzt wie # die Levelpunkte um (-5,-16) versetzt (Label-Offset des Binaries). # Stefans Durchspiel-Befund: Wege zwischen zwei FERTIGEN Leveln # werden BLAU (KARTE3), nur der Weg zum aktuellen, noch nicht # geschafften Punkt bleibt ORANGE (KARTE1). for i in range(1, self.current + 1): spur = self.dot_trail if i == self.current else self.dot_small for wx, wy in self.segments[i]['waypoints'][1:-1]: canvas.blit(spur, (wx - 5 + SMALL_OFF[0], wy - 16 + SMALL_OFF[1])) # waehrend des Laufens: Kopf entlang des Pfads if walk_pos is not None: px, py = walk_pos canvas.blit(head, (px + HEAD_OFF[0], py + HEAD_OFF[1])) def run_worldmap(clock, assets, menu_assets, char='TONY', state={}): '''Weltkarte: Links/Rechts wandert zum vorigen/naechsten Levelpunkt (Figur laeuft die Original-Wegpunkte ab), Space/Return startet das angewaehlte Level, ESC -> Menue. Rueckgabe: ('level', name) | 'menu' | 'quit'. state haelt die aktuelle Position ueber Aufrufe hinweg.''' wm = state.get('wm') if wm is None or wm.assets is not assets: wm = WorldMap(assets, menu_assets) state['wm'] = wm walking = None # (Wegpunktliste, laufindex, richtung) waehrend der Bewegung start = time.monotonic() canvas = pygame.Surface((320, 200)) while True: t = time.monotonic() - start for event in pygame.event.get(): if event.type == pygame.QUIT: return 'quit' if event.type == pygame.KEYDOWN: if event.key == pygame.K_RETURN and (event.mod & pygame.KMOD_ALT): intro.toggle_fullscreen() continue if event.key == pygame.K_ESCAPE: return 'menu' if walking is None: if event.key in (pygame.K_RIGHT, pygame.K_UP) and wm.current < len(wm.segments) - 1: pts = wm.segments[wm.current + 1]['waypoints'] walking = {'pts': pts, 'i': 0.0, 'target': wm.current + 1} elif event.key in (pygame.K_LEFT, pygame.K_DOWN) and wm.current > 0: pts = list(reversed(wm.segments[wm.current]['waypoints'])) walking = {'pts': pts, 'i': 0.0, 'target': wm.current - 1} elif event.key in (pygame.K_SPACE, pygame.K_RETURN): return ('level', wm.level_names[wm.current]) dt = clock.tick(60) / 1000.0 walk_pos = None if walking is not None: walking['i'] += dt / STEP_SECONDS pts = walking['pts'] if walking['i'] >= len(pts) - 1 or len(pts) < 2: wm.current = walking['target'] walking = None else: i0 = int(walking['i']) frac = walking['i'] - i0 x0, y0 = pts[i0] x1, y1 = pts[min(i0 + 1, len(pts) - 1)] walk_pos = (int(x0 + (x1 - x0) * frac), int(y0 + (y1 - y0) * frac)) wm.draw(canvas, char, t, walk_pos) intro._blit_centered(intro.build_letterboxed(canvas)) pygame.display.flip()