Falkencode · 09.09.2026

Falkencode: gen_world.py — Der Weltgenerator

gen_world.py ist der Weltenschmied: Ein deterministischer Generator (random.seed(42)), der world.json erzeugt — 210 Räume, über 100 NPCs, 52 Items, 10 Quests. Weil der Zufall festgenagelt ist, liefern zwei Läufe exakt dieselbe Welt; Änderungen am Skript ändern gezielt nur die Welt.

Hier stehen die Regionen, die Verbindungen inklusive Rückwegen aus jeder Region (Einbahnfallen sind ein klassischer MUD-Fehler!), die Monsterverteilung mit monsterfreien Eintrittsräumen, die Raum-Details für UNTERSUCHE und die Spezialräume: verschlossene Gruft und Drachenhort, Minenhalle, Marktschenke.

#!/usr/bin/env python3
"""Erzeugt world.json: ~150 Räume, NPCs, Items und Quests für das MUD.
Nach Änderungen einfach erneut ausführen oder world.json direkt editieren."""
import json, random

random.seed(42)

regions = {
    "dorf": {
        "name": "Falkenmoor (Hauptdorf)",
        "rooms": [
            ("markt", "Der Marktplatz von Falkenmoor", "Bunte Marktstände drängen sich um den alten Brunnen. Händler preisen ihr Obst an, und ein Stadtschreiber nagelt ein neues Edikt an die Tafel."),
            ("taverne", "Zum Goldenen Falken", "Warme Luft, der Geruch von Braten und Bier. Der Wirt poliert Krüge, während in der Ecke ein Barde ein klagendes Lied spielt."),
            ("schmiede", "Die Schmiede von Meister Eisenbart", "Funken sprühen, ein Hammer hallt auf den Amboss. An den Wänden hängen fertige Klingen und Helme."),
            ("rathaus", "Das Rathaus", "Ein stattliches Fachwerkgebäude. Der Bürgermeister verwaltet hier die Geschicke des Dorfes, und eine Karte der Umgebung hängt an der Wand."),
            ("kraeuterladen", "Kräuterhexe Weidenkrauts Laden", "Es riecht nach getrockneten Kräutern und Rauch. Glasgefäße mit seltsamen Tinkturen reihen sich in Regalen."),
            ("kirche", "Die alte Dorfkapelle", "Bunte Lichtflecken wandern über den Steinboden. Eine Priesterin zündet Kerzen am Altar an."),
            ("brunnen", "Am alten Wunschbrunnen", "Der tiefbrunnen, von dem man sich erzählt, er führe in alte Katakomben. Münzen schimmern auf dem Grund."),
            ("nordtor", "Das Nordtor", "Massive Eichentore öffnen den Weg in den dunklen Nadelwald. Wachen mustern jeden Reisenden."),
            ("suedtor", "Das Südtor", "Der Weg führt über Hügel hinab zum Fluss. Ein Schild warnt vor Straßenräubern."),
            ("osttor", "Das Osttor", "Von hier zieht ein Karrenweg in die steinigen Hügel. In der Ferne zeichnen sich Türme einer alten Burg ab."),
            ("westtor", "Das Westtor", "Ein moosiger Pfad verschwindet im Sumpfland. Nebel wabert über den Boden."),
            ("bauerhof", "Bauer Hinnerks Hof", "Hühner gackern, ein Hund bellt an der Kette. Auf dem Feld wartet Arbeit, und die Bäuerin winkt freundlich."),
            ("gasse", "Die Diebesgasse", "Eine schmale, verwinkelte Gasse. Schmutzige Gestalten huschen durch den Schatten der überhängenden Häuser."),
            ("kneipe", "Die Marktschenke", "Niedrige Balken, Honigduft und das Klirren der Krüge. Die Marktschenke ist der Winkel, in dem das Dorf seinen Tag austrinket."),
            ("laden", "Der Gemischtwarenladen", "Regale bis zur Decke: Seile, Fackeln, Töpfe, Würfel und Dinge, deren Zweck Geheimnis bleibt. Krämerin Alvida überblickt alles von ihrem Tresen."),
            ("haus", "Das Haus am Markt", "Ein gemütliches Haus, das die Stadt jedem Helden zur Verfügung stellt. An der Wand steht eine massive Eichentruhe mit hundert Fächern — TRUHE zeigt den Inhalt, LEGE <x> verstaut etwas, HOL <x> holt es zurück."),
        ],
    },
    "wald": {
        "name": "Der Nadelwald",
        "adj": ["dicht und dunkel", "vom Nebel durchzogen", "erfüllt von Vogelgezwitscher", "still und kühl", "von Farnen überwuchert"],
        "desc": ["Hohe Tannen ragen in den Himmel, kaum Licht dringt durch die Kronen.",
                 "Zwischen den Bäumen hängt ein dichter Nebel, der jeden Schritt schluckt.",
                 "Moos bedeckt die Steine, irgendwo knackt ein Zweig.",
                 "Ein Bach plätschert leise zwischen den Wurzeln.",
                 "Auf einem umgestürzten Baum sitzt ein Rabe und beobachtet dich."],
        "count": 34,
    },
    "huegel": {
        "name": "Die Steinigen Hügel",
        "adj": ["windig und öde", "mit Geröll übersät", "von Gras bewachsen", "steinig und verwinkelt"],
        "desc": ["Der Wind pfeift über die kahlen Hänge.", "Ziegenpfade winden sich zwischen Felsblöcken hindurch.",
                 "Verstreute Megalithen werfen lange Schatten.", "Ein verfallener Meilenstein weist niemandem mehr den Weg.",
                 "Weit unten rauscht der Fluss."],
        "count": 28,
    },
    "sumpf": {
        "name": "Der Schwarzsumpf",
        "adj": ["verfault und neblig", "von Mücken geschwärt", "undurchdringlich", "düster"],
        "desc": ["Blasen steigen aus dem schwarzen Wasser.", "Tote Bäume ragen wie Skelette aus dem Morast.",
                 "Der Gestank nach Fäulnis liegt schwer in der Luft.", "Irgendwo blubbert es bedrohlich.",
                 "Irrlichter tanzen am Rand deines Blickfelds."],
        "count": 22,
    },
    "gebirge": {
        "name": "Das Graukamm-Gebirge",
        "adj": ["schroff und kalt", "von Schnee bedeckt", "steinig und steil"],
        "desc": ["Eisiger Wind schneidert dir ins Gesicht.", "Ein schmaler Grat führt über den Abgrund.",
                 "Adler kreisen über den Gipfeln.", "Gletscherzungen glitzern zwischen den Felsen.",
                 "Geröll setzt sich unter deinen Stiefeln in Bewegung."],
        "count": 18,
    },
    "katakomben": {
        "name": "Die Katakomben unter dem Brunnen",
        "adj": ["finster und modrig", "von Spinnweben verhangen", "kalt und feucht"],
        "desc": ["Fackelschein tanzt über alte Grabplatten.", "Knochen türmen sich in Nischen der Wand.",
                 "Ein eisiger Luftzug kommt aus der Tiefe.", "Wasser tropft von der Decke in schwarze Pfützen.",
                 "Alte Runnen erzählen von längst toten Königen."],
        "count": 16,
    },
    "burg": {
        "name": "Burg Rabenstein",
        "rooms": [
            ("burg_hof", "Der Burghof", "Ein von Ruß geschwärzter Burghof. Soldaten üben am Waffenplatz, ein Banner mit einem Raben flattert im Wind."),
            ("burg_halle", "Die große Halle", "Lange Tafeln, ein prasselndes Feuer. Der Burgherr sitzt auf erhöhtem Stuhl und mustert Neuankömmlinge."),
            ("burg_kerker", "Der Kerker", "Es riecht nach Moder und Verzweiflung. In einer Zelle sitzt ein halbverhungerter Gefangener."),
            ("burg_waffenkammer", "Die Waffenkammer", "Reihen von Schwertern, Äxten und Rüstungen. Ein alter Waffenmeister wartet auf kämpferische Gäste."),
            ("burg_turm", "Der Wachturm", "Von hier übersieht man das ganze Land. Ein Wachsoldat lehnt am Zinnenkranz."),
            ("burg_kueche", "Die Burgküche", "Ein Feuer lodert unter einem gewaltigen Kessel. Die Köchin ruft nach Gewürzen."),
            ("burg_kapelle", "Die Burgkapelle", "Bleiglasfenster zeigen den Kampf eines Ritters gegen einen Drachen."),
            ("burg_zinnen", "Die Zinnen", "Der Wind pfeift um die steinernen Brüstungen. Man sieht bis zum Meer."),
        ],
    },
    "fluß": {
        "name": "Am Graufluss",
        "adj": ["ruhig und friedlich", "von Weiden gesäumt", "fischreich"],
        "desc": ["Der breite Fluss zieht gemächlich dahin.", "Ein hölzerner Steg führt ans Wasser.",
                 "Libellen tanzen über die Oberfläche.", "Am Ufer liegt ein umgekipptes Ruderboot.",
                 "Fischer reparieren ihre Netze."],
        "count": 13,
    },
}

items = {
    # Waffen: name, schaden
    "w_knueppel": ("Waffe", "Holzknüppel", "Ein schlichter, aber wirkungsvoller Knüppel.", 2, 0),
    "w_dolch": ("Waffe", "Rostiger Dolch", "Kurz, spitz, gut für dunkle Gassen.", 4, 0),
    "w_schwert": ("Waffe", "Kurzschwert", "Eine solide Klinge aus Dorfschmiede.", 7, 0),
    "w_axt": ("Waffe", "Waldläuferaxt", "Schwer und einhändig zu führen.", 9, 0),
    "w_langschwert": ("Waffe", "Langschwert", "Eine gut ausbalancierte Klinge für erfahrene Kämpfer.", 12, 0),
    "w_stab": ("Waffe", "Runenstab", "Ein alter Stab mit flackernden Runen.", 14, 0),
    "w_hammer": ("Waffe", "Kriegshammer", "Schwer genug, Rüstungen zu zertrümmern.", 16, 0),
    "w_drachenzahn": ("Waffe", "Drachenzahnklinge", "Eine seltene Klinge, geschmiedet aus einem Drachenzahn.", 20, 0),
    # Rüstungen: name, verteidigung
    "a_lendtuch": ("Rüstung", "Ledergurt", "Besser als nichts.", 0, 1),
    "a_leder": ("Rüstung", "Lederrüstung", "Zähes Leder, leicht zu tragen.", 0, 3),
    "a_kette": ("Rüstung", "Kettenhemd", "Tausend kleine Ringe schützen deinen Leib.", 0, 6),
    "a_schild": ("Rüstung", "Eichenschild", "Ein stabil gebundener Schild mit Falkenwappen.", 0, 4),
    "a_platte": ("Rüstung", "Plattenrüstung", "Der Hammer des Ritters. Schwer, aber nahezu undurchdringlich.", 0, 10),
    "a_drachenschupp": ("Rüstung", "Drachenschuppenpanzer", "Schuppen eines alten Drachen, federleicht und hart.", 0, 14),
    # Sonstiges
    "i_heiltrank": ("Trank", "Heiltrank", "Ein trüber roter Trank. Stellt 20 Lebenspunkte wieder her.", 0, 0),
    "i_brot": ("Nahrung", "Brotlaib", "Ein frisches Bauernbrot.", 0, 0),
    "i_kraeuter": ("Nahrung", "Heilkräuter", "Duftende Kräuter aus dem Wald.", 0, 0),
    "i_amulett": ("Schmuck", "Falkenamulett", "Ein kleines Silberamulett in Falkenform.", 0, 0),
    "i_brief": ("Quest", "Versiegeltes Schreiben", "Ein Brief mit dem Siegel des Bürgermeisters.", 0, 0),
    "i_karte": ("Quest", "Schatzkarte", "Eine vergilbte Karte mit rotem Kreuz.", 0, 0),
    "i_knochen": ("Sonstiges", "Alter Knochen", "Wem gehörte der wohl?", 0, 0),
    "i_muenzen": ("Wertgegenstand", "Beutel mit Münzen", "Klimpert angenehm.", 0, 0),
    "i_edelstein": ("Wertgegenstand", "Roter Edelstein", "Er glüht von innen, als lebte er.", 0, 0),
    "i_kerze": ("Nahrung", "Wachskerze", "Licht in der Dunkelheit.", 0, 0),
    "i_fell": ("Wertgegenstand", "Wolfsfell", "Weich und warm.", 0, 0),
    "i_krone": ("Quest", "Die verlorene Krone", "Die Krone des alten Königs aus den Katakomben.", 0, 0),
    "i_gewuerz": ("Quest", "Pfeffer aus dem Süden", "Die Burgköchin braucht es dringend.", 0, 0),
    # Kneipe: Getränke (1 Gold) & Essen (2 Gold)
    "i_bier": ("Getränk", "Kruger Bier", "Dunkel und kalt, mit feiner Schaumkrone.", 0, 0),
    "i_kaffee": ("Getränk", "Heißer Kaffee", "Aus fernen Ländern, rauchig und stark.", 0, 0),
    "i_soda": ("Getränk", "Zitronen-Soda", "Süß und sprudelnd — an heißen Tagen ein Traum.", 0, 0),
    "i_wein": ("Getränk", "Becherglas Wein", "Tiefrot und schwer, nach dem Süden schmeckend.", 0, 0),
    "i_tee": ("Getränk", "Kräutertee", "Weidenkrauts Mischung, beruhigt Geist und Nerven.", 0, 0),
    "i_suppe": ("Essen", "Tagesuppe", "Was der Markt hergibt, in einer Brühe vereint.", 0, 0),
    "i_eintopf": ("Essen", "Teller Eintopf", "Dickleibig und deftig, mit Wurzeln und Fleisch.", 0, 0),
    "i_braten": ("Essen", "Braten mit Brot", "Saftiges Fleisch dazu frisches Bauernbrot.", 0, 0),
    "i_met": ("Getränk", "Becher Met", "Goldgelber Honigwein aus dem Klosterkeller, süß und stark.", 0, 0),
    "i_fackel": ("Sonstiges", "Harzfackel", "Treibt selbst den dunkelsten Keller davon.", 0, 0),
    "i_seil": ("Sonstiges", "Strickseil", "Drei Ellen getwisteter Hanf, hält einen Ochsen.", 0, 0),
    "i_wuerfel": ("Sonstiges", "Glückswürfel", "Ein paar Beinwürfel — der Wirt sagt, sie lägen nie richtig.", 0, 0),
    "i_proviant": ("Essen", "Proviantpaket", "Brot, Käse und getrocknete Pflaumen, reisefest gebündelt.", 0, 0),
}

# NPCs: (id, name, raum, hp, dmg, def, aggressive?, dialog, quest)
npcs = [
    ("wirt", "Gastwirt Boldrick", "taverne", 30, 3, 0, False,
     "Sei willkommen im Goldenen Falken! Bier gibt's für 1 Gold, Eintopf für 2 — und plaudern kostet nichts. Nur auf dem Weg hier: Im Wald sollen gleich drei Räuber unterwegs sein.",
     None, {"sortiment": {"i_bier": 1, "i_soda": 1, "i_kaffee": 1, "i_tee": 1, "i_wein": 1,
                          "i_suppe": 2, "i_eintopf": 2, "i_braten": 2}, "nacht": True}),
    ("schenk", "Schenkwirt Tönjes", "kneipe", 30, 3, 0, False,
     "Hier gibt's das ehrliche Maß: Met frisch vom Fass, Eintopf vom Herd — Getränke 1 Gold, Essen 2. Die Stühle sind umsonst, die Gespräche auch.",
     None, {"sortiment": {"i_bier": 1, "i_met": 1, "i_soda": 1, "i_kaffee": 1, "i_tee": 1, "i_wein": 1,
                          "i_suppe": 2, "i_eintopf": 2, "i_braten": 2}, "nacht": True}),
    ("kraemer", "Krämerin Alvida", "laden", 26, 3, 0, False,
     "Alles für den Weg, ehrlich gewogen: Fackel gegen die Finsternis, Seil für die Not, Würfel für die Stube — und das Wanderpaket für 2 Gold. Nachts trinke ich aber nur selbst.",
     None, {"sortiment": {"i_fackel": 2, "i_seil": 3, "i_wuerfel": 1, "i_proviant": 2}}),
    ("buergermeister", "Bürgermeister Falkwin", "rathaus", 26, 2, 0, False,
     "Ach, ein Abenteurer! Meine Frau hat ihren Falken verloren, und im Brunnen geistert es. Sprich mit der Priesterin, wenn du Mut hast.",
     {"id": "q_brief", "name": "Der Botengang", "beschreibung": "Bringe das versiegelte Schreiben zum Burgherrn von Rabenstein.",
      "ziel": "burgherr", "item": "i_brief", "belohnung": ["w_schwert", "i_muenzen"], "xp": 40}),
    ("priesterin", "Priesterin Alwine", "kirche", 24, 2, 0, False,
     "Der Frieden dieses Landes ist zerbrochen. In den Katakomben unter dem Brunnen haust ein Skelettkönig. Fälle ihn, und bringe die alte Krone herbei.",
     {"id": "q_krone", "name": "Die verlorene Krone", "beschreibung": "Besiege den Skelettkönig und bringe die alte Krone.",
      "ziel": "priesterin", "item": "i_krone", "belohnung": ["a_kette", "i_heiltrank"], "xp": 100}),
    ("kraeuterhexe", "Weidenkraut die Hexe", "kraeuterladen", 22, 3, 0, False,
     "Heilkräuter aus dem Wald, frisch gepflückt, heilen jede Wunde. Bring mir drei Heilkräuter, und ich braue dir etwas Starkes.",
     {"id": "q_kraeuter", "name": "Der Kräutersud", "beschreibung": "Sammle 3 Heilkräuter im Nadelwald für die Hexe.",
      "ziel": "kraeuterhexe", "item": "i_kraeuter", "anzahl": 3, "belohnung": ["w_stab", "i_heiltrank"], "xp": 60}),
    ("schmied", "Meister Eisenbart", "schmiede", 40, 8, 2, False,
     "Klingen sind mein Leben. Bring mir ein Wolfsfell aus den Hügeln, dann schmiede ich dir eine Axt, wie sie kein anderer führt.",
     {"id": "q_fell", "name": "Fell für die Axt", "beschreibung": "Besiege einen Bergwolf und bring das Fell zum Schmied.",
      "ziel": "schmied", "item": "i_fell", "belohnung": ["w_axt", "a_leder"], "xp": 50}),
    ("bauer", "Bauer Hinnerk", "bauerhof", 28, 4, 0, False,
     "Verdammte Wölfe reißen mir die Schafe! Erschlag einen, und ich gebe dir Brot und was Münzen."),
    ("waffenmeister", "Waffenmeister Grimwald", "burg_waffenkammer", 60, 12, 4, False,
     "Zeig mir, was in dir steckt! Trainiere hier, wenn du dich stark genug fühlst. Der Burgherr sucht übrigens tapfere Schwerter."),
    ("burgherr", "Burgherr Rabenmark", "burg_halle", 80, 14, 5, False,
     "So, so. Ein weiterer Söldner. Meine Köchin braucht Pfeffer — und im Drachenhort über dem Gebirge haust der alte Glaurung. Beweise deinen Wert!",
     {"id": "q_drache", "name": "Die Schuppe des Glaurung", "beschreibung": "Öffne den Drachenhort (Hornschlüssel von einem Oger), besiege Glaurung und bringe dem Burgherrn eine Drachenschuppe.",
      "ziel": "burgherr", "item": "i_drachenschuppe", "belohnung": ["w_drachenschwert", "a_drachenschupp"], "gold": 100, "xp": 200}),
    ("koechin", "Köchin Gerlinde", "burg_kueche", 24, 2, 0, False,
     "Der Burgherr isst nur, wenn es im Halse brennt! Pfeffer gibt es in der Diebesgasse des Dorfes — bring ihn mir, ehe der Braten versalzen ist.",
     {"id": "q_gewuerz", "name": "Pfeffer für die Köchin", "beschreibung": "Finde Pfeffer aus dem Süden (Diebesgasse) und bringe ihn zur Köchin in die Burgküche.",
      "ziel": "koechin", "item": "i_gewuerz", "belohnung": ["a_schild", "i_heiltrank"], "xp": 70}),
    ("baerd", "Der Barde Fidel", "taverne", 18, 2, 0, False,
     "Von einem Schatz im Sumpf singt man Lieder... eine Karte soll es geben, bei den Räubern am Südweg."),
    ("wache", "Torwacht Jorck", "nordtor", 34, 6, 2, False,
     "Der Wald ist nicht sicher. Räuber und Wölfe, und schlimmeres aus dem Sumpf. Geh bewaffnet!"),
    ("hundefuehrer", "Jägerin Maren", "osttor", 30, 7, 1, False,
     "Im Gebirge hausen Oger. Wer die Drachenzahnklinge will, muss an ihnen vorbei. Und nimm ein Fell gegen die Kälte."),
]

# Aggressive NPCs werden im Generator auf Räume verteilt
monster_pool = [
    ("raeuber", "Straßenräuber", 22, 6, 1, "Halt! Deine Börse oder dein Leben!"),
    ("wolf", "Hungriger Wolf", 20, 5, 0, "Ein Wolf knurrt und legt die Ohren an."),
    ("bergwolf", "Bergwolf", 28, 7, 1, "Ein massiver Wolf mit grauem Fell kreist dich."),
    (" Goblin", None, 0, 0, 0, None),  # Platzhalter wird übersprungen
    ("goblin", "Grünhäutiger Goblin", 18, 4, 0, "Ki-ki-ki! Gold! Gold!"),
    ("ork", "Ork-Plünderer", 38, 9, 2, "Der Ork hebt seine zackige Klinge und brüllt."),
    ("skelett", "Wankendes Skelett", 24, 6, 1, "Knochen knirschen, als sich das Skelett aufrichtet."),
    ("spinne", "Riesenspinne", 26, 7, 0, "Acht Augen glitzern im Dunkeln."),
    ("sumpfkriecher", "Sumpfkriecher", 34, 8, 1, "Aus dem Morast erhebt sich eine schleimige Gestalt."),
    ("oger", "Oger des Graukamms", 60, 13, 3, "Der Oger schlägt mit einem Entiren Baumstamm nach dir."),
    ("banditen_anfuehrer", "Banditenanführer", 50, 11, 3, "Du betrittst das Lager der Banditen. Der Anführer zieht sein Schwert."),
    ("fledermaus", "Höhlenfledermaus", 12, 3, 0, "Ein Schwarm Fledermäuse stürzt kreischend herab."),
    ("grabgeist", "Grabgeist", 30, 8, 2, "Ein durchscheinender Geist fleht: 'Weiche... oder bleibe...'"),
]
monster_pool = [m for m in monster_pool if m[1]]

region_monsters = {
    "wald": ["raeuber", "wolf", "goblin", "spinne"],
    "huegel": ["raeuber", "bergwolf", "goblin", "banditen_anfuehrer"],
    "sumpf": ["sumpfkriecher", "spinne", "grabgeist"],
    "gebirge": ["bergwolf", "ork", "oger"],
    "katakomben": ["skelett", "fledermaus", "grabgeist"],
    "fluß": ["goblin", "raeuber"],
    "burg": [],
    "dorf": ["raeuber"],
}

loot_by_monster = {
    "raeuber": ["i_muenzen", "w_dolch"],
    "wolf": ["i_fell"],
    "bergwolf": ["i_fell", "i_muenzen"],
    "goblin": ["i_muenzen", "w_knueppel"],
    "ork": ["w_axt", "i_muenzen"],
    "skelett": ["i_knochen", "i_edelstein"],
    "spinne": ["i_kraeuter"],
    "sumpfkriecher": ["i_edelstein", "i_muenzen"],
    "oger": ["w_hammer", "i_edelstein"],
    "banditen_anfuehrer": ["w_langschwert", "i_karte", "i_muenzen"],
    "fledermaus": [],
    "grabgeist": ["i_amulett"],
}

rooms = {}

def add_room(rid, name, desc, region):
    rooms[rid] = {"id": rid, "name": name, "beschreibung": desc, "region": region,
                  "exits": {}, "items": [], "npcs": []}

# Dorf
for rid, name, desc in regions["dorf"]["rooms"]:
    add_room(rid, name, desc, "dorf")

# generierte Regionen
for reg, cfg in regions.items():
    if "count" not in cfg:
        continue
    for i in range(1, cfg["count"] + 1):
        rid = f"{reg}_{i}"
        name = f"{cfg['name']} ({i})"
        desc = random.choice(cfg["desc"]) + " " + random.choice(
            ["Die Luft ist klar.", "Es ist totenstill.", "Du fühlst dich beobachtet.",
             "Irgendwo in der Ferne schreit ein Vogel.", "Ein kalter Luftzug streift dich."])
        add_room(rid, name, desc, reg)

# Burg
for rid, name, desc in regions["burg"]["rooms"]:
    add_room(rid, name, desc, "burg")

# --- Verbindungen (Grid je Region + Übergänge) ---
def grid_connect(region, count, exits_extra=None):
    for i in range(1, count + 1):
        r = rooms[f"{region}_{i}"]
        if i > 1:
            r["exits"]["westen"] = f"{region}_{i-1}"
            rooms[f"{region}_{i-1}"]["exits"]["osten"] = f"{region}_{i}"
        if i + 6 <= count:
            r["exits"]["norden"] = f"{region}_{i+6}"
            rooms[f"{region}_{i+6}"]["exits"]["sueden"] = f"{region}_{i}"

for reg, cfg in regions.items():
    if "count" in cfg:
        grid_connect(reg, cfg["count"])

# Dorftore als Zentren
rooms["markt"]["exits"].update({"norden": "nordtor", "sueden": "suedtor", "osten": "osttor", "westen": "westtor",
                                 "oben": "taverne"})
rooms["nordtor"]["exits"]["sueden"] = "markt"; rooms["nordtor"]["exits"]["norden"] = "wald_1"
rooms["suedtor"]["exits"]["norden"] = "markt"; rooms["suedtor"]["exits"]["sueden"] = "fluß_1"
rooms["osttor"]["exits"]["westen"] = "markt"; rooms["osttor"]["exits"]["osten"] = "huegel_1"
rooms["westtor"]["exits"]["osten"] = "markt"; rooms["westtor"]["exits"]["westen"] = "sumpf_1"
rooms["taverne"]["exits"]["unten"] = "markt"
rooms["gasse"]["exits"]["norden"] = "markt"; rooms["markt"]["exits"]["sueden2"] = None  # platzhalter
rooms["rathaus"]["exits"]["sueden"] = "markt"
rooms["schmiede"]["exits"]["westen"] = "markt"
rooms["kraeuterladen"]["exits"]["osten"] = "markt"
rooms["kirche"]["exits"]["osten"] = "markt"
rooms["bauerhof"]["exits"]["sueden"] = "nordtor"
rooms["brunnen"]["exits"]["runter"] = "katakomben_1"
del rooms["markt"]["exits"]["sueden2"]
rooms["markt"]["exits"]["nordwesten"] = "brunnen"; rooms["brunnen"]["exits"]["suedosten"] = "markt"
rooms["markt"]["exits"]["nordosten"] = "rathaus"
rooms["markt"]["exits"]["suedwesten"] = "schmiede"
rooms["markt"]["exits"]["suedosten"] = "kraeuterladen"
rooms["markt"]["exits"]["nordwesten"] = "brunnen"  # bleibt
rooms["gasse"]["exits"].pop("norden"); rooms["gasse"]["exits"]["osten"] = "markt"; rooms["markt"]["exits"]["sueden"] = "gasse"
rooms["kirche"]["exits"]["osten"] = "markt"

# Regionen-Übergänge
rooms["huegel_1"]["exits"]["osten"] = "burg_hof"; rooms["burg_hof"]["exits"]["westen"] = "huegel_1"
rooms["huegel_8"]["exits"]["osten"] = "gebirge_1"; rooms["gebirge_1"]["exits"]["westen"] = "huegel_8"
rooms["fluß_5"]["exits"]["westen"] = "huegel_3"; rooms["huegel_3"]["exits"]["osten"] = "fluß_5"
rooms["sumpf_9"]["exits"]["norden"] = "fluß_9"; rooms["fluß_9"]["exits"]["sueden"] = "sumpf_9"
rooms["wald_12"]["exits"]["norden"] = "gebirge_3"; rooms["gebirge_3"]["exits"]["sueden"] = "wald_12"

# Burg innen
rooms["burg_hof"]["exits"].update({"norden": "burg_halle", "osten": "burg_waffenkammer", "westen": "burg_kueche", "oben": "burg_turm"})
rooms["burg_halle"]["exits"]["sueden"] = "burg_hof"; rooms["burg_halle"]["exits"]["norden"] = "burg_kapelle"
rooms["burg_waffenkammer"]["exits"]["westen"] = "burg_hof"
rooms["burg_kueche"]["exits"]["osten"] = "burg_hof"; rooms["burg_kueche"]["exits"]["norden"] = "burg_zinnen"
rooms["burg_turm"]["exits"]["unten"] = "burg_hof"
rooms["burg_kapelle"]["exits"]["sueden"] = "burg_halle"; rooms["burg_kapelle"]["exits"]["runter"] = "burg_kerker"
rooms["burg_zinnen"]["exits"]["sueden"] = "burg_kueche"
rooms["burg_kerker"]["exits"]["oben"] = "burg_kapelle"

# Fehlende Dorf-Verbindungen ergänzen
rooms["markt"]["exits"]["sueden"] = "suedtor"; rooms["suedtor"]["exits"]["norden"] = "markt"
rooms["markt"]["exits"]["gasse"] = "gasse"; rooms["gasse"]["exits"]["markt"] = "markt"
rooms["markt"]["exits"]["kapelle"] = "kirche"; rooms["kirche"]["exits"]["platz"] = "markt"
rooms["nordtor"]["exits"]["bauernhof"] = "bauerhof"; rooms["bauerhof"]["exits"]["nordtor"] = "nordtor"

# Rückwege aus allen Regionen (sonst sind Wald, Hügel, Burg & Co. Einbahnfallen)
rooms["wald_1"]["exits"]["sueden"] = "nordtor"
rooms["huegel_1"]["exits"]["westen"] = "osttor"
rooms["fluß_1"]["exits"]["nordwesten"] = "suedtor"
rooms["sumpf_1"]["exits"]["osten"] = "westtor"
rooms["katakomben_1"]["exits"]["oben"] = "brunnen"
rooms["burg_hof"]["exits"]["sueden"] = "huegel_1"

# Das Haus am Markt: Ausgang 'haus' vom Markt, Rückweg nach Osten
rooms["markt"]["exits"]["haus"] = "haus"
rooms["haus"]["exits"]["osten"] = "markt"

# Marktschenke und Gemischtwarenladen: benannte Ausgänge vom Markt,
# Rückweg über 'osten' und 'markt' (beide Schreibweisen funktionieren)
rooms["markt"]["exits"]["kneipe"] = "kneipe"
rooms["kneipe"]["exits"]["osten"] = "markt"
rooms["kneipe"]["exits"]["markt"] = "markt"
rooms["markt"]["exits"]["laden"] = "laden"
rooms["laden"]["exits"]["osten"] = "markt"
rooms["laden"]["exits"]["markt"] = "markt"
# fluß_1: der Rückweg zum Südtor bekommt das natürliche 'norden' — der alte
# Grid-Ausgang nach fluß_7 wandert nach nordosten (Graph bleibt identisch)
rooms["fluß_1"]["exits"]["nordosten"] = rooms["fluß_1"]["exits"].pop("norden")
rooms["fluß_1"]["exits"]["norden"] = "suedtor"

# Raum-Details für UNTERSUCHE <ding> — atmosphärisch, verrät keine Geheimnisse
details = {
    "markt": {"brunnen": "Der Brunnen gluckert leise vor sich hin. Auf dem Grund schimmern Münzen — zu tief, um sie je wiederzuholen.",
              "tafel": "Das Edikt erklärt feierlich, dass Geflügel nachts künftig nicht mehr krähen darf. Die Rechtschreibung ist grauenhaft."},
    "taverne": {"ecke": "In der Ecke stimmt der Barde seine Laute. Sein Lied handelt vom selben Schatz wie gestern. Und wie vorgestern."},
    "schmiede": {"amboss": "Der Amboss ist aus einem einzigen Felsblock gehauen. Feine Rillen erzählen, wie oft hier zugeschlagen wurde."},
    "rathaus": {"karte": "Eine alte Landkarte: Falkenmoor, der Nadelwald, die Hügel — und im Osten kleine Türme. Rote Zeichen markieren Orte, deren Bedeutung niemand mehr kennt."},
    "kraeuterladen": {"gläser": "In den Glasgefäßen schwimmen Wurzeln, Blüten und Dinge, die du lieber nicht benennst. Eines davon blubbert."},
    "kirche": {"kerzen": "Die Kerzen am Altar flackern, obwohl kein Luftzug geht. Als würde die Flamme hier etwas ertragen müssen."},
    "brunnen": {"grund": "Irgendwo tief unten hallt ein einzelner Tropfen. Ob dieser Brunnen überhaupt einen Boden hat?",
                "münzen": "Ein paar Kupfermünzen liegen offen im Wasser — selbst die hier will niemand berühren."},
    "suedtor": {"schild": "Das Schild warnt vor Straßenräubern. Darunter hat jemand gekritzelt: „Zu spät.“"},
    "bauerhof": {"feld": "Reihen von Kohl und Rüben — ein Drittel aufgefressen, mit Bißspuren, die viel zu groß sind."},
    "gasse": {"schatten": "Die Schatten in dieser Gasse haben Kanten. Besser, du berührst hier gar nichts."},
    "burg_hof": {"banner": "Ein Rabe mit ausgebreiteten Schwingen auf dunklem Tuch. Darunter, in alter Schrift: „Wir warten.“"},
    "burg_halle": {"stuhl": "Der erhöhte Stuhl ist Eichenholz; die Armlehnen sind von Jahren des Wartens glatt gerieben."},
    "burg_kerker": {"zelle": "Hinter den Gittern raschelt Stroh. Es klingt, als hätte sich darin längst etwas eingerichtet."},
    "burg_waffenkammer": {"klingen": "Reihen von Waffen — die meisten stumpf und verstaubt. Ein paar blitzen verdächtig scharf."},
    "burg_turm": {"blick": "Fern im Osten wabert ein rauchiger Dunst über das Gebirge. Keine Wolke sieht so aus wie diese."},
    "burg_kueche": {"kessel": "Im Kessel brodelt eine Suppe, deren Geruch allein den Magen füllt. Das Rezept ist ein gut gehütetes Geheimnis."},
    "burg_kapelle": {"fenster": "Das Bleiglas zeigt einen Ritter im Kampf gegen einen Drachen. Auf dem Fenster gewinnt der Drache."},
    "burg_zinnen": {"meer": "Bei klarem Wetter siehst du im Westen ein Rauchwölkchen über dem Meer — dort muss Möwenschrei liegen."},
    "hafen_markt": {"kisten": "In einer der Kisten kichern Fische noch immer leise vor sich hin."},
    "hafen_kneipe": {"teller": "Auf einem Tisch steht ein Krug Rum, der dort schon länger steht als der Wirt jung war."},
    "hafen_leuchtturm": {"fernrohr": "Durch das Fernrohr: Wellen, Möwen, Wellen. Dann etwas Dunkles auf dem Wasser — eine Möwe. Nur eine Möwe."},
    "hafen_wrack": {"rumpf": "Muscheln bedecken den Rumpf in sauberen Reihen, als hätte jemand sie sortiert. War aber niemand."},
    "hafen_speicher": {"fässer": "Eines der Fässer klopft von innen. Oder war das der Wind? Besser, du fragst nicht nach."},
    "mine_11": {"thron": "Der Thron aus Erz und Kristall ist prachtvoll. In die Armlehne ist etwas geritzt: drei Edelsteine."},
    "abtei_9": {"garten": "Im toten Garten blüht ein einziger Rosenstrauch. Die Blüten sind schwarz, und sie sollten es nicht sein."},
    "katakomben_16": {"runen": "Runen, krakelig in den Staub geritzt: „Mein Reich. Mein Krone. Mein.“"},
    "drachen_8": {"gold": "Selbst die Münzen auf dem Goldberg sind versengt. Was immer hier haust — es hat keinen Respekt vor Geld."},
}

# Duplikate in Exits aufräumen (letzter gewinnt)
for r in rooms.values():
    r["exits"] = dict(r["exits"])

# ================= ERWEITERUNG: Hafen, Mine, Abtei, Drachenhort =================

# Neue Items
items.update({
    "w_seemansmesser": ("Waffe", "Seemansmesser", "Klinge voller Salzspuren.", 6, 0),
    "w_spitzhacke": ("Waffe", "Zwergenspitzhacke", "Eher ein Werkzeug, aber wirksam.", 8, 0),
    "w_drachenschwert": ("Waffe", "Drachenschwert", "In Drachenfeuer geschmiedet. Die beste Klinge des Landes.", 25, 0),
    "a_harnisch": ("Rüstung", "Zwergenharnisch", "Gegossen in den Tiefen der Mine.", 8, 0),
    "a_moenchskutte": ("Rüstung", "Gesegnete Mönchskutte", "Stoff, doch mit einem Segen durchwoben.", 5, 0),
    "i_schluessel_gruft": ("Schlüssel", "Kryptaschlüssel", "Ein schwarzer Schlüssel mit Totenkopfbart.", 0, 0),
    "i_schluessel_drache": ("Schlüssel", "Hornschlüssel", "Geschnitzt aus einem Drachenhorn. Öffnet den Hort.", 0, 0),
    "i_reliquie": ("Quest", "Die Reliquie des Heiligen Falken", "Ein goldener Knochen, warm von innerem Licht.", 0, 0),
    "i_perle": ("Wertgegenstand", "Meeresperle", "Fängt das Licht wie Mondlicht.", 0, 0),
    "i_silbererz": ("Wertgegenstand", "Silbererz", "Noch ungeschmolzen.", 0, 0),
    "i_drachenschuppe": ("Quest", "Drachenschuppe", "Eine Schuppe des alten Glaurung.", 0, 0),
    "i_rum": ("Nahrung", "Krüger Rum", "Wärmt von innen.", 0, 0),
})
ITEM_EXTRAS = {
    "i_heiltrank": {"heilung": 20, "wert": 30}, "i_brot": {"wert": 3}, "i_kraeuter": {"wert": 8},
    # Kneipe: Getränke 1 Gold, Essen 2 Gold (Verkaufswert = Kaufpreis, kein Profit)
    "i_bier": {"heilung": 5, "wert": 1}, "i_kaffee": {"mana": 5, "wert": 1},
    "i_soda": {"heilung": 3, "wert": 1}, "i_wein": {"heilung": 4, "wert": 1},
    "i_tee": {"mana": 4, "wert": 1}, "i_suppe": {"heilung": 6, "wert": 2},
    "i_eintopf": {"heilung": 8, "wert": 2}, "i_braten": {"heilung": 10, "wert": 2},
    "i_met": {"heilung": 6, "wert": 1}, "i_fackel": {"wert": 2}, "i_seil": {"wert": 3},
    "i_wuerfel": {"wert": 1}, "i_proviant": {"heilung": 5, "wert": 2},
    "i_amulett": {"wert": 40}, "i_muenzen": {"wert": 25}, "i_edelstein": {"wert": 60},
    "i_kerze": {"wert": 2}, "i_fell": {"wert": 30}, "i_perle": {"wert": 45}, "i_silbererz": {"wert": 35},
    "i_rum": {"wert": 10}, "i_brief": {"wert": 0}, "i_karte": {"wert": 0}, "i_krone": {"wert": 0},
    "w_knueppel": {"wert": 5}, "w_dolch": {"wert": 20}, "w_seemansmesser": {"wert": 40},
    "w_spitzhacke": {"wert": 55}, "w_schwert": {"wert": 90}, "w_axt": {"wert": 110},
    "w_langschwert": {"wert": 160}, "w_stab": {"wert": 200}, "w_hammer": {"wert": 240},
    "w_drachenzahn": {"wert": 300}, "w_drachenschwert": {"wert": 500},
    "a_lendtuch": {"wert": 4}, "a_leder": {"wert": 40}, "a_schild": {"wert": 60},
    "a_moenchskutte": {"wert": 90}, "a_kette": {"wert": 130}, "a_harnisch": {"wert": 170},
    "a_platte": {"wert": 260}, "a_drachenschupp": {"wert": 400},
}

# Neue freundliche NPCs (mit Händlern & Quests)
npcs.extend([
    ("anschlagbrett", "Das Anschlagbrett", "markt", 999, 0, 0, False,
     "Ein breites Eichenbrett voller vergilbter Zettel, Bekanntmachungen und Angebote. BRETT liest es, BRETT <text> heftet eine Notiz an."),
    ("gildenmeister", "Gildenmeisterin Ehrentraut", "rathaus", 60, 10, 3, False,
     "Krieger, Magier oder Dieb? Jeder Weg hat seinen Lohn. Sprich GILDE, um die Gilden zu erfahren — hier im Rathaus kannst du beitreten."),
    ("bankier", "Bankier Wenzel", "rathaus", 28, 3, 1, False,
     "Die Stadtkasse verwahrt dein Gold — selbst Räuber und Monster kriegen sie nicht! EINZAHLEN <x> bringt Gold in Sicherheit, ABHEBEN <x> holt es zurück, KONTOSTAND zeigt alles. Beim Tod verlierst du nur das Bargeld in der Tasche."),
    ("briefkasten", "Der alte Briefkasten", "markt", 999, 0, 0, False,
     "Ein wetterfester Holzkasten mit dem Falkenwappen. Botsen reiten von hier in alle Welt: POST öffnet dein Fach, POST <name> <text> sendet einen Boten."),
    ("haendlerin", "Kauffrau Marlene", "hafen_markt", 30, 4, 0, False,
     "Ware aus aller Herren Länder! Was du nicht brauchst, kauf ich dir ab.",
     None, {"sortiment": {"w_seemansmesser": 35, "a_leder": 45, "i_heiltrank": 25, "i_brot": 4, "i_rum": 12, "a_schild": 55}}),
    ("schmied2", "Waffenhändler Turon", "burg_waffenkammer", 50, 10, 3, False,
     "Stahl kauft man bei mir, nicht beim Krämer.",
     None, {"sortiment": {"w_schwert": 80, "w_langschwert": 140, "a_kette": 120, "w_hammer": 200, "a_platte": 240}}),
    ("kraeuterhexe2", "Alraune die Händlerin", "kraeuterladen", 22, 2, 0, False,
     "Tränke und Salben, alles ehrlich gebraut.",
     None, {"sortiment": {"i_heiltrank": 25, "i_kraeuter": 10}}),
    ("kapitaen", "Kapitänin Isbella Möwenkraft", "hafen_steg", 38, 9, 2, False,
     "Ein Seeräuber namens Schwarzbart hat meine Karte gestohlen – darauf steht, wo der Schatz von Möwenschrei liegt. Bring sie mir!",
     {"id": "q_karte", "name": "Die Schatzkarte", "beschreibung": "Besiege Schwarzbart im Hafen und bringe die Schatzkarte der Kapitänin.",
      "ziel": "kapitaen", "item": "i_karte", "belohnung": ["w_seemansmesser", "i_perle"], "gold": 40, "xp": 60}),
    ("zwergenkoenig", "Zwergenkönig Eisenhelm", "mine_11", 70, 12, 4, False,
     "Tief drin unten wütet ein Steingolem. Doch zuerst: bring mir drei Edelsteine aus den Stollen, dann sollst du meinen Harnisch haben!",
     {"id": "q_edelsteine", "name": "Edelsteine für den König", "beschreibung": "Sammle 3 rote Edelsteine in der Mine für den Zwergenkönig.",
      "ziel": "zwergenkoenig", "item": "i_edelstein", "anzahl": 3, "belohnung": ["a_harnisch", "w_spitzhacke"], "gold": 30, "xp": 80}),
    ("abt", "Bruder Cedric", "abtei_9", 28, 5, 1, False,
     "Die Abtei ist gefallen. Untote Mönche wandeln umher, und die Reliquie des Heiligen Falken ist in die Gruft gerollt. Der Kryptaschlüssel... der Oger der Berge verschlang ihn.",
     {"id": "q_reliquie", "name": "Die Reliquie", "beschreibung": "Finde den Kryptaschlüssel (Oger im Gebirge), hole die Reliquie aus der Abtei-Gruft und bringe sie Bruder Cedric.",
      "ziel": "abt", "item": "i_reliquie", "belohnung": ["a_moenchskutte", "i_heiltrank"], "gold": 50, "xp": 90}),
    ("wirt2", "Wirtin Gerda", "taverne", 26, 3, 0, False,
     "Im Keller haust eine Riesenratte und stiehlt mein Silber! Wer ihr das Falkenamulett abnimmt, isst und trinkt eine Woche umsonst. Sonst: Getränke 1 Gold, Essen 2 — setz dich dazu!",
     {"id": "q_ratte", "name": "Die Kellerbestie", "beschreibung": "Besiege die Riesenratte im Tavernen-Keller (Nordwest-Weg vom Markt) und bringe das Falkenamulett der Wirtin.",
      "ziel": "wirt2", "item": "i_amulett", "belohnung": ["i_heiltrank"], "gold": 25, "xp": 30},
     {"sortiment": {"i_bier": 1, "i_soda": 1, "i_kaffee": 1, "i_tee": 1, "i_wein": 1,
                    "i_suppe": 2, "i_eintopf": 2, "i_braten": 2}, "nacht": True}),
    ("krugwirt", "Krugwirtin Solveig", "hafen_kneipe", 30, 4, 0, False,
     "Salz auf der Haut, Rum im Regal! Kaffee für Wachbleiber, Soda für Landratten — Getränke 1 Gold, Essen 2 Gold. Setz dich dazu, plaudern ist gratis.",
     None, {"sortiment": {"i_bier": 1, "i_soda": 1, "i_kaffee": 1, "i_tee": 1, "i_wein": 1,
                          "i_suppe": 2, "i_eintopf": 2, "i_braten": 2}, "nacht": True}),
])

# Neue Regionen: Hafen (handgearbeitet), Mine, Abtei, Drachenhort (generiert)
hafen_rooms = [
    ("hafen_tor", "Das Tor von Möwenschrei", "Möwen kreischen über den Dächern der Hafenstadt. Der Geruch von Salz und Teer liegt in der Luft."),
    ("hafen_markt", "Der Fischmarkt", "Aufgestapelte Kisten, rufende Händler, Fische in allen Farben. Eine Kauffrau winkt dir zu."),
    ("hafen_steg", "Die Landungsbrücke", "Boote schaukeln im Takt der Wellen. Eine Kapitänin mit Falkenumhang mustert das Meer."),
    ("hafen_kneipe", "Der Anker-Krug", "Rauer Haufen: Seeleute, Kartenmischer, ein Mann mit Augenklappe in der Ecke."),
    ("hafen_speicher", "Der Speicher", "Zwischen Ballen und Fässern ist es dunkel. Etwas raschelt."),
    ("hafen_leuchtturm", "Der Leuchtturm", "Von hier überblickt man die ganze Bucht. Der Wärter bietet dir fernrohr einen Blick an."),
    ("hafen_gasse", "Die Sabbelgasse", "Schmale Gasse, triefende Wände. Hier trauen sich nur Messerträger hin."),
    ("hafen_kai", "Am Kai", "Netze hängen zum Trocknen. Ein alter Fischer flickt seines und murmelt Gebete."),
    ("hafen_wrack", "Die Wrackbucht", "Ein gekentertes Schiff ragt aus dem Flutwasser. Vom Rumpf bis zum Mast ist alles mit Muscheln überzogen."),
]
for rid, name, desc in hafen_rooms:
    add_room(rid, name, desc, "hafen")

regions_neu = {
    "mine": {"name": "Die Zwergenmine", "count": 22,
             "desc": ["Stollengänge führen in die Tiefe, das Licht der Lampe schneidet kaum die Finsternis.",
                      "Schienen einer Lore ziehen sich am Boden entlang.",
                      "Der Schlag des Pickels hallt von fern.",
                      "Kristalle schimmern in der Wand.",
                      "Ein alter Grubenschacht öffnet sich neben dem Weg."]},
    "abtei": {"name": "Die Verfluchte Abtei", "count": 18,
              "desc": ["Kalter Weihrauch liegt in der Luft, obwohl niemand mehr hier räuchert.",
                       "Bleiglasfenster zeigen verblichene Heilige.",
                       "Ein Chor aus weiter Ferne – oder nur der Wind?",
                       "Kerzenstummel brennen von selbst, wenn du vorübergehst.",
                       "Grabplatten des Kreuzgangs sind teilweise zerbrochen."]},
    "drachen": {"name": "Der Drachenhort", "count": 8,
                "desc": ["Gebrannte Erde, kilometerweit. Hier ist alles Asche.",
                         "Knochen von Rindern und Schafen – oder waren es Ritter?",
                         "Die Hitme flimmert über schwarzem Gestein.",
                         "Ein Schwefelgeruch liegt über allem."]},
}
for reg, cfg in regions_neu.items():
    for i in range(1, cfg["count"] + 1):
        add_room(f"{reg}_{i}", f"{cfg['name']} ({i})",
                 random.choice(cfg["desc"]) + " " + random.choice(
                     ["Es ist totenstill.", "Du fühlst dich beobachtet.",
                      "Ein kalter Luftzug streift dich.", "Irgendwo tropft Wasser."]),
                 reg)

# Interne Vernetzung der generierten neuen Regionen
for reg, cfg in regions_neu.items():
    grid_connect(reg, cfg["count"])

# Hafen-Verbindungen (Kette mit Abzweigungen)
hafen_kette = ["hafen_tor", "hafen_markt", "hafen_steg", "hafen_kai", "hafen_wrack"]
for a, b in zip(hafen_kette, hafen_kette[1:]):
    rooms[a]["exits"]["osten"] = b; rooms[b]["exits"]["westen"] = a
rooms["hafen_markt"]["exits"]["norden"] = "hafen_kneipe"; rooms["hafen_kneipe"]["exits"]["sueden"] = "hafen_markt"
rooms["hafen_markt"]["exits"]["sueden"] = "hafen_speicher"; rooms["hafen_speicher"]["exits"]["norden"] = "hafen_markt"
rooms["hafen_steg"]["exits"]["norden"] = "hafen_leuchtturm"; rooms["hafen_leuchtturm"]["exits"]["sueden"] = "hafen_steg"
rooms["hafen_kneipe"]["exits"]["osten"] = "hafen_gasse"; rooms["hafen_gasse"]["exits"]["westen"] = "hafen_kneipe"

# Übergänge in die neue Welt
rooms["fluß_13"]["exits"]["sueden"] = "hafen_tor"; rooms["hafen_tor"]["exits"]["norden"] = "fluß_13"
rooms["huegel_15"]["exits"]["norden"] = "mine_1"; rooms["mine_1"]["exits"]["sueden"] = "huegel_15"
rooms["sumpf_20"]["exits"]["osten"] = "abtei_1"; rooms["abtei_1"]["exits"]["westen"] = "sumpf_20"

# Spezialräume: Minenhalle, Abtei-Kreuzgang/Gruft, Hort-Eingang (verschlossen)
rooms["mine_11"]["name"] = "Die große Minenhalle"
rooms["mine_11"]["beschreibung"] = "Ein gewaltiger Hohlraum unter dem Berg. Der Zwergenkönig hat hier seinen Thron aus Erz und Kristall errichtet."
rooms["abtei_9"]["name"] = "Der Kreuzgang der Abtei"
rooms["abtei_9"]["beschreibung"] = "Ein viereckiger Gang um einen toten Garten. Bruder Cedric hat hier zwischen den Säulen überlebt."
rooms["abtei_18"]["name"] = "Die Gruft der Abtei"
rooms["abtei_18"]["beschreibung"] = "Hinter einer verriegelten Steinpforte liegen die Gründer der Abtei. Die Reliquie glüht in einer Nische."
rooms["abtei_9"]["exits"]["norden"] = {"raum": "abtei_18", "verschlossen": True,
                                        "tuer": "die Steinpforte der Gruft", "schluessel": "i_schluessel_gruft"}
rooms["drachen_8"]["name"] = "Glaurungs Hort"
rooms["drachen_8"]["beschreibung"] = "Ein Krater aus geschmolzenem Stein. Auf einem Berg aus Gold ruht GLAURUNG, der alte Drache. Die Luft brennt."
rooms["drachen_4"]["exits"]["norden"] = {"raum": "drachen_8", "verschlossen": True,
                                          "tuer": "das Hornportal des Hortes", "schluessel": "i_schluessel_drache"}

# Drachenhort-Zugang vom Gebirge
rooms["gebirge_16"]["exits"]["osten"] = "drachen_1"; rooms["drachen_1"]["exits"]["westen"] = "gebirge_16"

# Neue Monster
monster_pool.extend([
    ("pirat", "Seeräuber", 26, 7, 1, "Der Pirat zieht einen Säbel: 'Kiek mal, wer da anspült!'"),
    ("schwarzbart", "Schwarzbart der Seeräuber", 55, 12, 3, "Der Seeräuber-Hauptmann lacht dunkel. Um seinen Hals baumelt eine Karte."),
    ("riesenratte", "Riesenratte", 24, 6, 0, "So groß wie ein Hund, mit gelben Zähnen und einem gestohlenen Amulett im Nackenfell."),
    ("hoehlenbaer", "Höhlenbär", 44, 10, 2, "Der Bär erhebt sich auf die Hinterbeine und brüllt."),
    ("steingolem", "Steingolem", 75, 13, 6, "Der Golem aus Erz und Fels erwacht mit tiefem Grollen."),
    ("untoter_moench", "Untoter Mönch", 30, 8, 2, "Der Mönch dreht sich um – sein Gesicht ist nur noch Schatten."),
    ("drache", "Glaurung der Alte Drache", 180, 20, 8, "GLAURUNG hebt seinen Schädel. Sein Atem glüht wie ein Ofen."),
])
region_monsters.update({
    "hafen": ["pirat"],
    "mine": ["goblin", "hoehlenbaer"],
    "abtei": ["untoter_moench", "grabgeist"],
    "drachen": [],
})
loot_by_monster.update({
    "pirat": ["i_muenzen", "i_rum"],
    "schwarzbart": ["i_karte", "i_perle"],
    "riesenratte": ["i_amulett", "i_muenzen"],
    "hoehlenbaer": ["i_fell"],
    "steingolem": ["i_edelstein", "i_silbererz"],
    "untoter_moench": ["i_kerze", "i_edelstein"],
    "drache": ["i_drachenschuppe", "w_drachenzahn"],
})

# Spezielle Monster (werden unten nach `special`-Definition ergänzt)
_extra_special = {
    "schwarzbart": ["hafen_gasse", "hafen_wrack"],
    "riesenratte": ["hafen_speicher"],
    "steingolem": ["mine_18"],
    "drache": ["drachen_8"],
}
# Hornschlüssel + Kryptaschlüssel bei Ogers
loot_by_monster["oger"] = ["i_schluessel_drache", "w_hammer"]
loot_by_monster.setdefault("grabgeist", []).append("i_schluessel_gruft")

# Quest-Antepassung: Brief wird bei Questannahme übergeben
for eintrag in npcs:
    if len(eintrag) > 8 and isinstance(eintrag[8], dict) and eintrag[8].get("id") == "q_brief":
        eintrag[8]["uebergabe"] = "i_brief"

# Neue verstreute Items
def _place(item_id, raum_id):
    rooms[raum_id]["items"].append(item_id)
for _ in range(4):
    _place("i_silbererz", random.choice([r for r in rooms if r.startswith("mine_")]))
    _place("i_perle", random.choice(["hafen_wrack", "hafen_kai", "hafen_speicher"]))
_place("i_edelstein", "mine_5"); _place("i_edelstein", "mine_9"); _place("i_edelstein", "mine_14")
_place("i_reliquie", "abtei_18")
_place("i_heiltrank", "hafen_kneipe"); _place("i_heiltrank", "mine_3"); _place("i_heiltrank", "abtei_5")
_place("w_seemansmesser", "hafen_kai"); _place("a_leder", "hafen_speicher")

# --- NPCs platzieren ---
world_npcs = {}
for entry in npcs:
    nid, name, raum, hp, dmg, df, aggr, dialog = entry[:8]
    quest = entry[8] if len(entry) > 8 and isinstance(entry[8], dict) else None
    laden = entry[9] if len(entry) > 9 else None
    world_npcs[nid] = {"id": nid, "name": name, "raum": raum, "hp": hp, "max_hp": hp,
                       "schaden": dmg, "verteidigung": df, "aggressiv": aggr,
                       "beschreibung": dialog, "quest": quest, "beute": [], "laden": laden}
    rooms[raum]["npcs"].append(nid)

# Monster verteilen
special = {
    "banditen_anfuehrer": ["huegel_20", "sumpf_15"],
    "oger": ["gebirge_12", "gebirge_16"],
    "grabgeist": ["katakomben_10", "katakomben_14"],
}
special.update(_extra_special)
placed_counts = {}
for reg, pool in region_monsters.items():
    if not pool:
        continue
    reg_room_ids = [rid for rid, r in rooms.items() if r["region"] == reg and rid.split("_")[0] == reg or (reg == "dorf" and rid in ("gasse",))]
    reg_room_ids = [rid for rid in reg_room_ids if rooms[rid]["region"] == reg]
    for rid in reg_room_ids:
        if random.random() < 0.45:
            mid = random.choice(pool)
            if mid in special:
                continue
            tag = f"{mid}_{placed_counts.get(mid, 0)}"
            placed_counts[mid] = placed_counts.get(mid, 0) + 1
            name, hp, dmg, df, dialog = [(m[1], m[2], m[3], m[4], m[5]) for m in monster_pool if m[0] == mid][0]
            world_npcs[tag] = {"id": tag, "name": name, "raum": rid, "hp": hp, "max_hp": hp,
                               "schaden": dmg, "verteidigung": df, "aggressiv": True,
                               "beschreibung": dialog, "quest": None, "beute": loot_by_monster.get(mid, [])}
            rooms[rid]["npcs"].append(tag)

for mid, spots in special.items():
    name, hp, dmg, df, dialog = [(m[1], m[2], m[3], m[4], m[5]) for m in monster_pool if m[0] == mid][0]
    for rid in spots:
        tag = f"{mid}_{placed_counts.get(mid, 0)}"
        placed_counts[mid] = placed_counts.get(mid, 0) + 1
        world_npcs[tag] = {"id": tag, "name": name, "raum": rid, "hp": hp, "max_hp": hp,
                           "schaden": dmg, "verteidigung": df, "aggressiv": True,
                           "beschreibung": dialog, "quest": None, "beute": loot_by_monster.get(mid, [])}
        rooms[rid]["npcs"].append(tag)

# Eintrittsräume der Regionen monsterfrei halten — dort soll man ankommen und
# sich orientieren können, nicht sofort in einen Kampf geraten
for rid in ("wald_1", "huegel_1", "sumpf_1", "fluß_1", "katakomben_1"):
    for nid in list(rooms[rid]["npcs"]):
        if world_npcs[nid]["aggressiv"]:
            del world_npcs[nid]
            rooms[rid]["npcs"].remove(nid)

# Skelettkönig (Boss)
world_npcs["skelettkoenig"] = {"id": "skelettkoenig", "name": "Der Skelettkönig", "raum": "katakomben_16",
                               "hp": 90, "max_hp": 90, "schaden": 15, "verteidigung": 5, "aggressiv": True,
                               "beschreibung": "Eine frostige Stimme flüstert: 'Mein Reich... mein...' Die alte Krone glänzt auf seinem Haupt.",
                               "quest": None, "beute": ["i_krone", "i_edelstein"]}
rooms["katakomben_16"]["npcs"].append("skelettkoenig")

# --- Items verteilen ---
def place(item_id, room_id):
    rooms[room_id]["items"].append(item_id)

# Startausrüstung & Dorffunde
place("w_knueppel", "markt"); place("i_brot", "markt"); place("i_heiltrank", "taverne")
place("w_dolch", "gasse"); place("i_gewuerz", "gasse"); place("i_amulett", "kirche")
place("a_lendtuch", "bauerhof"); place("i_brot", "bauerhof"); place("i_heiltrank", "kraeuterladen")
# Verstreute Funde
scatter = ["i_heiltrank", "i_brot", "i_kerze", "i_muenzen", "w_knueppel", "a_leder", "w_dolch", "i_kraeuter", "i_kerze", "i_muenzen"]
region_rooms = [rid for rid, r in rooms.items() if r["region"] in ("wald", "huegel", "sumpf", "gebirge", "katakomben", "fluß")]
for item in scatter:
    for _ in range(3):
        place(item, random.choice(region_rooms))
# Gute Stücke verstecken
place("w_langschwert", "huegel_25"); place("a_kette", "wald_30"); place("w_hammer", "gebirge_10")
place("a_platte", "burg_kerker"); place("w_drachenzahn", "gebirge_18"); place("a_drachenschupp", "katakomben_13")
place("w_stab", "sumpf_18"); place("i_edelstein", "katakomben_5"); place("i_karte", "sumpf_6")
place("i_heiltrank", "katakomben_9"); place("i_heiltrank", "gebirge_5"); place("i_heiltrank", "huegel_12")

# Details zuweisen — erst jetzt existieren alle Räume (auch Hafen, Mine, Abtei, Hort)
for rid, d in details.items():
    if rid in rooms:
        rooms[rid]["details"] = d

world = {
    "titel": f"Falkenmoor – Ein Abenteuer in {len(rooms)} Räumen",
    "start_raum": "markt",
    "spieler": {"hp": 50, "max_hp": 50, "schaden": 3, "verteidigung": 0, "start_gold": 10},
    "regionen": {k: v["name"] for k, v in regions.items()},
    "raeume": rooms,
    "npcs": world_npcs,
    "items": {k: dict({"typ": v[0], "name": v[1], "beschreibung": v[2],
                       "schaden": v[3], "verteidigung": v[4]}, **ITEM_EXTRAS.get(k, {}))
              for k, v in items.items()},
}

with open(__file__.replace("gen_world.py", "world.json"), "w", encoding="utf-8") as f:
    json.dump(world, f, ensure_ascii=False, indent=2)

print(f"Räume: {len(rooms)}, NPCs: {len(world_npcs)}, Items: {len(items)}")

← Zurück zum Lehrpfad