// Universum (Teil von index.html): Aus einem Startwert (stand.universum.saat) entstehen Welten
// auf einem unendlichen Raster - wie die Sternensysteme in Elite: gleiche Saat, gleiche Welten.
// Jede Welt hat eine der zehn Basiskarten (Assets/10_testmap/karten.js), ein Biom und mit etwas Glueck
// eine Siedlung, deren Bewohner aus dem Figurenbaukasten zusammengewuerfelt werden.
// Gehandelt wird wie in der Steinzeit: Ware gegen Ware ("6 Gaense fuer 1 Esel"), jede Siedlung
// bewertet anders - Reisen und Tauschen lohnt sich.

const KARTEN = window.KARTEN;
const REICHWEITE = 2.2;                                 // so weit reicht eine Reise (Rasterfelder)
const SIEDLUNG_CHANCE = 0.45, WELT_CHANCE = 0.55;

// Streuwert aus mehreren Zahlen (FNV-artig), Grundlage aller Zufallsquellen
function mische(...zahlen) {
  let h = 2166136261;
  for (const z of zahlen) { h ^= (z | 0) >>> 0; h = Math.imul(h, 16777619); h ^= h >>> 13; }
  return h >>> 0;
}
const wuerfel = (r, n) => Math.floor(r() * n);
const eineVon = (r, liste) => liste[wuerfel(r, liste.length)];

// Namen: Welten klingen nach Stein und Wald, Menschen kurz und freundlich
const W_ANFANG = ["Ur", "Ka", "Mo", "Ta", "Bra", "Lu", "Gor", "Ri", "Ne", "Os", "Dun", "Ha", "Wi", "Ul", "Fen", "Ark", "Sel", "Tor"];
const W_MITTE = ["mok", "ra", "tal", "ben", "dor", "lin", "gra", "vu", "sa", "kel", "run", "ma", "tor", "lis", "ga", "nor"];
const W_ENDE = ["", "", "", "a", "en", "is", "um", "o", "ar", "heim"];
const P_ANFANG = ["Ma", "Ro", "Ki", "Ta", "Lu", "Bo", "Ni", "Fe", "Gu", "Ha", "Se", "Oda", "Il", "Ve", "Ku", "Zo"];
const P_ENDE = ["ra", "lo", "na", "ko", "mi", "run", "ba", "sa", "ti", "le", "rik", "mo", "ja", "dan"];
const weltName = r => { const n = eineVon(r, W_ANFANG) + eineVon(r, W_MITTE) + eineVon(r, W_ENDE); return n[0] + n.slice(1).toLowerCase(); };
const personName = r => eineVon(r, P_ANFANG) + eineVon(r, P_ENDE);

// Tauschwaren: Grundwert in Zweigen. Siedlungen bieten an, was Hand und Netz hergeben -
// Hergestelltes und Fische - und wollen, was man sammelt.
// Tiere stehen bewusst NICHT hier: sie werden nicht gekauft, sondern gezaehmt (die
// Tagesarbeit "Tiere zähmen" in dorf.js, drei Fuettertage je Tier).
const WERT = {
  rotauge: 0.3, barsch: 0.4, karpfen: 0.5, hecht: 1.2,
  rosa: 0.1, ringel: 0.1, gaense: 0.1, lavendel: 0.15, kiesel: 0.06, ast: 0.08, eichel: 0.08,
  angel: 1, floete: 1, kranz: 0.5, strauss: 0.8, kette: 0.6, vogelhaus: 1.5, kraeuterfisch: 0.8,
  stein: 0.25,                 // Balken ist raus: bauen.js rechnet ihn seit je in Aeste um
};
// Die Tierarten gibt es weiter - als Haustiere, die man zaehmt (HAUSTIERE in dorf.js).
// Diese Liste bleibt, weil welt.js die Tiere auf der Karte daran erkennt.
const TIERE = ["gans", "huhn", "ziege", "schaf", "schwein", "esel", "hund"];
const HANDELSWAREN = Object.keys(WERT);
const TAUSCHMASS = "ast";      // in Zweigen wird gerechnet: 91 % der Waren bekommen so einen Kurs

// ---------------------------------------------------------------------------
// Welten
// ---------------------------------------------------------------------------
const weltCache = new Map();
const heimat = () => weltBei(0, 0);
function weltAusId(id) {
  if (id === "heim" || !id) return heimat();
  const m = /^w(-?\d+)_(-?\d+)$/.exec(id);
  return (m && weltBei(+m[1], +m[2])) || heimat();
}
function weltBei(gx, gy) {
  const saat = stand.universum.saat, heim = gx === 0 && gy === 0;
  const schluessel = `${saat}:${gx}:${gy}:${heim ? stand.biom : ""}`;
  if (weltCache.has(schluessel)) return weltCache.get(schluessel);
  const h = mische(saat, gx + 7919, gy + 104729);
  let w = null;
  if (heim || h % 1000 < WELT_CHANCE * 1000) {
    const r = zufallsquelle(h);
    const biome = window.BIOME || [{ id: "lichtung", name: "Sonnige Lichtung" }];
    const id = heim ? "heim" : `w${gx}_${gy}`;
    const name = weltName(r);
    w = { id, gx, gy, heim, name: heim ? stand.dorfname || t("Heimat") : name,
          px: gx + (heim ? 0 : (r() - 0.5) * 0.45), py: gy + (heim ? 0 : (r() - 0.5) * 0.45),
          karte: heim ? stand.universum.karte : 1 + wuerfel(r, KARTEN.karten.length),
          biom: heim ? stand.biom : eineVon(r, biome).id, bewohner: [], handel: null };
    if (heim) {
      w.bewohner = dorfBewohner();                           // die Lebenden des eigenen Dorfes (dorf.js)
    } else if (r() < SIEDLUNG_CHANCE) {
      const n = 1 + wuerfel(r, 3);
      for (let i = 0; i < n; i++)
        w.bewohner.push({ id: `${id}_${i}`, name: personName(r), figur: zufallsFigur(r), stimme: wuerfel(r, 1e9) });
      w.handel = handelErzeugen(r);
    }
  }
  weltCache.set(schluessel, w);
  return w;
}
function welterIn(zentrum, radius) {
  const liste = [];
  const n = Math.ceil(radius);                                  // Raster ist ganzzahlig
  for (let gy = zentrum.gy - n; gy <= zentrum.gy + n; gy++)
    for (let gx = zentrum.gx - n; gx <= zentrum.gx + n; gx++) {
      const w = weltBei(gx, gy);
      if (w && Math.hypot(w.px - zentrum.px, w.py - zentrum.py) <= radius + 0.01) liste.push(w);
    }
  return liste;
}
const biomVon = id => (window.BIOME || []).find(b => b.id === id) || { id, name: id };
const kartenName = nr => (KARTEN.karten[nr - 1] || {}).name || t("Karte {0}", nr);
// Bilder nur einmal laden (Tilesets je Biom, Hausbilder)
const bildCache = new Map();
const ladeGecacht = pfad => { if (!bildCache.has(pfad)) bildCache.set(pfad, ladeBild(pfad)); return bildCache.get(pfad); };
const tilesetBilder = (K, biomId) => {
  const ersatz = biomVon(biomId).bilder || {};
  return Promise.all(K.tilesets.map(ts => ladeGecacht(WURZEL + (ersatz[ts.name] || ts.bild))));
};
function karteMalen(g, K, bilder) {
  g.imageSmoothingEnabled = false;
  for (const n of K.reihenfolge) K.ebenen[n].forEach((z, y) => z.forEach((gid, x) => {
    if (!gid) return;
    const i = K.tilesets.findIndex(t => gid >= t.firstgid && gid < t.firstgid + t.anzahl), ts = K.tilesets[i];
    if (!ts) return;                                         // Kachel ohne Blatt ueberspringen
    const k = gid - ts.firstgid;
    if (bilder[i]) g.drawImage(bilder[i], (k % ts.spalten) * ts.tw, Math.floor(k / ts.spalten) * ts.th, ts.tw, ts.th,
                               x * TK, (y + 1) * TK - ts.th, ts.tw, ts.th);
  }));
}
// Standbild einer Basiskarte in einem Biom (Vorschau bei der Anmeldung; geschmueckt wie die Heimat)
async function kartenBild(nr, biomId, c, saat = 42) {
  const K = schmuecken(karteDaten(nr), saat, [], { heim: true });
  karteMalen(c.getContext("2d"), K, await tilesetBilder(K, biomId));
  return c;
}
// Abbild einer Welt, so wie sie wirklich aussieht (fuer die Weltkarte)
const weltBildCache = new Map();
function weltBild(w) {
  const schluessel = `${w.id}:${w.biom}`;
  if (!weltBildCache.has(schluessel)) weltBildCache.set(schluessel, (async () => {
    const G = karteDaten(w.karte);
    const K = schmuecken(G, weltSaat(w), [], { heim: !!w.heim });
    const c = document.createElement("canvas"); c.width = K.breite * TK; c.height = K.hoehe * TK;
    karteMalen(c.getContext("2d"), K, await tilesetBilder(K, w.biom));
    return c;
  })());
  return weltBildCache.get(schluessel);
}

// Zufaellige Figur aus dem Baukasten (wie "Zufall" in der Schmiede, aber mit eigener Quelle)
function zufallsFigur(r) {
  const n = k => wuerfel(r, k);
  const teil = k => 1 + n(OPTIONEN[k].length - 1);
  const f = { ...START, pose: 0, richtung: 0, vorlage: 0, haut: n(HAUTTOENE.length), unterw: 0, art: n(2),
    outfit: teil("outfit"), ober: teil("ober"), hose: teil("hose"), schuhe: teil("schuhe"),
    acc: n(2) ? 0 : teil("acc"), frisur: 1 + n(FRISUREN.length), haarfarbe: n(HAARFARBEN.length),
    hut: n(3) ? 0 : n(3), hutfarbe: n(5), waffe: 0, schild: 0 };
  Object.keys(OPTIONEN).forEach(k => {
    const opt = OPTIONEN[k][Math.min(f[k], OPTIONEN[k].length - 1)];
    f[k + "Farbe"] = n(Math.max(1, opt.farben.length));
  });
  return f;
}
// Spritesheet (Bewegungsseite, 8x8 Frames) einer Figur zeichnen
// ---------------------------------------------------------------------------
// Klassenkleidung: wer eine Klasse ausuebt, traegt deren Outfit
// ---------------------------------------------------------------------------
// Das Atlas bringt 15 Berufsoutfits mit; fuenf davon passen auf die Spielklassen. Der Name
// verweist auf den Eintrag in D.komplett (OPTIONEN.outfit), die Beruf-Id auf den Hut.
const KLASSE_OUTFIT = {
  farmer:  { outfit: "Bauer",       beruf: null },          // Bauer hat keinen eigenen Hut
  jaeger:  { outfit: "Jäger",       beruf: "jaeger" },
  schmied: { outfit: "Schmied",     beruf: "schmied" },
  bauer:   { outfit: "Bauarbeiter", beruf: "bauarbeiter" },
  druide:  { outfit: "Kräuterfrau", beruf: null },
};
// Die Figur so umstellen, dass sie das Outfit ihrer Klasse traegt
function klassenKleidung(figur, klasse) {
  const k = KLASSE_OUTFIT[klasse];
  if (!k || !D || !D.komplett) return figur;
  const nr = D.komplett.findIndex(o => o.name === k.outfit);
  if (nr < 0) return figur;
  const f = { ...figur, art: 0, outfit: nr + 1, outfitFarbe: 0 };   // art 0 = komplettes Outfit
  if (k.beruf) {
    const bi = (D.berufe || []).findIndex(b => b.id === k.beruf);
    // Huete: 0 = keiner, dann die freien Formen, dann die Berufshuete
    if (bi >= 0) { f.hut = 1 + (D.huete || []).length + bi; f.hutfarbe = 0; }
  } else {
    f.hut = 0;
  }
  return f;
}

async function figurBlatt(figur, klasse = null) {
  await seiteLaden("p1");
  // Auf wirklich jedes Blatt warten, das schon angefordert wurde. ebene() laesst eine Schicht
  // stillschweigend weg, solange ihr Bild noch laedt - und das Ergebnis wird dauerhaft
  // zwischengespeichert. Dadurch fehlte je nach Ladereihenfolge mal der Hut, mal die Jacke,
  // und die Bewohner sahen nach jedem Neuladen anders aus.
  await Promise.all([...bilder.values()].map(e => e.bereit));
  const alt = { ...s };
  // Auf einem sauberen Grundzustand aufsetzen, nicht auf der zuletzt gezeichneten Figur:
  // sonst erbt ein Bewohner Felder, die seine eigene Figur nicht setzt (Waffe, Schild,
  // Farben) - und die stammen dann vom Spieler, der sich im Lauf des Spiels veraendert.
  Object.assign(s, START, klasse ? klassenKleidung(figur, klasse) : figur);
  const c = document.createElement("canvas");
  c.width = c.height = 512;
  const g = c.getContext("2d");
  for (let zeile = 0; zeile < 8; zeile++)
    for (let spalte = 0; spalte < 8; spalte++) g.drawImage(figurFrame("p1", zeile, spalte), spalte * 64, zeile * 64);
  Object.assign(s, alt);
  return c;
}

// ---------------------------------------------------------------------------
// Handel: jede Siedlung bewertet Waren anders; angeboten wird Hergestelltes und Fisch
// ---------------------------------------------------------------------------
function handelErzeugen(r) {
  const faktor = {};
  HANDELSWAREN.forEach(w => { faktor[w] = 0.6 + r() * 1.0; });
  // Nur was genug wert ist, taugt als Angebot - fuer eine Handvoll Kiesel faehrt niemand
  // ueber Land. Billiges bleibt den Wuenschen vorbehalten.
  const waren = HANDELSWAREN.filter(w => WERT[w] >= 0.4 && w !== TAUSCHMASS).sort(() => r() - 0.5);
  const angebot = waren.slice(0, 2 + wuerfel(r, 2));
  const mass = TAUSCHMASS;
  const rest = HANDELSWAREN.filter(w => !angebot.includes(w) && w !== mass).sort(() => r() - 0.5);
  const wunsch = [mass, ...rest.slice(0, 3)];
  wunsch.slice(1).forEach(w => { faktor[w] *= 1.3; });             // Gesuchtes ist dort mehr wert
  return { faktor, angebot, wunsch, saat: wuerfel(r, 1e9) };
}
const wertIn = (w, ware) => WERT[ware] * w.handel.faktor[ware];
// Kurse: fuer jede angebotene Ware das Tauschmass (Zweige) und ein weiterer Wunsch; moeglichst
// "1 Stueck fuer n Zweige" - erst wenn das zu schief waere, mehr auf einmal
function kurse(w) {
  const liste = [];
  const r = zufallsquelle(mische(w.handel.saat, spielTagNummer()));
  const kurs = (g, h) => {
    let best = null;
    for (let m = 1; m <= 4; m++) {
      const n = Math.ceil(m * wertIn(w, h) / wertIn(w, g) - 1e-9);
      if (n > 20) break;
      const guete = (n * wertIn(w, g)) / (m * wertIn(w, h));
      if (!best || guete < best.guete - 0.02) best = { gib: [g, n], bekomme: [h, m], guete };
      if (guete <= 1.25) break;
    }
    return best;
  };
  for (const h of w.handel.angebot) {
    const [mass, ...andere] = w.handel.wunsch;
    const k1 = mass !== h && kurs(mass, h);
    if (k1) liste.push(k1);
    // Ein weiterer Wunsch, der zu dieser Ware passt (billige Dinge nicht zu Dutzenden)
    for (const g of [...andere].sort(() => r() - 0.5)) {
      const k2 = g !== h && kurs(g, h);
      if (k2) { liste.push(k2); break; }
    }
  }
  return liste;
}
// Ohne gewaehlte Welt (Titelbild) gab es hier einen Absturz: aktWelt ist dann null, und
// jeder Aufrufer musste das selbst wissen. Einmal hat es genau deshalb das ganze Titelbild
// haengen lassen. Jetzt faellt es auf ein eigenes Fach zurueck, statt zu werfen.
function weltSpeicher(id = (typeof aktWelt !== "undefined" && aktWelt ? aktWelt.id : "titel")) {
  stand.welten = stand.welten || {};
  return (stand.welten[id] = stand.welten[id] || {});
}
function vorrat(w, ware) {
  const r = zufallsquelle(mische(w.handel.saat, spielTagNummer(), HANDELSWAREN.indexOf(ware)));
  const basis = 2 + wuerfel(r, 5);
  const ws = weltSpeicher(w.id);
  const heute = ws.gekauft && ws.gekauft.tag === spielTagSchluessel() ? ws.gekauft.waren[ware] || 0 : 0;
  return Math.max(0, basis - heute);
}
function tauschen2(w, k) {
  const [g, n] = k.gib, [h, m] = k.bekomme;
  if (vorrat(w, h) < m) return meldung(t("{0} sind hier für heute ausgetauscht.", dingName(h, true)));
  const ergebnis = umtauschen([[[g], n]], [[h, m]]);
  if (ergebnis === "fehlt") return meldung(t("Dafür brauchst du {0} – du hast {1}.", menge(g, n), anzahlIn([g])));
  if (ergebnis === "voll") return meldung(t("Deine Taschen sind voll – mach erst Platz."));
  const ws = weltSpeicher(w.id);
  if (!ws.gekauft || ws.gekauft.tag !== spielTagSchluessel()) ws.gekauft = { tag: spielTagSchluessel(), waren: {} };
  ws.gekauft.waren[h] = (ws.gekauft.waren[h] || 0) + m;
  zaehle("getauscht");
  if (w.gesinnung !== undefined) w.gesinnung = Math.min(5, (w.gesinnung || 0) + 1);   // Handel schafft Vertrauen
  if (TIERE.includes(h)) zaehle("tiere", m);
  geraeusch("tausch");
  meldung(t("{0} gegen {1} getauscht", menge(g, n), menge(h, m)));
  speichernBald();
  meilensteinePruefen();
}

// ---------------------------------------------------------------------------
// Tauschbrett (Menue anstelle der Karte)
// ---------------------------------------------------------------------------
let handelOffen = false, handelWahl = 0, handelKurse = [];
let handelZiel = null;                                     // Welt oder Nachbardorf, mit dem getauscht wird
// Mit wem wird gehandelt? Das Nachbardorf, wenn eines gewaehlt ist - sonst die Welt, in der
// man steht. Die Tastenbedienung rechnete hier frueher immer mit aktWelt: beim Nachbardorf
// griff sie damit auf w.handel der Heimat zu, die gar keinen Handel hat.
const handelWelt = () => (handelZiel && handelZiel.handel ? handelZiel : aktWelt);

function handelZeigen(an, ziel = null) {
  menueWechsel(an ? "handel" : null);
  handelOffen = an;
  document.getElementById("handel").hidden = !an;
  if (!an) return;
  if (ziel) handelZiel = ziel;
  const w = handelWelt();
  handelKurse = kurse(w);
  handelWahl = Math.min(handelWahl, handelKurse.length - 1);
  document.getElementById("handelkopf").textContent = t("Tauschen in {0}", w.name);
  const liste = document.getElementById("handelliste");
  liste.innerHTML = "";
  handelKurse.forEach((k, i) => {
    const [g, n] = k.gib, [h, m] = k.bekomme, hast = anzahlIn([g]), da = vorrat(w, h);
    const zeile = document.createElement("div");
    zeile.className = "kurs" + (i === handelWahl ? " an" : "") + (hast >= n && da >= m ? " geht" : "");
    const bild = d => { const c = document.createElement("canvas"); c.width = c.height = DING_TK; c.getContext("2d").drawImage(dingBild(d), 0, 0); return c; };
    const links = document.createElement("span"); links.className = "seite";
    links.append(bild(g), Object.assign(document.createElement("b"), { textContent: menge(g, n) }));
    const rechts = document.createElement("span"); rechts.className = "seite";
    rechts.append(bild(h), Object.assign(document.createElement("b"), { textContent: menge(h, m) }));
    const pfeil = Object.assign(document.createElement("span"), { className: "pfeil", textContent: "→" });
    const info = Object.assign(document.createElement("small"), { textContent: t("du hast {0} · Vorrat {1}", hast, da) });
    zeile.append(links, pfeil, rechts, info);
    zeile.addEventListener("click", () => {                  // erst auswaehlen, erst der zweite Klick tauscht
      const schon = handelWahl === i; handelWahl = i;
      if (schon) tauschen2(w, k);
      handelZeigen(true);
    });
    liste.append(zeile);
  });
  // Ein Satz zum Kursgefuege: das teuerste Tier in Gaensen
  const teuer = [...w.handel.angebot].sort((a, b) => wertIn(w, b) - wertIn(w, a))[0];
  const inGaensen = Math.max(1, Math.round(wertIn(w, teuer) / wertIn(w, "gans")));
  document.getElementById("handelfuss").textContent =
    teuer === "gans" ? t("Hier ist eine Gans etwa {0} Hühner wert.", Math.round(wertIn(w, "gans") / wertIn(w, "huhn")))
    : inGaensen === 1 ? t("Hier gilt: 1 {0} ist etwa 1 Gans wert.", dingName(teuer))
                      : t("Hier gilt: 1 {0} ist etwa {1} Gänse wert.", dingName(teuer), inGaensen);
}
function handelTaste(k) {
  if (k === "Escape" || k === "t") handelZeigen(false);
  else if (k === "ArrowUp" || k === "ArrowDown" || k === "w" || k === "s") {
    handelWahl = (handelWahl + (k === "ArrowUp" || k === "w" ? -1 : 1) + handelKurse.length) % handelKurse.length;
    handelZeigen(true);
  } else if ((k === "Enter" || k === " ") && handelKurse[handelWahl]) { tauschen2(handelWelt(), handelKurse[handelWahl]); handelZeigen(true); }
  return true;
}

// Gespraech mit den Leuten aus einem Nachbardorf. Die Saetze stammten aus der Zeit, als sie
// auf einer fremden WELT standen - "Du kommst von weit her, oder?" zu jemandem, der zwei
// Parzellen weit gelaufen ist und sie demnaechst anschliesst, klang seltsam (K2).
const FREMD_GRUSS = ["Oh, Besuch aus dem Nachbardorf! Willkommen in {welt}.",
  "Ihr da drüben und wir hier in {welt} – wir sollten öfter tauschen.",
  "Selten, dass jemand von euch herüberkommt. Schön, dass du da bist!", "Hallo! Was bringst du denn mit?"];
const FREMD_WIEDER = ["Schön, dich wiederzusehen, {name}!", "Na, {name}? Wieder unterwegs?", "Da bist du ja wieder!",
  "Ich hab mich schon gefragt, wann du mal wieder vorbeischaust.", "Du kennst ja den Weg.",
  "Setz dich ruhig einen Moment, {name}."];
// Was ein Bekannter statt der Vorstellung sagt. Vorher kam "Ich bin X. Hier tauschen wir, wie
// es schon immer war" bei JEDER Begegnung - beim zwanzigsten Mal weiss man das.
const FREMD_DAZU = ["Bei uns gibt's gerade mehr, als wir brauchen. Sag an, wenn dir was fehlt.",
  "Die Nächte werden nicht leichter, oder? Bei uns auch nicht.",
  "Was macht euer Dorf? Steht schon was?",
  "Tauschen ist gut. Reden ist auch nicht schlecht.",
  "Wir haben neulich am Bach was gefunden. Frag mich mal beim Tauschen."];
function fremdReden(n) {
  const p = PERSONEN[n.id], heute = spielTagSchluessel(), f = stand.freundschaft[n.id] || 0;
  // Wie bei den Dorfbewohnern: die Quelle hing nur an Stimme und Tagnummer, zweimal ansprechen
  // ergab wortgleich dasselbe. Jedes Gespraech zaehlt jetzt mit.
  const dd = typeof dorf === "function" ? dorf() : null;
  if (dd) dd.geredet = (dd.geredet || 0) + 1;
  const r = zufallsquelle(mische(n.stimme || 1, spielTagNummer() * 977 + (dd ? dd.geredet : 0)));
  const frischF = liste => (typeof frischerSatz === "function" ? frischerSatz(r, liste) : eineVon(r, liste));
  const wo = (typeof dorfVon === "function" && dorfVon(n.id) ? dorfVon(n.id).name : aktWelt.name);
  const seiten = [t(f ? frischF(FREMD_WIEDER) : frischF(FREMD_GRUSS)).replaceAll("{welt}", wo),
                  // Vorgestellt wird man sich einmal, nicht bei jedem Treffen
                  f ? t(frischF(FREMD_DAZU))
                    : t("Ich bin {0}. Hier tauschen wir, wie es schon immer war: Ware gegen Ware.", p.name)];
  if (stand.gesprochen[n.id] !== heute) { stand.freundschaft[n.id] = f + 1; stand.gesprochen[n.id] = heute; }
  stand.bekannte = stand.bekannte || {};
  stand.bekannte[n.id] = { name: p.name, welt: wo };
  speichernBald();
  const wahl = [];
  const heimatDorf = typeof dorfVon === "function" ? dorfVon(n.id) : null;
  const zielDorf = heimatDorf || (aktWelt.handel ? aktWelt : null);
  if (zielDorf) wahl.push({ text: t("Tauschen"), tun: () => { setTimeout(() => handelZeigen(true, zielDorf), 0); return null; } });
  if (stand.geschenkt[n.id] !== heute && stand.taschen.some(Boolean)) wahl.push({ text: t("Etwas schenken"), tun: () => schenkenWahl(n) });
  wahl.push({ text: t("Tschüss!") });
  dialogStart(n, seiten, { wahl: wahl.length > 1 ? wahl : null, danach: meilensteinePruefen });
}

// ---------------------------------------------------------------------------
// Weltkarte: Welten in Reichweite, Reisen per Enter
// ---------------------------------------------------------------------------
let reiseOffen = false, reiseZiel = null, reiseRueck = null, reiseMalung = 0;
const KARTENMASS = 86;                                     // Pixel je Rasterfeld auf der Weltkarte
function reiseZeigen(an, rueck = null) {
  menueWechsel(an ? "reise" : null);
  reiseOffen = an;
  document.getElementById("reisekarte").hidden = !an;
  if (!an) { if (reiseRueck) { Object.assign(spieler, reiseRueck); reiseRueck = null; } return; }
  if (rueck) reiseRueck = rueck;
  const welten = welterIn(aktWelt, REICHWEITE + 0.3);
  if (!reiseZiel || !welten.includes(reiseZiel)) reiseZiel = aktWelt;
  weltkarteMalen(welten);
  reiseInfo();
}
// Die Weltkarte ist aus denselben Kacheln wie die Welten: Wiese und Wald im Biom der aktuellen Welt,
// Erdwege zu den erreichbaren Welten, jede Welt als kleines Abbild ihrer echten Karte
async function weltkarteMalen(welten) {
  const nr = ++reiseMalung;
  const c = document.getElementById("sternkarte"), g = c.getContext("2d");
  const B = c.width, Hh = c.height;
  const pos = w => [Math.round(B / 2 + (w.px - aktWelt.px) * KARTENMASS), Math.round(Hh / 2 + (w.py - aktWelt.py) * KARTENMASS)];
  const K = karteDaten(aktWelt.karte), bilder = await tilesetBilder(K, aktWelt.biom);
  if (nr !== reiseMalung) return;
  const iForest = K.tilesets.findIndex(t => t.name === "gentle forest v01"), iBaum = K.tilesets.findIndex(t => t.name === "gentle trees 80x96 v01");
  const forest = bilder[iForest], baumBild = bilder[iBaum];
  g.imageSmoothingEnabled = false;
  const r = zufallsquelle(mische(stand.universum.saat, aktWelt.gx, aktWelt.gy, 7));
  // Wiese
  for (let y = 0; y < Hh; y += 16) for (let x = 0; x < B; x += 16) {
    const id = [97, 98, 82, 97, 98][Math.floor(r() * 5)];
    if (forest) g.drawImage(forest, (id % 16) * 16, Math.floor(id / 16) * 16, 16, 16, x, y, 16, 16);
  }
  // Wege zu erreichbaren Welten (Erde, wie die Wege auf den Karten)
  const erreichbar = welten.filter(w => w !== aktWelt && abstand(w) <= REICHWEITE + 0.01);
  const [ax, ay] = pos(aktWelt);
  for (const w of erreichbar) {
    const [bx, by] = pos(w), n = Math.ceil(Math.hypot(bx - ax, by - ay) / 3);
    for (let i = 0; i <= n; i++) {
      const x = Math.round(ax + (bx - ax) * i / n), y = Math.round(ay + (by - ay) * i / n);
      g.fillStyle = "#6e5236"; g.fillRect(x - 3, y - 3, 6, 6);
      g.fillStyle = "#9a7a52"; g.fillRect(x - 2, y - 2, 4, 4);
    }
  }
  // Wald zwischen den Welten: halb so grosse Baeume, nicht auf Welten und Wegen
  const frei = (x, y) => welten.every(w => { const [wx, wy] = pos(w); return Math.abs(wx - x) > 44 || Math.abs(wy - y) > 40; });
  const baeume = [];
  for (let i = 0; i < 90; i++) { const x = Math.floor(r() * B), y = Math.floor(r() * Hh); if (frei(x, y)) baeume.push([x, y, Math.floor(r() * 2)]); }
  baeume.sort((a, b) => a[1] - b[1]).forEach(([x, y, v]) => { if (baumBild) g.drawImage(baumBild, v * 80, 0, 80, 96, x - 20, y - 44, 40, 48); });
  // Welten: kleines Abbild der echten Karte mit Rahmen, Name auf einem Schildchen
  const TB = 64, TH = 45;
  g.font = "12px ManaSeedBody, monospace"; g.textAlign = "center"; g.textBaseline = "middle";
  const reihe = [...welten].sort((a, b) => (a === reiseZiel) - (b === reiseZiel) || a.py - b.py);
  for (const w of reihe) {
    const bild = await weltBild(w);
    if (nr !== reiseMalung) return;
    const [x, y] = pos(w), nah = w === aktWelt || abstand(w) <= REICHWEITE + 0.01;
    const x0 = x - TB / 2, y0 = y - TH / 2;
    g.globalAlpha = nah ? 1 : 0.45;
    g.fillStyle = "#1e120a"; g.fillRect(x0 - 3, y0 - 3, TB + 6, TH + 6);
    g.imageSmoothingEnabled = true; g.drawImage(bild, x0, y0, TB, TH); g.imageSmoothingEnabled = false;
    if (w.bewohner.length) { g.fillStyle = "#1e120a"; g.fillRect(x0 + TB - 9, y0 - 5, 10, 10); g.fillStyle = "#e8b84a"; g.fillRect(x0 + TB - 7, y0 - 3, 6, 6); }
    if (w === aktWelt) { g.strokeStyle = "#8a2a2a"; g.lineWidth = 3; g.strokeRect(x0 - 5, y0 - 5, TB + 10, TH + 10); }
    if (w === reiseZiel) { g.strokeStyle = "#e8b84a"; g.lineWidth = 3; g.strokeRect(x0 - 8, y0 - 8, TB + 16, TH + 16); }
    if (nah || w === reiseZiel) {
      const tb = Math.ceil(g.measureText(w.name).width) + 10;
      g.fillStyle = "#1e120a"; g.fillRect(x - tb / 2 - 2, y0 + TH + 5, tb + 4, 17);
      g.fillStyle = "#e9d8a6"; g.fillRect(x - tb / 2, y0 + TH + 7, tb, 13);
      g.fillStyle = "#3b2a1a"; g.fillText(w.name, x, y0 + TH + 14);
    }
    g.globalAlpha = 1;
  }
}
function reiseInfo() {
  const z = reiseZiel, info = document.getElementById("reiseinfo");
  const wieViele = z.bewohner.length === 1 ? t("eine Person") : t("{0} Leute", z.bewohner.length);
  const zeilen = [
    [t("Welt"), z === aktWelt ? t("{0} (hier)", z.name) : z.name],
    [t("Biom"), biomVon(z.biom).name],
    [t("Gelände"), kartenName(z.karte)],
    [t("Leben"), z.heim ? t("dein Dorf")
      : z.bewohner.length ? `${wieViele}: ${z.bewohner.map(b => b.name).join(", ")}`
                          : t("niemand – nur Natur")],
    [t("Tausch"), z.handel ? t("bieten {0}", z.handel.angebot.map(a => dingName(a, true)).join(", ")) : "–"],
    [t("Weg"), z === aktWelt ? "–" : t("{0} Tagesmärsche", abstand(z).toFixed(1))],
  ];
  info.innerHTML = "";
  zeilen.forEach(([k, v]) => { const dt = document.createElement("dt"); dt.textContent = k; const dd = document.createElement("dd"); dd.textContent = v; info.append(dt, dd); });
  document.getElementById("reiseknopf").disabled = z === aktWelt || abstand(z) > REICHWEITE + 0.01;
}
const abstand = w => Math.hypot(w.px - aktWelt.px, w.py - aktWelt.py);
function reiseTaste(k) {
  const RICHT = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1], a: [-1, 0], d: [1, 0], w: [0, -1], s: [0, 1] };
  if (k === "Escape") reiseZeigen(false);
  else if (RICHT[k]) {
    // Naechste Welt in dieser Richtung (nur in Reichweite)
    const [rx, ry] = RICHT[k], z = reiseZiel;
    let best = null, wert = Infinity;
    for (const w of welterIn(aktWelt, REICHWEITE)) {
      if (w === z) continue;
      const dx = w.px - z.px, dy = w.py - z.py, vor = dx * rx + dy * ry;
      if (vor <= 0.05) continue;
      const quer = Math.abs(dx * ry - dy * rx), v = vor + quer * 2;
      if (v < wert) { wert = v; best = w; }
    }
    if (best) { reiseZiel = best; reiseZeigen(true); }
  } else if (k === "Enter" || k === " ") reisen();
  return true;
}
document.getElementById("reiseknopf").addEventListener("click", () => reisen());
document.getElementById("sternkarte").addEventListener("click", ev => {
  const c = ev.currentTarget, rect = c.getBoundingClientRect();
  const x = (ev.clientX - rect.left) * c.width / rect.width, y = (ev.clientY - rect.top) * c.height / rect.height;
  const mass = KARTENMASS;
  let best = null, wert = 36;
  for (const w of welterIn(aktWelt, REICHWEITE)) {
    const d = Math.hypot(c.width / 2 + (w.px - aktWelt.px) * mass - x, c.height / 2 + (w.py - aktWelt.py) * mass - y);
    if (d < wert) { wert = d; best = w; }
  }
  if (best) { reiseZiel = best; reiseZeigen(true); }
});
async function reisen() {
  const ziel = reiseZiel, von = aktWelt;
  if (!ziel || ziel === von || abstand(ziel) > REICHWEITE + 0.01) return;
  reiseRueck = null;
  reiseZeigen(false);
  zaehle("reisen");
  if (!(stand.besucht || []).includes(ziel.id)) stand.besucht = [...(stand.besucht || []), ziel.id];
  meldung(t("Du wanderst nach {0} …", ziel.name), 2500);
  // Ankunft auf der Seite, die zur alten Welt zeigt
  const dx = von.px - ziel.px, dy = von.py - ziel.py;
  const seite = Math.abs(dx) > Math.abs(dy) ? (dx < 0 ? "W" : "O") : "S";
  await weltWechseln(ziel, seite);
  meldung(ziel.heim ? t("Wieder zu Hause.")
          : ziel.bewohner.length ? t("Willkommen in {0}.", ziel.name)
                                 : t("Willkommen in {0} – hier lebt niemand.", ziel.name), 3000);
  meilensteinePruefen();
}
// Welt laden und Figur an einen Ausgang (oder den Start) stellen
async function weltWechseln(ziel, seite = null) {
  weltLaedt = weltLaden(ziel);
  await weltLaedt;
  const aus = KARTE.ausgaenge.filter(a => a.seite === seite);
  const a = aus.length ? aus[Math.floor(aus.length / 2)] : null;
  let p;
  if (a) {
    const [ix, iy] = { W: [2, 0], O: [-2, 0], S: [0, -2] }[a.seite];
    p = { ...mitte(a.x + ix, a.y + iy), blick: { W: "O", O: "W", S: "N" }[a.seite] };
    if (!begehbar(p.x, p.y)) p = null;
  }
  if (!p) p = { ...mitte(KARTE.start.x, KARTE.start.y), blick: "O" };
  Object.assign(spieler, { x: p.x, y: p.y, blick: p.blick, bewegt: false, bild: 0, seit: 0, sprung: 0 });
  stand.ort = { welt: ziel.id, x: Math.round(p.x), y: Math.round(p.y), blick: p.blick };
  letzterOrt = { ...stand.ort };
  speichernBald();
}

// ---------------------------------------------------------------------------
// Weltschmuck: Die Basiskarte liefert nur das Gelaende (Wege, Wasser, Anhoehen). Waldrand, Baeume,
// Buesche, Felsen, Baumstamm, Blumen, Graeser und Kiesel wuerfelt jede Welt aus ihrem eigenen
// Startwert - mal dichter Wald, mal Blumenwiese, mal steinig. So sieht keine Welt wie die andere aus.
// Nichts darf einen Weg abschneiden: jedes feste Stueck wird nur gesetzt, wenn danach noch alles
// erreichbar ist. Reservierte Flaechen (Bauplaetze) und Plaetze der Bewohner bleiben frei.
// ---------------------------------------------------------------------------
const weltSaat = w => (w && w.id !== "titel" && stand ? mische(stand.universum.saat, w.gx, w.gy, 424242) : 42);
// heim: die Heimat bleibt lichter (weniger Baeume, Buesche, Felsen innen) - dort wird gebaut
function schmuecken(K, saat, reserviert = [], { heim = false } = {}) {
  const r = zufallsquelle(saat), W = K.breite, H = K.hoehe;
  const zahl = n => Math.floor(r() * n);
  const ts = n => { const b = K.tilesets.find(t => t.name === n); return b ? b.firstgid : 0; };
  const F = ts("gentle forest v01"), T32 = ts("gentle 32x32 v01"), BAUM = ts("gentle trees 80x96 v01");
  const leer = () => Array.from({ length: H }, () => Array(W).fill(0));
  const ebenen = { ...K.ebenen, deko: leer(), objekte: leer(), gross: leer(), baeume: leer(), haus: leer() };
  const fest = K.grund.map(z => z.slice());
  const belegt = K.wiese.map(z => z.map(v => !v));
  const sperre = (x, y) => { if (y >= 0 && y < H && x >= 0 && x < W) belegt[y][x] = true; };
  [K.start, ...(K.plaetze || []), ...(K.npcs || [])].forEach(p => { for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) sperre(p.x + dx, p.y + dy); });
  reserviert.forEach(q => { for (let y = q.y; y < q.y + q.h; y++) for (let x = q.x - 1; x <= q.x + q.b; x++) sperre(x, y); });
  // Charakter dieser Welt
  const art = { wald: r() * (heim ? 0.4 : 1), buesche: Math.round((2 + zahl(9)) * (heim ? 0.4 : 1)), gross: Math.round((4 + zahl(16)) * (heim ? 0.4 : 1)), blumen: 0.01 + r() * 0.09, graeser: 0.01 + r() * 0.06,
                lavendel: r() * r() * 0.05, kiesel: 0.004 + r() * 0.02, mix: [0.2 + r(), 0.2 + r(), 0.2 + r()] };
  if (heim) { art.kiesel = Math.max(art.kiesel, 0.05); art.blumen = Math.max(art.blumen, 0.05); }   // genug zum Bauen
  // Erreichbarkeit vom Start aus
  const erreichbar = () => {
    const gesehen = new Uint8Array(W * H), stapel = [K.start.y * W + K.start.x];
    gesehen[stapel[0]] = 1;
    let n = 0;
    while (stapel.length) {
      const i = stapel.pop(), x = i % W, y = (i - x) / W; n++;
      for (const [nx, ny] of [[x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]])
        if (nx >= 0 && ny >= 0 && nx < W && ny < H && !fest[ny][nx] && !gesehen[ny * W + nx]) { gesehen[ny * W + nx] = 1; stapel.push(ny * W + nx); }
    }
    return n;
  };
  let basis = erreichbar();
  const setzen = (zellen, malen) => {
    if (zellen.some(([x, y]) => x < 0 || y < 0 || x >= W || y >= H || belegt[y][x])) return false;
    zellen.forEach(([x, y]) => { fest[y][x] = 1; });
    const n = erreichbar();
    if (n < basis - zellen.length) { zellen.forEach(([x, y]) => { fest[y][x] = 0; }); return false; }
    basis = n;
    zellen.forEach(([x, y]) => { belegt[y][x] = true; });
    malen();
    return true;
  };
  // Baeume (80x96, unten links an der Zelle verankert; Stamm in der dritten Spalte)
  const randGras = (x, y) => y >= 0 && y < H && x >= 0 && x < W && !K.untergrund[y][x] && !K.wasser[y][x];
  const baum = (x, y) => {
    if (x < 0 || x > W - 5 || y < 2 || y >= H) return false;
    const sx = x + 2, malen = () => { ebenen.baeume[y][x] = BAUM + zahl(2); };
    if (K.grund[y][sx]) {                                  // Stamm im Waldrand: dort ist ohnehin alles fest
      if (!randGras(sx, y) || ebenen.baeume[y][x]) return false;
      malen(); return true;
    }
    return setzen([[sx, y]], malen);
  };
  const luecke = 0.05 + (1 - art.wald) * 0.2;              // lichter Wald hat Luecken im Rand
  for (let x = 0; x < W; x += 3) if (r() > luecke) baum(x + zahl(2), 5);   // Stamm direkt am Rand: erreichbar
  for (let y = 9; y < H; y += 3) { baum(zahl(2), y + zahl(2)); baum(W - 5 - zahl(2), y + zahl(2)); }
  for (let x = 0; x < W; x += 4) if (r() > luecke) baum(x + zahl(2), H - 1);
  const innen = Math.round(art.wald * 9);
  for (let i = 0, n = 0; i < 60 && n < innen; i++) n += baum(2 + zahl(W - 9), 9 + zahl(H - 11));
  // Mehrteilige Objekte (16er-Kacheln): Busch 2x2, Fels 2x2, Baumstamm 3x2
  const mehrteilig = (x0, y0, ids) => {
    const zellen = [];
    ids.forEach((z, dy) => z.forEach((_, dx) => zellen.push([x0 + dx, y0 + dy])));
    return setzen(zellen, () => ids.forEach((z, dy) => z.forEach((id, dx) => { ebenen.objekte[y0 + dy][x0 + dx] = F + id; })));
  };
  if (r() < 0.6) for (let i = 0; i < 40 && !mehrteilig(2 + zahl(W - 6), 7 + zahl(H - 10), [[56, 57, 58], [72, 73, 74]]); i++);
  if (r() < 0.6) for (let i = 0; i < 40 && !mehrteilig(2 + zahl(W - 5), 7 + zahl(H - 10), [[86, 87], [102, 103]]); i++);
  for (let i = 0, n = 0; i < 120 && n < art.buesche; i++) n += mehrteilig(1 + zahl(W - 4), 7 + zahl(H - 9), [[8, 9], [24, 25]]);
  // 32er-Objekte (Stumpf, Fels, Busch, Farn): Anker unten links, 2x2 Kacheln
  const felsig = r();
  for (let i = 0, n = 0; i < 150 && n < art.gross; i++) {
    const x = 1 + zahl(W - 3), y = 7 + zahl(H - 8);
    const id = r() < felsig * 0.5 ? 1 : [0, 2, 3, 3][zahl(4)];
    n += setzen([[x, y], [x + 1, y], [x, y - 1], [x + 1, y - 1]], () => { ebenen.gross[y][x] = T32 + id; });
  }
  // Jeder Baum muss erreichbar sein: neben dem Stamm ein Feld, das man vom Start aus erreicht
  {
    const gesehen = new Uint8Array(W * H), stapel = [K.start.y * W + K.start.x];
    gesehen[stapel[0]] = 1;
    while (stapel.length) {
      const i = stapel.pop(), x = i % W, y = (i - x) / W;
      for (const [nx, ny] of [[x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]])
        if (nx >= 0 && ny >= 0 && nx < W && ny < H && !fest[ny][nx] && !gesehen[ny * W + nx]) { gesehen[ny * W + nx] = 1; stapel.push(ny * W + nx); }
    }
    for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
      if (!ebenen.baeume[y][x]) continue;
      const sx = x + 2;
      const nah = [[sx + 1, y], [sx - 1, y], [sx, y + 1], [sx, y - 1]].some(([nx, ny]) => nx >= 0 && ny >= 0 && nx < W && ny < H && gesehen[ny * W + nx]);
      if (!nah) { ebenen.baeume[y][x] = 0; if (!K.grund[y][sx]) fest[y][sx] = 0; }
    }
  }
  // Kleine Deko auf freier Wiese
  const summe = art.mix.reduce((a, b) => a + b, 0);
  const blume = () => { let z = r() * summe; for (let i = 0; i < 3; i++) { if ((z -= art.mix[i]) < 0) return [104, 105, 106][i]; } return 106; };
  for (let y = 6; y < H - 1; y++) for (let x = 1; x < W - 1; x++) {
    if (belegt[y][x]) continue;
    const z = r();
    if (z < art.blumen) ebenen.deko[y][x] = F + blume();
    else if (z < art.blumen + art.graeser) ebenen.deko[y][x] = F + (r() < 0.5 ? 42 : 26);
    else if (z < art.blumen + art.graeser + art.lavendel) ebenen.deko[y][x] = F + 90;
    else if (z < art.blumen + art.graeser + art.lavendel + art.kiesel) { ebenen.deko[y][x] = F + (r() < 0.5 ? 88 : 89); fest[y][x] = 1; }
  }
  return { ...K, ebenen, fest, art };
}
