// Fähigkeiten (Teil von index.html): Je Klasse drei Zweige mit vier Stufen. Jede Klassenstufe
// gibt einen Punkt für den Baum dieser Klasse. Arbeitsboni gelten nur in der Klasse, in der
// man gerade arbeitet - was den Körper stärkt (Gesundheit, Angriff, Ausdauer), bleibt immer.

// wirkung: essen/holz/stein/kraeuter = Ertrag je Tagesarbeit · angriff/verteidigung/gesund/ausdauer = Werte
//          handlungen = eine große Handlung mehr am Tag (nur beim Spieler)
//          heilen = Pflege · bauen = Bauarbeit · schmieden = Schmiedearbeit · mauer = Stärke der Mauer
//          nachtheilung = heilt das Dorf jede Nacht · zaehmen = Haustiere · koennen = Kampffähigkeit
const SKILLS = {
  farmer: [
    { name: "Acker", stufen: [
      { name: "Gute Saat",     text: "+1 Essen, Feld wie Jagd",       wirkung: { essen: 1 } },
      { name: "Fruchtfolge",   text: "nochmal +1 Essen",              wirkung: { essen: 1 } },
      { name: "Vorratskeller", text: "+2 Essen, +1 Handlung am Tag",  wirkung: { essen: 2, handlungen: 1 } },
      { name: "Erntefest",     text: "heilt jede Nacht 1 im Dorf",    wirkung: { nachtheilung: 1 } },
    ] },
    { name: "Hof", stufen: [
      { name: "Futtermeister", text: "Haustiere geben +1 Essen",      wirkung: { haustieressen: 1 } },
      { name: "Zugtier",       text: "+1 Holz je Sammeltag",          wirkung: { holz: 1 } },
      { name: "Weidezaun",     text: "+1 Bauarbeit",                  wirkung: { bauen: 1 } },
      { name: "Hofherr",       text: "+2 Essen je Arbeit",        wirkung: { essen: 2 } },
    ] },
    { name: "Dreschflegel", stufen: [
      { name: "Zäher Rücken",  text: "+3 Gesundheit",                 wirkung: { gesund: 3 } },
      { name: "Dreschflegel",  text: "+1 Angriff",                    wirkung: { angriff: 1 } },
      { name: "Langer Atem",   text: "+2 Ausdauer",                   wirkung: { ausdauer: 2 } },
      { name: "Sense",         text: "Kampf: trifft alle Monster",    wirkung: { koennen: "sense" } },
    ] },
  ],
  jaeger: [
    { name: "Pirsch", stufen: [
      { name: "Fährtenleser",  text: "+1 Essen, Jagd wie Feld",       wirkung: { essen: 1 } },
      { name: "Schlingen",     text: "nochmal +1 Essen",              wirkung: { essen: 1 } },
      { name: "Wildwechsel",   text: "+2 Essen je Arbeit",              wirkung: { essen: 2 } },
      { name: "Großwild",      text: "+3 Essen je Arbeit",              wirkung: { essen: 3 } },
    ] },
    { name: "Fell und Horn", stufen: [
      { name: "Gerber",        text: "+1 Holz je Sammeltag",          wirkung: { holz: 1 } },
      { name: "Hornbogen",     text: "+1 Angriff",                    wirkung: { angriff: 1 } },
      { name: "Köcher",        text: "+1 Angriff",                    wirkung: { angriff: 1 } },
      { name: "Jagdglück",     text: "+1 Kräuter je Suche",           wirkung: { kraeuter: 1 } },
    ] },
    { name: "Bogen", stufen: [
      { name: "Scharfes Auge", text: "+1 Angriff",                    wirkung: { angriff: 1 } },
      { name: "Ruhige Hand",   text: "+2 Ausdauer",                   wirkung: { ausdauer: 2 } },
      { name: "Wildhüter",     text: "+3 Gesundheit",                 wirkung: { gesund: 3 } },
      { name: "Gezielter Schuss", text: "Kampf: doppelter Schaden",   wirkung: { koennen: "schuss" } },
    ] },
  ],
  // Der Schmiedebonus verteilt sich abwechselnd: die halbe Punktzahl auf den Angriff,
  // der Rest auf die Verteidigung. Der zweite Punkt legt deshalb nichts drauf, er
  // verschiebt nur - der Text sagt das jetzt, statt mehr zu versprechen (Beschluss P6).
  schmied: [
    { name: "Esse", stufen: [
      { name: "Hammerschlag",  text: "+1 Schmiedearbeit",             wirkung: { schmieden: 1 } },
      { name: "Doppelschlag",  text: "+1 Angriff statt Schild",       wirkung: { schmieden: 1 } },
      { name: "Härten",        text: "+1 Schmiedearbeit",             wirkung: { schmieden: 1 } },
      { name: "Meisterstück",  text: "+2 Schmiedearbeit",             wirkung: { schmieden: 2 } },
    ] },
    { name: "Rüstung", stufen: [
      { name: "Nieten",        text: "+1 Verteidigung",               wirkung: { verteidigung: 1 } },
      { name: "Kettenhemd",    text: "+1 Verteidigung",               wirkung: { verteidigung: 1 } },
      { name: "Buckelschild",  text: "+1 Verteidigung",               wirkung: { verteidigung: 1 } },
      { name: "Plattner",      text: "+2 Verteidigung",               wirkung: { verteidigung: 2 } },
    ] },
    { name: "Waffen", stufen: [
      { name: "Schneide",      text: "+1 Angriff",                    wirkung: { angriff: 1 } },
      { name: "Wucht",         text: "+2 Angriff",                    wirkung: { angriff: 2 } },
      { name: "Standfest",     text: "+3 Gesundheit",                 wirkung: { gesund: 3 } },
      { name: "Schildwall",    text: "Kampf: +3 Verteidigung für alle", wirkung: { koennen: "schildwall" } },
    ] },
  ],
  bauer: [
    { name: "Gerüst", stufen: [
      { name: "Lehre",         text: "+1 Bauarbeit",                  wirkung: { bauen: 1 } },
      { name: "Zimmerung",     text: "nochmal +1",                    wirkung: { bauen: 1 } },
      { name: "Doppelschicht", text: "+2 Bauarbeit, +1 Handlung am Tag", wirkung: { bauen: 2, handlungen: 1 } },
      { name: "Baumeister",    text: "+2 Bauarbeit",                  wirkung: { bauen: 2 } },
    ] },
    { name: "Mauerwerk", stufen: [
      { name: "Steinbruch",    text: "+1 Stein je Sammeltag",         wirkung: { stein: 1 } },
      { name: "Mörtel",        text: "Mauer hält mehr aus",           wirkung: { mauer: 1 } },
      { name: "Wehrgang",      text: "Mauer hält mehr aus",           wirkung: { mauer: 1 } },
      { name: "Bollwerk",      text: "Mauer hält deutlich mehr aus",  wirkung: { mauer: 2 } },
    ] },
    { name: "Fäuste", stufen: [
      { name: "Kräftig",       text: "+3 Gesundheit",                 wirkung: { gesund: 3 } },
      { name: "Vorschlaghammer", text: "+2 Angriff",                  wirkung: { angriff: 2 } },
      { name: "Zäh",           text: "+1 Verteidigung",               wirkung: { verteidigung: 1 } },
      { name: "Barrikade",     text: "Kampf: halber Schaden für alle", wirkung: { koennen: "barrikade" } },
    ] },
  ],
  druide: [
    { name: "Kräuter", stufen: [
      { name: "Kräuterkunde",  text: "+1 Kräuter je Suche",           wirkung: { kraeuter: 1 } },
      { name: "Salben",        text: "+1 Pflege",                     wirkung: { heilen: 1 } },
      { name: "Tinkturen",     text: "+1 Pflege",                     wirkung: { heilen: 1 } },
      { name: "Wurzelwerk",    text: "+2 Kräuter je Suche",           wirkung: { kraeuter: 2 } },
    ] },
    { name: "Magie", stufen: [
      { name: "Erster Funke",  text: "+1 Pflege",                     wirkung: { heilen: 1 } },
      { name: "Lebenskraft",   text: "heilt jede Nacht 1 im Dorf",    wirkung: { nachtheilung: 1 } },
      { name: "Bann",          text: "+1 Verteidigung",               wirkung: { verteidigung: 1 } },
      { name: "Segen",         text: "+1 Angriff",                    wirkung: { angriff: 1 } },
    ] },
    { name: "Hain", stufen: [
      { name: "Tierfreund",    text: "zähmt Tiere schneller",         wirkung: { zaehmen: 1 } },
      { name: "Wildkräuter",   text: "+1 Essen je Arbeit",        wirkung: { essen: 1 } },
      { name: "Dornen",        text: "+1 Angriff",                    wirkung: { angriff: 1 } },
      { name: "Heilkraut",     text: "Kampf: heilt 6 ohne Vorrat",    wirkung: { koennen: "heilkraut" } },
    ] },
  ],
  // Kampf-Tree (Beschluss 30.09.): klassenlos, fuer JEDEN lernbar, gespeist aus
  // Kampferfahrung. Bis dahin steckten Gesundheit, Angriff, Wehr und Ausdauer in den
  // Kampfzweigen der Klassen - 21 von 60 Faehigkeiten. Wer eine Klasse ohne solchen Zweig
  // spielte, kam nie an diese Werte; seit der Farmer-Zweig zu "Werkzeuge" wurde, waere ein
  // Farmer dauerhaft bei 10 Gesundheit geblieben. Dieser Baum ist keine Arbeitsklasse: er
  // hat in KLASSEN keine `aufgabe`, darum faellt er aus KLASSENLISTE heraus.
  kampf: [
    { name: "Gesundheit", stufen: [
      { name: "Zäher Rücken",  text: "+3 Gesundheit",                 wirkung: { gesund: 3 } },
      { name: "Standfest",     text: "+3 Gesundheit",                 wirkung: { gesund: 3 } },
      { name: "Kräftig",       text: "+3 Gesundheit",                 wirkung: { gesund: 3 } },
      { name: "Barrikade",     text: "Kampf: halber Schaden für alle", wirkung: { koennen: "barrikade" } },
    ] },
    { name: "Angriff", stufen: [
      { name: "Schneide",      text: "+1 Angriff",                    wirkung: { angriff: 1 } },
      { name: "Wucht",         text: "+1 Angriff",                    wirkung: { angriff: 1 } },
      { name: "Vorschlaghammer", text: "+1 Angriff",                  wirkung: { angriff: 1 } },
      { name: "Sense",         text: "Kampf: trifft alle Monster",    wirkung: { koennen: "sense" } },
    ] },
    { name: "Wehr", stufen: [
      { name: "Nieten",        text: "+1 Verteidigung",               wirkung: { verteidigung: 1 } },
      { name: "Kettenhemd",    text: "+1 Verteidigung",               wirkung: { verteidigung: 1 } },
      { name: "Plattner",      text: "+1 Verteidigung",               wirkung: { verteidigung: 1 } },
      { name: "Schildwall",    text: "Kampf: +3 Verteidigung für alle", wirkung: { koennen: "schildwall" } },
    ] },
    { name: "Ausdauer", stufen: [
      { name: "Langer Atem",   text: "+2 Ausdauer",                   wirkung: { ausdauer: 2 } },
      { name: "Ruhige Hand",   text: "+2 Ausdauer",                   wirkung: { ausdauer: 2 } },
      { name: "Durchhalten",   text: "+2 Ausdauer",                   wirkung: { ausdauer: 2 } },
      { name: "Gezielter Schuss", text: "Kampf: doppelter Schaden",   wirkung: { koennen: "schuss" } },
    ] },
    { name: "Heilung", stufen: [
      { name: "Salben",        text: "+1 Pflege",                     wirkung: { heilen: 1 } },
      { name: "Tinkturen",     text: "+1 Pflege",                     wirkung: { heilen: 1 } },
      { name: "Erster Funke",  text: "+1 Pflege",                     wirkung: { heilen: 1 } },
      { name: "Heilkraut",     text: "Kampf: heilt 6 ohne Vorrat",    wirkung: { koennen: "heilkraut" } },
    ] },
  ],
};
// Der klassenlose Baum. Seine Arbeitswirkungen (Pflege) zaehlen in JEDER Klasse mit,
// seine Eintraege verfallen bei keinem Berufswechsel.
const KAMPF_BAUM = "kampf";
// Kampffähigkeiten, die ein Baum freischaltet
const KOENNEN = {
  sense:      { name: "Sense",           text: "trifft alle Monster" },
  schuss:     { name: "Gezielter Schuss", text: "doppelter Schaden" },
  schildwall: { name: "Schildwall",      text: "+3 Verteidigung, eine Runde" },
  barrikade:  { name: "Barrikade",       text: "halber Schaden, eine Runde" },
  heilkraut:  { name: "Heilkraut",       text: "heilt 6 ohne Kräuter" },
};

// Vierter Zweig: gibt es nur mit Haustier (das Konzept: ein Tier öffnet einen Zweig)
const GEFAEHRTE = { name: "Gefährte", stufen: [
  { name: "Futterhand",   text: "Haustiere geben +1 Essen",       wirkung: { haustieressen: 1 } },
  { name: "Treue",        text: "+2 Gesundheit",                 wirkung: { gesund: 2 } },
  { name: "Wachsam",      text: "+1 Verteidigung",               wirkung: { verteidigung: 1 } },
  { name: "Seite an Seite", text: "+2 Angriff",                  wirkung: { angriff: 2 } },
] };
const zweigeVon = (id, klasse) => wert(id).haustier ? [...SKILLS[klasse], GEFAEHRTE] : SKILLS[klasse];

const KOERPER = ["gesund", "angriff", "verteidigung", "ausdauer"];   // gilt in jeder Klasse
const ausdauerVon = id => (wert(id).ausdauer || 6) + bonus(id, "ausdauer");

function gelernte(id, klasse) {
  const w = wert(id);
  w.skills = w.skills || {};
  return (w.skills[klasse] = w.skills[klasse] || []);       // ["Acker:2", ...] = Zweig und Stufe
}
// Verfaellt dieser gelernte Eintrag beim Berufswechsel? Koerperwerte gelten in jeder Klasse
// weiter - sie mitzuzaehlen hat die Warnung im Dorfbuch uebertrieben und von folgenlosen
// Wechseln abgehalten. Eintraege sehen aus wie "2:1" (Zweig:Stufe).
function verfaelltBeiWechsel(klasse, eintrag) {
  if (klasse === KAMPF_BAUM) return false;                   // klassenlos: gilt in jedem Beruf
  const [zi, si] = String(eintrag).split(":").map(Number);
  const zweige = SKILLS[klasse] || [];
  const zweig = zi === zweige.length ? GEFAEHRTE : zweige[zi];
  const stufe = zweig && zweig.stufen && zweig.stufen[si];
  if (!stufe) return true;                                  // unbekannt: lieber warnen
  return !Object.keys(stufe.wirkung || {}).every(a => KOERPER.includes(a));
}
const hatSkill = (id, klasse, zweig, stufe) => {
  if (gelernte(id, klasse).includes(`${zweig}:${stufe}`)) return true;
  // Der Gefaehrtenzweig steht in jedem Baum an letzter Stelle - er ist derselbe Zweig,
  // egal aus welcher Klasse man ihn oeffnet, und zaehlt darum auch nur einmal.
  const zweige = zweigeVon(id, klasse);
  if (!zweige || zweige[zweig] !== GEFAEHRTE) return false;
  return Object.keys(SKILLS).some(k => {
    const andere = zweigeVon(id, k);
    const i = andere ? andere.indexOf(GEFAEHRTE) : -1;
    return i >= 0 && gelernte(id, k).includes(`${i}:${stufe}`);
  });
};
// Punkte kommen aus zwei Quellen: Klassenstufen (Arbeit) und Kampferfahrung (allgemein).
// Sie liegen in einem gemeinsamen Topf und lassen sich in jedem Baum ausgeben.
// Gemessen (Testfall "balance", aktiv gespielt): Mit 10 kam der beste Kaempfer nach acht
// Naechten auf ZWEI freie Punkte - bei einem Baum, der 20 Faehigkeiten tief ist, also
// nichts. Mit 4 sind es sechs; über einen ganzen Lauf reicht das fuer zwei bis drei
// ausgebaute Zweige, nie fuer alle fünf. Genau das soll es sein: eine Wahl, kein Abhaken.
const KAMPF_JE_PUNKT = 4;
function verdientePunkte(id) {
  const w = wert(id);
  const ausStufen = Object.keys(SKILLS).reduce((s2, k) =>
    s2 + (k === KAMPF_BAUM ? 0 : Math.max(0, (w.stufe[k] || 1) - 1)), 0);
  return ausStufen + Math.floor((w.erfKampf || 0) / KAMPF_JE_PUNKT);
}
const ausgegebenePunkte = id => Object.keys(SKILLS).reduce((s2, k) => s2 + gelernte(id, k).length, 0);
function punkte(id, klasse) {
  return Math.max(0, verdientePunkte(id) - ausgegebenePunkte(id));
}
// ---------------------------------------------------------------------------
// Erkenntnisse: die letzte Stufe jedes Zweigs ist verschlossen
// ---------------------------------------------------------------------------
// Die staerkste Stufe eines Zweigs (Sense, Erntefest, Grosswild ...) laesst sich nicht
// einfach erarbeiten - jemand muss sie mitbringen. Gerettete Ueberlebende erzaehlen, was
// sie draussen gelernt haben; daraus wird eine Erkenntnis. Sie gehoert dem ganzen DORF,
// nicht dem, der zugehoert hat: hier gibt es keine Aemter und kein Herrschaftswissen.
const LETZTE_STUFE = 3;
const erkenntnisse = () => { const d = typeof dorf === "function" ? dorf() : null; return d ? (d.erkenntnisse = d.erkenntnisse || []) : []; };
const erkenntnisSchluessel = (klasse, zweig) => `${klasse}:${zweig}`;
const erkenntnisDa = (klasse, zweig) => erkenntnisse().includes(erkenntnisSchluessel(klasse, zweig));
// Alle noch verschlossenen Zweige - ueber alle Klassen, nicht nur die eigene
function offeneErkenntnisse() {
  const raus = [];
  for (const [klasse, zweige] of Object.entries(SKILLS))
    zweige.forEach((z, zi) => { if (!erkenntnisDa(klasse, zi)) raus.push({ klasse, zweig: zi, zweigName: z.name, stufe: z.stufen[LETZTE_STUFE] }); });
  return raus;
}
// Eine Erkenntnis vergeben. Gibt zurueck, was aufgeschlossen wurde - oder null, wenn alles offen ist.
function erkenntnisGeben() {
  const offen = offeneErkenntnisse();
  if (!offen.length) return null;
  const e = offen[Math.floor(Math.random() * offen.length)];
  erkenntnisse().push(erkenntnisSchluessel(e.klasse, e.zweig));
  return e;
}

function lernbar(id, klasse, zweig, stufe) {
  const baum = zweigeVon(id, klasse);
  if (!baum[zweig] || !baum[zweig].stufen[stufe]) return t("diesen Zweig gibt es hier nicht");
  if (hatSkill(id, klasse, zweig, stufe)) return t("schon gelernt");
  if (stufe > 0 && !hatSkill(id, klasse, zweig, stufe - 1)) return t("erst die Stufe davor");
  // Die letzte Stufe braucht eine Erkenntnis. Nur fuer die festen Klassenzweige - ein
  // Haustierzweig kommt aus SKILLS nicht vor und bleibt frei lernbar.
  if (stufe === LETZTE_STUFE && SKILLS[klasse] && SKILLS[klasse][zweig] && !erkenntnisDa(klasse, zweig))
    return t("dafür fehlt dem Dorf noch eine Erkenntnis");
  if (!punkte(id, klasse)) return t("kein Punkt frei");
  return "";
}
// Beschluss P5: Wer eine Faehigkeit aus einer fremden Klasse waehlt, bekommt einen Hinweis -
// und stimmt er zu, wechselt der Bewohner die Klasse dazu. Vorher kostete so ein Punkt
// genauso viel wie jeder andere, wirkte bei der Arbeit aber nie, und niemand sagte es.
function skillLernen(id, klasse, zweig, stufe) {
  const grund = lernbar(id, klasse, zweig, stufe);
  if (grund) return meldung(grund, 2000);
  const w = wert(id);
  const s2 = zweigeVon(id, klasse)[zweig].stufen[stufe];
  // Koerperwerte (Leben, Angriff, Wehr, Ausdauer) gelten in jeder Klasse - dafuer muss
  // niemand umlernen. Nur Arbeitsboni haengen an der Klasse.
  const nurKoerper = Object.keys(s2.wirkung || {}).every(a => KOERPER.includes(a));
  const fremd = klasse && klasse !== w.klasse && !nurKoerper;
  if (fremd && typeof frage === "function") {
    const wie = (KLASSEN[klasse] || {}).name || klasse;
    frage(t("{0} wirkt nur, wenn {1} als {2} arbeitet. Soll {1} umlernen?", s2.name, person2(id).name, wie), [
      { text: t("Ja, umlernen"), hell: true, tun: () => {
          w.klasse = klasse; w.aufgabe = null;
          if (id === "spieler" && typeof spielerKleidung === "function") spielerKleidung();
          else if (typeof npcBlaetter === "object") delete npcBlaetter[id];
          if (typeof bewohnerAuffrischen === "function") bewohnerAuffrischen();
          skillEintragen(id, klasse, zweig, stufe);
          if (typeof werteNachziehen === "function") werteNachziehen(id);   // Leben/Wehr sofort
          meldung(t("{0} ist jetzt {1}.", person2(id).name, wie), 3200);
          if (typeof skillsZeigen === "function") skillsZeigen(true, id);
        } },
      { text: t("Doch nicht") },
    ]);
    // Nicht "true": der Aufrufer ruft daraufhin werteNachziehen(), und das schenkt zwei
    // Leben. Wer sich vor eine fremde Faehigkeit stellte und die Rueckfrage wegklickte,
    // konnte sich damit beliebig oft heilen - ohne Punkt, ohne Kraut, ohne Handlung.
    return false;
  }
  return skillEintragen(id, klasse, zweig, stufe);
}
function skillEintragen(id, klasse, zweig, stufe) {
  gelernte(id, klasse).push(`${zweig}:${stufe}`);
  const s = zweigeVon(id, klasse)[zweig].stufen[stufe];
  geraeusch("fanfare");
  meldung(t("{0} kann jetzt: {1}.", person2(id).name, s.name), 3000);
  speichernBald();
  return true;
}
// Summe einer Wirkung. Arbeitsboni zaehlen nur in der gelernten Klasse, Koerperwerte immer.
function bonus(id, art, klasse = null) {
  const w = wert(id);
  // Koerperwerte gelten ueberall; Arbeitswirkungen nur in der eigenen Klasse - plus dem
  // klassenlosen Kampf-Baum, dessen Pflegestufen jeder nutzen kann, der pflegt.
  const klassen = KOERPER.includes(art) ? Object.keys(SKILLS)
                                        : [...new Set([klasse || w.klasse, KAMPF_BAUM])];
  let summe = 0;
  const schonGezaehlt = new Set();                           // fuer den Gefaehrtenzweig
  for (const k of klassen)
    for (const eintrag of gelernte(id, k)) {
      const [zweig, stufe] = eintrag.split(":");
      const zweige = zweigeVon(id, k);
      const s = zweige && zweige[zweig] && zweige[zweig].stufen[stufe];
      if (!s || !s.wirkung[art]) continue;
      if (zweige[zweig] === GEFAEHRTE) {
        if (schonGezaehlt.has(stufe)) continue;              // in jedem Baum derselbe Zweig
        schonGezaehlt.add(stufe);
      }
      summe += s.wirkung[art];
    }
  return summe;
}
const koennen = id => {
  const w = wert(id), liste = [];
  for (const k of Object.keys(SKILLS))
    for (const eintrag of gelernte(id, k)) {
      const [zweig, stufe] = eintrag.split(":");
      const zweige = zweigeVon(id, k);
      const s = zweige[zweig] && zweige[zweig].stufen[stufe];
      if (s && s.wirkung.koennen) liste.push(s.wirkung.koennen);
    }
  return liste;
};
const dorfBonus = art => lebende().reduce((s, id) => s + bonus(id, art), 0);

// ---------------------------------------------------------------------------
// Die Seite: drei Zweige nebeneinander, vier Stufen untereinander
// ---------------------------------------------------------------------------
let skillOffen = false, skillWer = "spieler", skillKlasse = null, skillZweig = 0, skillStufe = 0;
function skillsZeigen(an, wer = skillWer) {
  if (menueWechsel(an ? "skills" : null) === false) return;
  skillOffen = an;
  document.getElementById("skills").hidden = !an;
  if (!an) return;
  if (skillWer !== wer) { skillZweig = 0; skillStufe = 0; skillKlasse = null; }
  skillWer = wer;
  const w = wert(wer);
  if (!skillKlasse || !SKILLS[skillKlasse]) skillKlasse = w.klasse;
  // Der Kampf-Baum hat keine Klassenstufe - dort steht, wie viele Punkte erkaempft sind.
  const baumRang = k => (k === KAMPF_BAUM
    ? `${KLASSEN[k].name} ${Math.floor((w.erfKampf || 0) / KAMPF_JE_PUNKT)}`
    : `${KLASSEN[k].name} ${w.stufe[k] || 1}`);
  document.getElementById("skillkopf").textContent = `${person2(wer).name} · ${baumRang(skillKlasse)}`;
  const stufen = Object.keys(SKILLS).map(baumRang);
  document.getElementById("skillklassen").textContent = stufen.join(" · ");
  const feld = document.getElementById("skillfeld");
  feld.innerHTML = "";
  // So viele Spalten wie Zweige: mit einem Haustier sind es vier statt drei. Das Raster stand
  // fest auf drei, und der vierte Zweig fiel in eine zweite Reihe, halb abgeschnitten.
  feld.style.setProperty("--zweige", zweigeVon(wer, skillKlasse).length);
  skillZweig = Math.min(skillZweig, zweigeVon(wer, skillKlasse).length - 1);
  // --- Bewohnerleiste: alle auf einmal, mit freien Punkten und Plan ---------
  const leiste = document.getElementById("skillleute");
  if (leiste) {
    leiste.innerHTML = "";
    for (const id of lebende()) {
      const w2 = wert(id);
      const frei = punkte(id, w2.klasse);
      const e = document.createElement("div");
      e.className = "sperson" + (id === wer ? " an" : "");
      e.innerHTML = `<b>${person2(id).name}</b>`
        + (frei ? `<span class="frei">+${frei}</span>` : "")
        + (w2.plan ? `<span class="plant">▸</span>` : "");
      e.title = KLASSEN[w2.klasse] ? KLASSEN[w2.klasse].name : "";
      e.addEventListener("click", () => skillsZeigen(true, id));
      leiste.append(e);
    }
  }
  // --- Das Netz: Zweige als Straenge, Stufen als Knoten ---------------------
  const plan = w.plan;
  zweigeVon(wer, skillKlasse).forEach((zweig, zi) => {
    const spalte = document.createElement("div");
    spalte.className = "szweig";
    const kopf = document.createElement("h3");
    kopf.textContent = zweig.name;
    spalte.append(kopf);
    zweig.stufen.forEach((s, si) => {
      const hat = hatSkill(wer, skillKlasse, zi, si), offen = !lernbar(wer, skillKlasse, zi, si);
      // Verschlossen: die letzte Stufe, zu der dem Dorf noch die Erkenntnis fehlt
      const zu = si === LETZTE_STUFE && !hat && SKILLS[skillKlasse] && SKILLS[skillKlasse][zi]
                 && !erkenntnisDa(skillKlasse, zi);
      if (si > 0) {                                        // Linie zum Knoten darueber
        const l = document.createElement("div");
        l.className = "slinie" + (hat || offen ? "" : " zu");
        spalte.append(l);
      }
      const z = document.createElement("div");
      z.className = "sknoten" + (hat ? " hat" : offen ? " offen" : "") + (zu ? " zu" : "")
                    + (zi === skillZweig && si === skillStufe ? " an" : "")
                    + (plan && plan.klasse === skillKlasse && plan.zweig === zi && plan.stufe === si ? " ziel" : "");
      const c = document.createElement("canvas");
      c.width = c.height = 18;
      symbolMalen(c.getContext("2d"), zu ? "schloss" : wirkungArt(s), hat ? "#3b2a1a" : "#5a4a38");
      const rang = document.createElement("span");
      rang.className = "srang";
      rang.textContent = String(si + 1);
      z.append(c, rang);
      const klick = () => {
        if (skillZweig === zi && skillStufe === si) { if (skillLernen(wer, skillKlasse, zi, si)) werteNachziehen(wer); }
        skillZweig = zi; skillStufe = si; skillsZeigen(true, wer);
      };
      z.addEventListener("click", klick);
      spalte.append(z);
      // Der Name unter dem Knoten: im Bild steht sonst nur ein Symbol, und bei 80
      // Faehigkeiten weiss niemand auswendig, welches wofuer steht.
      const nm = document.createElement("p");
      nm.className = "sname" + (hat ? " hat" : "");
      // Der Name steht AUCH bei verschlossenen Knoten da - das Schloss im Symbol sagt
      // schon, dass es zu ist. Dreimal "verschlossen" untereinander sagt dagegen nichts.
      nm.textContent = s.name;
      nm.addEventListener("click", klick);
      spalte.append(nm);
    });
    feld.append(spalte);
  });
  // --- Was der gewaehlte Knoten kann ---------------------------------------
  const info = document.getElementById("skillinfo");
  if (info) {
    const zw = zweigeVon(wer, skillKlasse)[skillZweig];
    const st = zw && zw.stufen[skillStufe];
    const hatG = hatSkill(wer, skillKlasse, skillZweig, skillStufe);
    const zuG = skillStufe === LETZTE_STUFE && !hatG && SKILLS[skillKlasse] && SKILLS[skillKlasse][skillZweig]
                && !erkenntnisDa(skillKlasse, skillZweig);
    info.innerHTML = st
      ? `<b>${st.name}</b> — ` + (zuG ? t("verschlossen – jemand muss davon erzählen") : st.text)
        + (hatG ? ` · ${t("gelernt")}` : "")
      : "";
  }
  const gesamtZweige = Object.values(SKILLS).reduce((n, z) => n + z.length, 0);
  document.getElementById("skillfuss").innerHTML =
    `${punkte(wer, skillKlasse) === 1 ? t("1 Punkt frei (Arbeit und Kampf)")
                                       : t("{0} Punkte frei (Arbeit und Kampf)", punkte(wer, skillKlasse))}`
    + ` · ${t("{0} von {1} Erkenntnissen", erkenntnisse().length, gesamtZweige)}`
    + ` · <kbd>↑</kbd><kbd>↓</kbd><kbd>←</kbd><kbd>»</kbd> ${t("wählen")}`
    + ` · <kbd>Enter</kbd> ${t("lernen")} · <kbd>−</kbd> ${t("zurücknehmen")}`
    + ` · <kbd>P</kbd> ${t("vormerken")} · <kbd>Q</kbd><kbd>E</kbd> ${t("Bewohner")}`
    + ` · <kbd>Tab</kbd> ${t("Klasse")} · <kbd>L</kbd> ${t("schließen")}`;
}
// Gesundheit kommt aus einer einzigen Rechnung: Grundwert, alle Klassenstufen, Faehigkeiten
// Wie viel Gesundheit jemand hoechstens hat: Grundwert plus die Stufen SEINER Klasse.
//
// Frueher summierte das ueber alle Klassen. Weil ein Klassenwechsel die alten Stufen behaelt
// (so ist es gewollt), bekam man die Lebenspunkte dafuer ein zweites Mal: zwei Klassen auf
// Stufe 6 ergaben 20 statt 15. Wechseln war damit ein versteckter Gesundheitsbonus - und
// gemerkt hat man es nur, wenn man nachgerechnet hat.
//
// Die alten Stufen sind nicht verloren: wer zurueckwechselt, hat sie wieder.
function maxGesundVon(id) {
  const w = wert(id);
  const stufen = Math.max(0, (w.stufe[w.klasse] || 1) - 1);
  return 10 + stufen + bonus(id, "gesund");
}
// ---------------------------------------------------------------------------
// Symbole fuer das Knotennetz
// ---------------------------------------------------------------------------
// Indie wollte einen Baum wie in Anno 117: Knoten mit Symbol, durch Linien verbunden.
// 80 Faehigkeiten haetten 80 Zeichnungen gebraucht - sie tragen aber nur 17 verschiedene
// WIRKUNGEN (gezaehlt am 15.10.). Also ein Symbol je Wirkung, nicht je Faehigkeit.
//
// Vier davon gibt es laengst als Dingbild (Holz, Stein, Kraeuter, Essen) - die werden
// benutzt, nicht nachgezeichnet. Die uebrigen dreizehn stehen hier als 9x9-Muster im Code:
// so braucht es keine neue Bilddatei, und Indies Asset-Ordner bleiben unberuehrt.
//   .  nichts   #  Hauptfarbe   o  dunkler Rand
const WIRKUNG_DING = { essen: "eichel", holz: "ast", stein: "kiesel", kraeuter: "kraeuter" };
const WIRKUNG_SYMBOL = {
  angriff:      ["....#....", "...###...", "...###...", "...###...", "...###...", "..#####..", "....#....", "...ooo...", "........."],
  verteidigung: ["#######..", "#.....#..", "#.....#..", "#.....#..", ".#...#...", "..#.#....", "...#.....", ".........", "........."],
  gesund:       [".##.##...", "#######..", "#######..", ".#####...", "..###....", "...#.....", ".........", ".........", "........."],
  heilen:       ["...#.....", "...#.....", ".#####...", "...#.....", "...#.....", "...#.....", ".........", ".........", "........."],
  ausdauer:     ["..##.....", "..##.....", "..##.....", "..###....", "..####...", ".#####...", "ooooo....", ".........", "........."],
  bauen:        [".#####...", ".#####...", "...#.....", "...#.....", "...#.....", "...#.....", "...#.....", ".........", "........."],
  schmieden:    ["..#####..", ".#######.", "..#####..", "...###...", "...###...", "..#####..", ".........", ".........", "........."],
  mauer:        ["###.###..", "###.###..", ".........", "#.###.#..", "#.###.#..", ".........", "###.###..", "###.###..", "........."],
  handlungen:   ["...#.....", ".#.#.#...", "..###....", "#.###.#..", "..###....", ".#.#.#...", "...#.....", ".........", "........."],
  nachtheilung: ["..###....", ".##......", ".#.......", ".#.......", ".##......", "..###....", ".........", ".........", "........."],
  haustieressen:[".........", "#######..", ".#####...", "..###....", "...#.....", ".........", ".........", ".........", "........."],
  zaehmen:      [".#.#.#...", ".#.#.#...", "..###....", ".#####...", ".#####...", "..###....", ".........", ".........", "........."],
  schloss:      ["..###....", ".#...#...", ".#...#...", "#######..", "#######..", "#..#..#..", "#######..", ".........", "........."],
  koennen:      ["....#....", "...###...", ".#######.", "...###...", "..#.#.#..", ".#.....#.", ".........", ".........", "........."],
};
// Welches Symbol gehoert zu einer Stufe? Die staerkste Wirkung zuerst - eine Faehigkeit mit
// "essen: 2, handlungen: 1" ist vor allem eine Essensfaehigkeit.
function wirkungArt(s2) {
  const w = (s2 && s2.wirkung) || {};
  let beste = null, hoechst = -1;
  for (const [k, v] of Object.entries(w)) if (v > hoechst) { hoechst = v; beste = k; }
  return beste;
}
// Malt das Symbol in ein kleines Canvas. Ohne Wirkung (reine Textfaehigkeit) bleibt es leer.
function symbolMalen(c, art, farbe) {
  if (!art) return;
  if (WIRKUNG_DING[art] && typeof dingBild === "function") {
    try { c.drawImage(dingBild(WIRKUNG_DING[art]), 0, 0, DING_TK, DING_TK, 0, 0, 18, 18); return; }
    catch (e) { /* Blatt noch nicht da: weiter mit dem gezeichneten Muster */ }
  }
  const muster = WIRKUNG_SYMBOL[art];
  if (!muster) return;
  const P = 2;
  for (let y = 0; y < muster.length; y++)
    for (let x = 0; x < muster[y].length; x++) {
      const z = muster[y][x];
      if (z === ".") continue;
      c.fillStyle = z === "o" ? "rgba(0,0,0,.35)" : farbe;
      c.fillRect(x * P, y * P, P, P);
    }
}

// ---------------------------------------------------------------------------
// Verwalten: zuruecknehmen und vorplanen (Indie, 15.10.)
// ---------------------------------------------------------------------------
// Sechs Baeume mit zusammen rund 100 Faehigkeiten, und das je Bewohner - bei zehn Leuten
// ist das ohne Uebersicht nicht zu ueberblicken. Dazu drei Stuecke: zuruecknehmen,
// vorplanen, und die Bewohnerleiste in skillsZeigen().

// Nur die OBERSTE gelernte Stufe eines Zweigs laesst sich zurueckgeben. Sonst stuende eine
// hoehere Stufe ohne ihre Vorstufe da - genau das verhindert lernbar() beim Lernen.
function verlernbar(id, klasse, zweig, stufe) {
  if (!hatSkill(id, klasse, zweig, stufe)) return t("das ist nicht gelernt");
  if (hatSkill(id, klasse, zweig, stufe + 1)) return t("erst die Stufe darüber zurücknehmen");
  return "";
}
function verlernen(id, klasse, zweig, stufe) {
  const grund = verlernbar(id, klasse, zweig, stufe);
  if (grund) { meldung(grund, 2200); return false; }
  const liste = gelernte(id, klasse);
  const i = liste.indexOf(`${zweig}:${stufe}`);
  if (i < 0) return false;
  liste.splice(i, 1);
  // NICHT werteNachziehen() rufen! Das schenkt zwei Leben (siehe dort) - ueber Lernen und
  // Verlernen im Wechsel waere das eine Heilquelle ohne Punkt, ohne Kraut, ohne Handlung.
  // Beim Zuruecknehmen kann maxGesund SINKEN, also neu rechnen und nach unten begrenzen.
  const w = wert(id);
  w.maxGesund = maxGesundVon(id);
  w.gesund = Math.min(w.gesund, w.maxGesund);
  const s2 = (zweigeVon(id, klasse)[zweig] || {}).stufen || [];
  geraeusch("kiesel");
  meldung(t("{0} verlernt: {1}. Der Punkt ist wieder frei.",
            person2(id).name, (s2[stufe] || {}).name || "?"), 2800);
  speichernBald();
  return true;
}

// Vorplanen: ein Ziel im Baum merken. Sobald ein Punkt frei wird, lernt der Bewohner selbst
// die naechste Stufe auf dem Weg dorthin. Gespeichert wird je Bewohner EIN Ziel - mehr
// waere eine Warteschlange, die niemand mehr ueberblickt.
function planSetzen(id, klasse, zweig, stufe) {
  const w = wert(id);
  if (w.plan && w.plan.klasse === klasse && w.plan.zweig === zweig && w.plan.stufe === stufe) {
    delete w.plan;                                         // nochmal draufgedrueckt: Plan weg
    meldung(t("{0} hat keinen Plan mehr.", person2(id).name), 2200);
  } else {
    w.plan = { klasse, zweig, stufe };
    const s2 = ((zweigeVon(id, klasse)[zweig] || {}).stufen || [])[stufe];
    meldung(t("{0} arbeitet jetzt auf {1} hin.", person2(id).name, (s2 || {}).name || "?"), 2800);
  }
  speichernBald();
  return true;
}
// Einen Schritt auf dem Plan gehen, wenn ein Punkt frei ist. Gibt den gelernten Namen
// zurueck oder null. Wird jeden Morgen fuer jeden Bewohner gerufen.
function planSchritt(id) {
  const w = wert(id);
  const pl = w && w.plan;
  if (!pl) return null;
  if (!punkte(id, pl.klasse)) return null;
  // Die unterste noch fehlende Stufe auf dem Weg zum Ziel
  for (let st = 0; st <= pl.stufe; st++) {
    if (hatSkill(id, pl.klasse, pl.zweig, st)) continue;
    if (lernbar(id, pl.klasse, pl.zweig, st)) return null;  // gesperrt: nicht draengeln
    // Eine FREMDE Klasse oeffnet in skillLernen() eine Rueckfrage - das waere mitten im
    // Morgen ein Dialog, den niemand erwartet. Der Plan bleibt bei der eigenen Klasse.
    if (pl.klasse !== w.klasse) return null;
    skillEintragen(id, pl.klasse, pl.zweig, st);
    w.maxGesund = maxGesundVon(id);
    const s2 = ((zweigeVon(id, pl.klasse)[pl.zweig] || {}).stufen || [])[st];
    if (st >= pl.stufe) delete w.plan;                      // Ziel erreicht
    return (s2 || {}).name || "?";
  }
  delete w.plan;
  return null;
}

function werteNachziehen(id) {
  const w = wert(id);
  w.maxGesund = maxGesundVon(id);
  w.gesund = Math.min(w.maxGesund, w.gesund + 2);
  skillsZeigen(true, id);
}
function skillTaste(k) {
  const zweige = zweigeVon(skillWer, skillKlasse).length;
  if (k === "l" || k === "Escape") { skillsZeigen(false); return true; }
  if (k === "Tab") {
    const liste = Object.keys(SKILLS);
    skillKlasse = liste[(liste.indexOf(skillKlasse) + 1) % liste.length];
  } else if (k === "q" || k === "e") {
    // Bewohner wechseln, ohne den Bildschirm zu verlassen. Vorher musste man schliessen,
    // hinlaufen und neu oeffnen - bei zehn Leuten und sechs Baeumen war das die eigentliche
    // Arbeit. Die Auswahl im Baum bleibt stehen, damit sich zwei Leute vergleichen lassen.
    const leute = lebende();
    const i = Math.max(0, leute.indexOf(skillWer));
    const n2 = leute[(i + (k === "e" ? 1 : leute.length - 1)) % leute.length];
    const merkZweig = skillZweig, merkStufe = skillStufe, merkKlasse = skillKlasse;
    skillsZeigen(true, n2);
    if (SKILLS[merkKlasse]) skillKlasse = merkKlasse;
    skillZweig = Math.min(merkZweig, zweigeVon(skillWer, skillKlasse).length - 1);
    skillStufe = merkStufe;
  } else if (k === "-" || k === "Backspace") {
    verlernen(skillWer, skillKlasse, skillZweig, skillStufe);
  } else if (k === "p") {
    planSetzen(skillWer, skillKlasse, skillZweig, skillStufe);
  } else if (k === "ArrowLeft" || k === "a") skillZweig = (skillZweig - 1 + zweige) % zweige;
  else if (k === "ArrowRight" || k === "d") skillZweig = (skillZweig + 1) % zweige;
  else if (k === "ArrowUp" || k === "w") skillStufe = (skillStufe + 3) % 4;
  else if (k === "ArrowDown" || k === "s") skillStufe = (skillStufe + 1) % 4;
  else if (k === "Enter" || k === " ") { if (skillLernen(skillWer, skillKlasse, skillZweig, skillStufe)) return werteNachziehen(skillWer) || true; }
  skillsZeigen(true, skillWer);
  return true;
}
