Falkencode · 09.09.2026
Falkencode: engine.py — Die Spiellogik
engine.py ist das Herzstück — rund 60 Befehle und die komplette Spiellogik: Befehlsverteiler mit deutschen und englischen Aliasen, die geteilte Welt mit Respawn nach 5 Minuten, Rundenkampf, Rassen-Boni (Ork +2 Schaden, Elf-Zauberermäßigung …), Gilden, 10 Quests, Zauber mit Mana, Händler und Kneipen (Orks zahlen 10 % Aufschlag — das Dorf traut ihnen nicht), Bank mit todesfestem Guthaben, Beutel-Kapazität nach Stufe, Truhe mit 100 Fächern, Tag/Nacht-Zyklus und die Göttlichen Befehle für den Admin.
Zwei Stellen lohnen den Blick: faerbe() versieht jede Ausgabezeile je nach Typ mit klassischen ANSI-Farbcodes (Raumtitel gold, Schaden rot, Quests grün) — der Browser setzt sie in CSS-Farben um. Und _raum_ansehen() baut die Raumansicht zusammen und übersetzt sie je nach Spielersprache.
#!/usr/bin/env python3
"""Falkenmoor-MUD: Spiel-Engine (Multiuser, Persistenz, Händler, Türen)."""
import json, random, os, re, threading, time
import db
import uebersetz
BASE = os.path.dirname(os.path.abspath(__file__))
WORLD_PATH = os.path.join(BASE, "world.json")
RESPAWN_SEKUNDEN = 300 # besiegte Monster kehren nach 5 Minuten zurück
_richtung_aliase = {
"n": "norden", "no": "nordosten", "nw": "nordwesten",
"s": "sueden", "so": "suedosten", "sw": "suedwesten",
"o": "osten", "w": "westen", "hoch": "oben", "tief": "runter",
"north": "norden", "south": "sueden", "east": "osten", "west": "westen",
"northeast": "nordosten", "northwest": "nordwesten",
"southeast": "suedosten", "southwest": "suedwesten",
"up": "oben", "down": "runter", "e": "osten",
}
_GODPORT_BEFEHLE = {"godport", "goport", "gottport", "teleport"}
_ADMIN_NAMEN = {"Hugo"}
# Emotes: befehl -> (selbst-Text, raum-Text, braucht Ziel?)
_EMOTES = {
"lach": ("Du lachst herzlich.", "{name} lacht herzlich.", False),
"lache": ("Du lachst herzlich.", "{name} lacht herzlich.", False),
"weine": ("Eine Träne läuft dir über die Wange.", "{name} weint bitterlich.", False),
"wein": ("Eine Träne läuft dir über die Wange.", "{name} weint bitterlich.", False),
"tanz": ("Du drehst eine Runde.", "{name} tanzt ein einsames Reigentänzchen.", False),
"tanze": ("Du drehst eine Runde.", "{name} tanzt ein einsames Reigentänzchen.", False),
"seufz": ("Du seufzt tief.", "{name} seufzt tief aus der Seele.", False),
"seufze": ("Du seufzt tief.", "{name} seufzt tief aus der Seele.", False),
"jauchz": ("Du jauchzt vor Freude!", "{name} jauchzt laut vor Freude!", False),
"jauchze": ("Du jauchzt vor Freude!", "{name} jauchzt laut vor Freude!", False),
"zitter": ("Du zitterst am ganzen Körper.", "{name} zittert wie Espenlaub.", False),
"zittre": ("Du zitterst am ganzen Körper.", "{name} zittert wie Espenlaub.", False),
"schnarch": ("Du... schnarchst? Wach auf!", "{name} schnarcht laut.", False),
"schnarche": ("Du... schnarchst? Wach auf!", "{name} schnarcht laut.", False),
"verbeug": ("Du verbeugst dich tief.", "{name} verbeugt sich tief{ziel_zu}.", True),
"verbeuge": ("Du verbeugst dich tief{ziel_vor}.", "{name} verbeugt sich tief{ziel_zu}.", True),
"wink": ("Du winkst freundlich{ziel_zu}.", "{name} winkt freundlich{ziel_zu}.", True),
"winke": ("Du winkst freundlich{ziel_zu}.", "{name} winkt freundlich{ziel_zu}.", True),
"zwinker": ("Du zwinkerst{ziel_zu}.", "{name} zwinkert vielversprechend{ziel_zu}.", True),
"zwinkere": ("Du zwinkerst{ziel_zu}.", "{name} zwinkert vielversprechend{ziel_zu}.", True),
"gruess": ("Du grüßt{ziel_zu}.", "{name} grüßt{ziel_zu}.", True),
"gruesse": ("Du grüßt{ziel_zu}.", "{name} grüßt{ziel_zu}.", True),
"knuddel": ("Du knuddelst{ziel_zu}.", "{name} knuddelt{ziel_zu}.", True),
"knudle": ("Du knuddelst{ziel_zu}.", "{name} knuddelt{ziel_zu}.", True),
"klatsch": ("Du klatschst Beifall{ziel_zu}.", "{name} klatscht Beifall{ziel_zu}.", True),
"klatsche": ("Du klatschst Beifall{ziel_zu}.", "{name} klatscht Beifall{ziel_zu}.", True),
}
# Tag/Nacht: 1 Spieltag = 2 echte Stunden (5 Minuten pro Spielstunde)
_SPIELSTUNDE_SEKUNDEN = 300
_TAGESZEITEN = {
"nacht": "Die Nacht liegt dunkel über dem Land.",
"morgengrauen": "Am Horizont graut der Morgen.",
"tag": "Die Sonne steht hoch am Himmel.",
"abenddaemmerung": "Der Himmel glüht in Abendrot.",
}
# Wappen-Grafik für den Startraum (Marktplatz)
_START_ART = """\
^v^ ^v^
|_|_|_| |_|_|_|
|_____| |_____|
_|_____|___________|_____|_
| ,---. |
| | | |
|____________| |_________|
FALKENMOOR"""
# Beutel-Kapazität: Basiswert + Zuwachs je Stufe
KAPAZITAET_BASIS = 20
KAPAZITAET_PRO_STUFE = 2
TRUHE_FAECHER = 100
HAUS_RAUM = "haus"
# ANSI-Farbcodes: Der Server markiert Zeilentypen, der Browser-Client
# setzt sie in CSS-Farben um (klassische MUD-Technik).
_ANSI_RESET = "\x1b[0m"
_FAERBUNG = [
("【", "\x1b[1;33m"), # Raumtitel: gold + fett
("⚔", "\x1b[33m"), # eigener Treffer: gelb
("💢", "\x1b[31m"), # erlittener Schaden: rot
("☠", "\x1b[1;35m"), # Sieg: magenta + fett
("💀", "\x1b[1;31m"), # Todesfolge: rot + fett
("✨", "\x1b[36m"), # Zauber: cyan
("★", "\x1b[1;32m"), # Quest angenommen: grün + fett
("✔", "\x1b[1;32m"), # Quest abgeschlossen: grün + fett
("Belohnung:", "\x1b[32m"),
("Beute:", "\x1b[33m"),
("💬", "\x1b[36m"), # Chat: cyan
("📢", "\x1b[36m"),
("🛡", "\x1b[36m"),
("📬", "\x1b[34m"), # Post: blau
("📨", "\x1b[34m"),
("✉", "\x1b[34m"),
("Du kaufst", "\x1b[33m"),
("Du verkaufst", "\x1b[33m"),
("Du trinkst", "\x1b[33m"),
("Du isst", "\x1b[33m"),
("Du nimmst alles", "\x1b[1;33m"),
("You take everything", "\x1b[1;33m"),
("Du nimmst", "\x1b[33m"),
("You take", "\x1b[33m"),
("You buy", "\x1b[33m"),
("You sell", "\x1b[33m"),
("You drink", "\x1b[33m"),
("You eat", "\x1b[33m"),
("You put down", "\x1b[33m"),
("Du sagst", "\x1b[36m"),
("Du rufst", "\x1b[36m"),
("Reward:", "\x1b[32m"),
("Loot:", "\x1b[33m"),
("Exits:", "\x1b[2m"),
("Assessment:", "\x1b[35m"),
("Condition:", "\x1b[2m"),
("Du sagst", "\x1b[36m"),
("Du rufst", "\x1b[36m"),
("Ausgänge:", "\x1b[2m"), # dezent
("Einschätzung:", "\x1b[35m"),
("═══", "\x1b[1;33m"), # Hilfe-Titel
]
def faerbe(text):
"""Färbt Zeilen nach ihrem führenden Kennzeichen. Bereits gefärbte
Zeilen (beginnen mit Escape) bleiben unverändert."""
zeilen = []
for z in text.split("\n"):
if z.startswith("\x1b"):
zeilen.append(z)
continue
if re.match(r"^\([^)]*(?:Uhr|o'clock)\)", z):
zeilen.append("\x1b[2m" + z + _ANSI_RESET)
continue
for praefix, code in _FAERBUNG:
if z.startswith(praefix):
zeilen.append(code + z + _ANSI_RESET)
break
else:
zeilen.append(z)
return "\n".join(zeilen)
# Zauber: name -> (ab Stufe, Mana-Kosten, Wirkung-Kürzel, Beschreibung)
_ZAUBER = {
"heilung": (1, 6, "heil25", "Heilt 25 Lebenspunkte"),
"funke": (1, 3, "dmg8", "Ein magischer Funke: 8+Schaden"),
"feuerball": (3, 10, "dmg18", "Ein Feuerball: 18+Schaden"),
"blitz": (5, 14, "dmg28", "Ein Blitz: 28+Schaden"),
"heimwind": (4, 12, "port", "Trägt dich zum Marktplatz"),
}
_GILDEN = {
"krieger": "Krieger (+3 Schaden, +10 Max-HP)",
"magier": "Magier (+10 Max-Mana, Zauber −1 Mana)",
"dieb": "Dieb (findet doppelt so viel Gold bei Siegen)",
}
def _norm(s):
return (s.lower().replace("ü", "ue").replace("ö", "oe").replace("ä", "ae")
.replace("ß", "ss"))
def _raum_name(name):
"""Anzeigename ohne angehängte Raumnummer wie ' (7)'."""
return re.sub(r"\s*\(\d+\)\s*$", "", str(name)).strip()
_RASSEN_ALIASE = {"zwerg": "zwerg", "dwarf": "zwerg", "elf": "elf",
"ork": "ork", "orc": "ork", "mensch": "mensch", "human": "mensch"}
class Welt:
"""Geteilte Welt für alle verbundene Spieler."""
def __init__(self, pfad=WORLD_PATH):
with open(pfad, encoding="utf-8") as f:
data = json.load(f)
self.titel = data["titel"]
self.raeume = data["raeume"]
self.items = data["items"]
self.npcs = {n["id"]: dict(n, tot_seit=None) for n in data["npcs"].values()}
self.start_raum = data["start_raum"]
self.spieler_defaults = data["spieler"]
self.lock = threading.RLock()
def npc_in_raum(self, raum_id):
"""Lebende (respawnerte) NPCs im Raum."""
jetzt = time.time()
with self.lock:
out = []
for n in self.npcs.values():
if n["raum"] != raum_id:
continue
if n["hp"] <= 0:
if n["tot_seit"] and jetzt - n["tot_seit"] >= RESPAWN_SEKUNDEN:
n["hp"] = n["max_hp"]
n["tot_seit"] = None
else:
continue
out.append(n)
return out
ONLINE = {} # name -> dict(spieler=Spieler, engine=Engine)
_online_lock = threading.Lock()
class Spieler:
def __init__(self, name, welt):
self.name = name
self.hp = welt.spieler_defaults["hp"]
self.max_hp = welt.spieler_defaults["max_hp"]
self.basis_schaden = welt.spieler_defaults["schaden"]
self.basis_verteidigung = welt.spieler_defaults["verteidigung"]
self.gold = welt.spieler_defaults.get("start_gold", 10)
self.bank = 0
self.sprache = "de"
self.truhe = []
self.rasse = None
self.raum = welt.start_raum
self.inventar = []
self.waffe = None
self.ruestung = None
self.xp = 0
self.level = 1
self.mana = 20
self.max_mana = 20
self.gilde = None
self.quests_aktiv = {}
self.quests_fertig = []
self.kampf_ziel = None
def kapazitaet(self):
return KAPAZITAET_BASIS + (self.level - 1) * KAPAZITAET_PRO_STUFE
@property
def schaden(self):
b = self.basis_schaden
if self.waffe and self.waffe in self.inventar:
b += SPIEL_WELT.items[self.waffe].get("schaden", 0)
if self.gilde == "krieger":
b += 3
if self.rasse == "ork":
b += 2
return max(1, b + self.level - 1)
@property
def verteidigung(self):
b = self.basis_verteidigung
if self.ruestung and self.ruestung in self.inventar:
b += SPIEL_WELT.items[self.ruestung].get("verteidigung", 0)
return b
def serialisieren(self):
return {"hp": self.hp, "max_hp": self.max_hp, "gold": self.gold,
"bank": self.bank, "sprache": self.sprache, "truhe": self.truhe,
"rasse": self.rasse,
"raum": self.raum, "inventar": self.inventar, "waffe": self.waffe,
"ruestung": self.ruestung, "xp": self.xp, "level": self.level,
"mana": self.mana, "max_mana": self.max_mana, "gilde": self.gilde,
"quests_aktiv": self.quests_aktiv, "quests_fertig": self.quests_fertig}
def laden(self, daten):
for k in ("hp", "max_hp", "gold", "bank", "sprache", "truhe", "rasse", "raum", "inventar", "waffe", "ruestung",
"xp", "level", "mana", "max_mana", "gilde",
"quests_aktiv", "quests_fertig"):
if k in daten:
setattr(self, k, daten[k])
if self.raum not in SPIEL_WELT.raeume:
self.raum = SPIEL_WELT.start_raum
SPIEL_WELT = Welt()
class Engine:
def __init__(self, spieler, sende_raum=None, sende_alle=None, beim_save=None):
self.p = spieler
self.w = SPIEL_WELT
self.sende_raum = sende_raum or (lambda raum, text, ausser: None)
self.sende_alle = sende_alle or (lambda text, ausser: None)
self.save = beim_save or (lambda: None)
# ---------- Tag/Nacht ----------
@staticmethod
def tageszeit():
"""Liefert (spielstunde 0-23, phasenname, atmosphaere-text)."""
stunde = int(time.time() // _SPIELSTUNDE_SEKUNDEN) % 24
if 6 <= stunde < 8:
phase = "morgengrauen"
elif 8 <= stunde < 19:
phase = "tag"
elif 19 <= stunde < 21:
phase = "abenddaemmerung"
else:
phase = "nacht"
return stunde, phase, _TAGESZEITEN[phase]
def _nacht_ist(self):
return self.tageszeit()[1] == "nacht"
# ---------- Ausgabe ----------
def _raum_ansehen(self):
raum = self.w.raeume[self.p.raum]
stunde, phase, atmosphaere = self.tageszeit()
zeilen = [f"\n【 {_raum_name(raum['name'])} 】"]
if self.p.raum == self.w.start_raum:
zeilen.append(_START_ART)
zeilen += [
f"({phase.capitalize()}, {stunde} Uhr) — {atmosphaere}",
raum["beschreibung"]]
npcs = self.w.npc_in_raum(self.p.raum)
if npcs:
zeilen.append("Hier sind: " + ", ".join(
f"{n['name']}{' ⚔' if n['aggressiv'] else ''}" for n in npcs))
with _online_lock:
spieler_hier = [n for n, e in ONLINE.items()
if e["spieler"].raum == self.p.raum and n != self.p.name]
if spieler_hier:
zeilen.append("Andere Helden: " + ", ".join(spieler_hier))
if raum["items"]:
zeilen.append("Am Boden liegen: " + ", ".join(
self.w.items[i]["name"] for i in raum["items"]))
if raum["exits"]:
zeilen.append("Ausgänge: " + ", ".join(self._exit_anzeigen(k, v) for k, v in raum["exits"].items()))
return faerbe(uebersetz.text("\n".join(zeilen), self.p.sprache))
def _exit_anzeigen(self, richtung, ziel):
if isinstance(ziel, dict) and ziel.get("verschlossen"):
return f"{richtung} (verschlossen)"
return richtung
def _npc_finden(self, suchwort):
suchwort = _norm(suchwort)
for n in self.w.npc_in_raum(self.p.raum):
if suchwort in _norm(n["name"]) or suchwort == n["id"]:
return n
return None
def _item_finden_boden(self, suchwort):
suchwort = _norm(suchwort)
for i in self.w.raeume[self.p.raum]["items"]:
if suchwort in _norm(self.w.items[i]["name"]):
return i
return None
def _item_finden_inv(self, suchwort):
suchwort = _norm(suchwort)
for i in self.p.inventar:
if suchwort in _norm(self.w.items[i]["name"]):
return i
return None
def _rasse_xp(self, xp):
return int(xp * 1.1) if self.p.rasse == "mensch" else xp
def _xp_vergeben(self, xp):
self.p.xp += self._rasse_xp(xp)
while self.p.xp >= self.p.level * 100:
self.p.xp -= self.p.level * 100
self.p.level += 1
self.p.max_hp += 10
self.p.max_mana += 5
self.p.hp = self.p.max_hp
self.p.mana = self.p.max_mana
return (f"✦ Du bist jetzt Stufe {self.p.level}! "
f"(Max-HP: {self.p.max_hp}, Max-Mana: {self.p.max_mana})")
return None
def _sende(self, text, ausser=None):
self.sende_raum(self.p.raum, text, ausser)
# ---------- Befehlsverteiler ----------
KEIN_ARG = {"umschauen", "l", "schaue", "inv", "inventar", "i", "quests", "quest",
"aufgaben", "status", "stat", "wer", "spieler", "hilfe", "?", "befehle",
"fliehe", "flucht", "godport", "goport", "gottport", "teleport"}
def verarbeite(self, eingabe):
ergebnis = self._verarbeite_roh(eingabe)
if not ergebnis:
return ergebnis
if self.p.sprache == "en":
ergebnis = uebersetz.text(ergebnis, "en")
return faerbe(ergebnis)
def cmd_sprache(self, arg):
wahl = _norm(arg).strip()
if wahl in ("en", "englisch", "english"):
self.p.sprache = "en"
return ("🗣 Language: ENGLISH. Room/NPC/item names remain German for now "
"(world translation in progress). Type HELP for English commands.")
if wahl in ("de", "deutsch", "german"):
self.p.sprache = "de"
return "🗣 Sprache: DEUTSCH. Tippe HILFE für alle Befehle."
return (f"Sprache/Language: {self.p.sprache.upper()}. "
"SPRACHE EN · SPRACHE DE")
def _verarbeite_roh(self, eingabe):
text = eingabe.strip()
if not text:
return ""
teile = text.split(maxsplit=1)
befehl = _richtung_aliase.get(teile[0].lower(), teile[0].lower())
arg = teile[1].strip() if len(teile) > 1 else ""
if _norm(befehl) in _GODPORT_BEFEHLE:
ergebnis = self.cmd_godport()
self.save()
return ergebnis
# Emote?
if _norm(befehl) in _EMOTES:
ergebnis = self.cmd_emote(befehl, arg)
if ergebnis is not None:
return ergebnis
# Richtung direkt?
if befehl in ("gehe", "gang", "lauf", "laufe", "beweg") and arg:
worte = arg.split(maxsplit=1)
befehl = _richtung_aliase.get(worte[0].lower(), worte[0].lower())
arg = worte[1] if len(worte) > 1 else ""
if befehl in self.w.raeume[self.p.raum]["exits"] or befehl in _richtung_aliase.values():
ergebnis = self.cmd_gehe(befehl)
self.save()
return ergebnis
tabelle = {
"umschauen": (self.cmd_umschauen, False), "schaue": (self.cmd_schaue, True),
"l": (self.cmd_umschauen, False), "look": (self.cmd_umschauen, False),
"untersuche": (self.cmd_untersuche, True), "untersuch": (self.cmd_untersuche, True),
"betrachte": (self.cmd_untersuche, True), "betracht": (self.cmd_untersuche, True),
"x": (self.cmd_untersuche, True), "examine": (self.cmd_untersuche, True),
"nimm": (self.cmd_nimm, True), "ablegen": (self.cmd_ablegen, True),
"take": (self.cmd_nimm, True), "get": (self.cmd_nimm, True),
"drop": (self.cmd_ablegen, True),
"benutze": (self.cmd_benutze, True), "trinke": (self.cmd_benutze, True),
"use": (self.cmd_benutze, True), "drink": (self.cmd_benutze, True),
"esse": (self.cmd_benutze, True), "iss": (self.cmd_benutze, True),
"eat": (self.cmd_benutze, True),
"rueste": (self.cmd_rueste, True), "ruestung": (self.cmd_rueste, True),
"wear": (self.cmd_rueste, True), "wield": (self.cmd_rueste, True),
"greif": (self.cmd_angriff, True), "angriff": (self.cmd_angriff, True),
"attackiere": (self.cmd_angriff, True), "toete": (self.cmd_angriff, True),
"kill": (self.cmd_angriff, True), "attack": (self.cmd_angriff, True),
"fliehe": (self.cmd_fliehe, False), "flucht": (self.cmd_fliehe, False),
"flee": (self.cmd_fliehe, False),
"sprich": (self.cmd_sprich, True), "rede": (self.cmd_sprich, True),
"talk": (self.cmd_sprich, True),
"gib": (self.cmd_gib, True), "give": (self.cmd_gib, True),
"sprich": (self.cmd_sprich, True), "rede": (self.cmd_sprich, True),
"gib": (self.cmd_gib, True),
"kauf": (self.cmd_kauf, True), "kaufe": (self.cmd_kauf, True),
"buy": (self.cmd_kauf, True),
"verkauf": (self.cmd_verkauf, True), "verkaufe": (self.cmd_verkauf, True),
"sell": (self.cmd_verkauf, True),
"einzahlen": (self.cmd_einzahlen, True), "einzahl": (self.cmd_einzahlen, True),
"deposit": (self.cmd_einzahlen, True),
"abheben": (self.cmd_abheben, True), "abhebe": (self.cmd_abheben, True),
"withdraw": (self.cmd_abheben, True),
"kontostand": (self.cmd_kontostand, False), "bank": (self.cmd_kontostand, False),
"balance": (self.cmd_kontostand, False),
"truhe": (self.cmd_truhe, False), "chest": (self.cmd_truhe, False),
"lege": (self.cmd_lege, True), "lagere": (self.cmd_lege, True),
"store": (self.cmd_lege, True),
"hol": (self.cmd_hol, True), "hole": (self.cmd_hol, True),
"fetch": (self.cmd_hol, True),
"sprache": (self.cmd_sprache, True), "language": (self.cmd_sprache, True),
"rasse": (self.cmd_rasse, True), "race": (self.cmd_rasse, True),
"verkauf": (self.cmd_verkauf, True), "verkaufe": (self.cmd_verkauf, True),
"sag": (self.cmd_sagen, True), "sage": (self.cmd_sagen, True),
"say": (self.cmd_sagen, True),
"post": (self.cmd_post, True), "brief": (self.cmd_post, True),
"mail": (self.cmd_post, True),
"brett": (self.cmd_brett, True), "notiz": (self.cmd_brett, True),
"board": (self.cmd_brett, True),
"zauber": (self.cmd_zauber, True), "zaubere": (self.cmd_zauber, True), "z": (self.cmd_zauber, True),
"spell": (self.cmd_zauber, True), "cast": (self.cmd_zauber, True),
"gilde": (self.cmd_gilde, True), "guild": (self.cmd_gilde, True),
"gildenruf": (self.cmd_gildenruf, True), "gchat": (self.cmd_gildenruf, True),
"rufe": (self.cmd_rufen, True),
"inv": (self.cmd_inventar, False), "inventar": (self.cmd_inventar, False),
"inventory": (self.cmd_inventar, False),
"i": (self.cmd_inventar, False),
"quests": (self.cmd_quests, False), "quest": (self.cmd_quests, False),
"aufgaben": (self.cmd_quests, False),
"status": (self.cmd_status, False), "stat": (self.cmd_status, False),
"spieler": (self.cmd_spieler, False),
"players": (self.cmd_spieler, False), "who": (self.cmd_spieler, False),
"hilfe": (self.cmd_hilfe, False), "?": (self.cmd_hilfe, False),
"help": (self.cmd_hilfe, False),
"befehle": (self.cmd_hilfe, False),
"godport": (self.cmd_godport, False), "goport": (self.cmd_godport, False),
"gottport": (self.cmd_godport, False), "teleport": (self.cmd_godport, False),
"godmap": (self.cmd_godmap, False), "gottkarte": (self.cmd_godmap, False),
"gottpost": (self.cmd_gottpost, False), "gottwer": (self.cmd_gottwer, False),
"gottspruch": (self.cmd_gottspruch, True), "gottheil": (self.cmd_gottheil, False),
"gottheilung": (self.cmd_gottheil, False),
"gottgold": (self.cmd_gottgold, True),
"gotttrank": (self.cmd_gotttrank, True),
"gottzug": (self.cmd_gottzug, True), "gotthole": (self.cmd_gotthole, True),
"gottzorn": (self.cmd_gottzorn, True),
"gottitem": (self.cmd_gottitem, True), "gottgabe": (self.cmd_gottitem, True),
"gottmonster": (self.cmd_gottmonster, True), "gottgeschöpf": (self.cmd_gottmonster, True),
}
eintrag = tabelle.get(befehl)
if not eintrag:
return f"Was meinst du mit '{eingabe}'? Tippe HILFE für alle Befehle."
fn, braucht_arg = eintrag
ergebnis = fn(arg) if braucht_arg else fn()
self.save()
return ergebnis
# ---------- Bewegung ----------
def cmd_gehe(self, richtung):
raum = self.w.raeume[self.p.raum]
ziel = raum["exits"].get(richtung)
if ziel is None:
if raum["exits"]:
return "Dahin geht es nicht. Ausgänge: " + ", ".join(self._exit_anzeigen(k, v) for k, v in raum["exits"].items())
return "Hier gibt es keine Ausgänge."
if self.p.kampf_ziel:
return "Du bist im Kampf! Erst FLIEHE oder siege."
if isinstance(ziel, dict):
if ziel.get("verschlossen"):
schluessel = ziel.get("schluessel")
if not schluessel or schluessel not in self.p.inventar:
return (f"Der Weg {richtung} ist versperrt: {ziel.get('tuer', 'eine Tür')} ist verschlossen. "
+ (f"Du brauchst: {self.w.items[schluessel]['name']}." if schluessel else ""))
ziel_id = ziel["raum"]
else:
ziel_id = ziel["raum"]
else:
ziel_id = ziel
if ziel_id not in self.w.raeume:
return "Dorthin führt kein Weg."
self.p.mana = min(self.p.max_mana, self.p.mana + 1)
self._sende(f"{self.p.name} geht Richtung {richtung}.")
self.p.raum = ziel_id
out = [self._raum_ansehen()]
for n in self.w.npc_in_raum(ziel_id):
if n["aggressiv"]:
self.p.kampf_ziel = n["id"]
out.append(f"⚔ {n['name']} fällt über dich her!")
out.append(self._kampf_runde())
break
return "\n".join(out)
def cmd_umschauen(self):
return self._raum_ansehen()
def cmd_schaue(self, arg):
return self.cmd_untersuche(arg) if arg else self._raum_ansehen()
def cmd_untersuche(self, arg):
"""UNTERSUCHE <ding>: Raum-Details, Gegenstände, NPCs (mit Kampfschätzung),
Mitspieler. Verrät keine Werte, die die Spannung killen."""
raum = self.w.raeume[self.p.raum]
if not arg:
dinge = sorted(s.replace("_", " ") for s in raum.get("details", {}))
zeilen = ["Auffällig hier: " + (", ".join(dinge) if dinge else "nichts Besonderes.")]
zeilen.append("Gegenstände und Personen hier kannst du ebenfalls untersuchen.")
return "\n".join(zeilen)
wort = _norm(arg)
# 1) Raum-Details
for schluessel, text in raum.get("details", {}).items():
k = _norm(schluessel)
if wort == k or (len(wort) >= 3 and (k.startswith(wort) or wort in k)):
return f"Du schaust dir {schluessel.replace('_', ' ')} an: {text}"
# 2) Gegenstand am Boden
iid = self._item_finden_boden(arg)
if iid:
return self._item_text(iid, "liegt hier")
# 3) Gegenstand im Inventar
iid = self._item_finden_inv(arg)
if iid:
return self._item_text(iid, "führst du bei dir")
# 4) NPC im Raum
npc = self._npc_finden(arg)
if npc:
return self._npc_text(npc)
# 5) Mitspieler im Raum
with _online_lock:
hier = [(n, e["spieler"]) for n, e in ONLINE.items()
if e["spieler"].raum == self.p.raum and n != self.p.name]
treffer = [n for n, _ in hier if _norm(n).startswith(wort)]
if len(treffer) == 1:
_, sp = next((n, s) for n, s in hier if n == treffer[0])
gilde = f", {sp.gilde.capitalize()}" if sp.gilde else ""
kampf = " Gerade im Kampf!" if sp.kampf_ziel else ""
return (f"{sp.name} — Stufe {sp.level}{gilde}. "
f"{'🩹 Angeschlagen.' if sp.hp < sp.max_hp // 3 else 'Frisch und munter.'}{kampf}")
if len(treffer) > 1:
return "Wen meinst du? " + ", ".join(treffer)
return f"Etwas namens „{arg}“ kannst du hier nicht entdecken."
def _item_text(self, iid, lage):
it = self.w.items[iid]
zeilen = [f"{it['name']} ({it['typ']}, {lage}): {it['beschreibung']}"]
if it["typ"] == "Waffe":
zeilen.append(f"Als Waffe führt sich das gut an: Schaden +{it.get('schaden', 0)}.")
elif it["typ"] == "Rüstung":
zeilen.append(f"Schützend: Verteidigung +{it.get('verteidigung', 0)}.")
elif it["typ"] == "Trank":
zeilen.append(f"Getrunken heilt es etwa {it.get('heilung', 20)} Lebenspunkte.")
wert = it.get("wert")
if wert:
zeilen.append(f"Händlern ist das etwa {wert} Gold wert.")
return "\n".join(zeilen)
def _npc_text(self, npc):
name = npc["name"]
if npc["aggressiv"]:
zeilen = [f"{name} — {npc['beschreibung']}"]
zustand = npc["hp"] / max(1, npc["max_hp"])
if zustand < 0.25:
zeilen.append("Zustand: schwer verwundet.")
elif zustand < 0.6:
zeilen.append("Zustand: verwundet.")
else:
zeilen.append("Zustand: ungeschwächt.")
zeilen.append(self._einschaetzen(npc))
return "\n".join(zeilen)
zeilen = [f"{name} wirkt friedlich."]
if npc.get("laden"):
zeilen.append("Ein Sortiment wird hier aufbewahrt — SPRICH mit dieser Person über Waren.")
if npc.get("quest") and npc["quest"]["id"] in self.p.quests_aktiv:
benoetigt = npc["quest"].get("anzahl", 1)
zeilen.append(f"{name} erwartet noch etwas von dir "
f"({self.p.quests_aktiv[npc['quest']['id']]}/{benoetigt}).")
elif npc.get("quest") and npc["quest"]["id"] not in self.p.quests_fertig:
zeilen.append(f"{name} scheint dir etwas zu sagen zu haben.")
return "\n".join(zeilen)
def _einschaetzen(self, npc):
"""Grobe Gefahren-Einschätzung (klassisches 'consider') — ohne Zahlen."""
pro_runde = max(1, self.p.schaden)
runden_fuer_gegner = max(0.5, npc["hp"] / pro_runde)
treffer_auf_mich = max(1, npc["schaden"] - self.p.verteidigung)
runden_fuer_mich = max(0.5, max(1, self.p.hp) / treffer_auf_mich)
if runden_fuer_mich >= runden_fuer_gegner * 3:
return "Einschätzung: Ein Kinderspiel."
if runden_fuer_mich >= runden_fuer_gegner * 1.4:
return "Einschätzung: Wirkt schwächer als du."
if runden_fuer_mich * 1.4 >= runden_fuer_gegner:
return "Einschätzung: Etwa auf Augenhöhe — ein fairer Kampf."
if runden_fuer_mich * 3 >= runden_fuer_gegner:
return "Einschätzung: Wirkt gefährlich — besser gewappnet als du."
return "Einschätzung: Lebensgefährlich! Ohne Vorbereitung wird das nichts."
def cmd_godport(self):
"""Gott-Teleport: sofort zum Startraum."""
ziel = self.w.start_raum
if ziel not in self.w.raeume:
return "Startraum ist unbekannt."
if self.p.raum == ziel:
return "Du bist bereits am Start.\n" + self._raum_ansehen()
self.p.kampf_ziel = None
self._sende(f"{self.p.name} verschwindet in einem Lichtblitz.")
self.p.raum = ziel
self._sende(f"{self.p.name} materialisiert sich.")
return "✦ Du wirst zum Startraum versetzt.\n" + self._raum_ansehen()
def cmd_godmap(self):
"""Gott-Karte: ASCII-Übersicht der Umgebung des Spielers."""
radius = 2
delta = {"norden": (0, -1), "sueden": (0, 1), "osten": (1, 0), "westen": (-1, 0),
"nordosten": (1, -1), "nordwesten": (-1, -1),
"suedosten": (1, 1), "suedwesten": (-1, 1)}
pos = {(0, 0): self.p.raum}
seen = {self.p.raum}
frontier = [(self.p.raum, 0, 0)]
while frontier:
rid, x, y = frontier.pop(0)
for richtung, ziel in self.w.raeume[rid]["exits"].items():
zid = ziel["raum"] if isinstance(ziel, dict) else ziel
if zid not in self.w.raeume or zid in seen:
continue
d = delta.get(richtung)
if not d:
continue
nx, ny = x + d[0], y + d[1]
if abs(nx) > radius or abs(ny) > radius or (nx, ny) in pos:
continue
pos[(nx, ny)] = zid
seen.add(zid)
frontier.append((zid, nx, ny))
zeilen = ["✦ GOTTKARTE (Norden ist oben) ✦"]
for y in range(-radius, radius + 1):
reihe = []
for x in range(-radius, radius + 1):
if (x, y) not in pos:
reihe.append(" ")
elif (x, y) == (0, 0):
reihe.append(" [☠] ")
else:
npcs = self.w.npc_in_raum(pos[(x, y)])
if any(n["aggressiv"] for n in npcs):
zeichen = "[!]"
elif npcs:
zeichen = "[o]"
else:
zeichen = "[·]"
reihe.append(f" {zeichen} ")
zeilen.append("│".join(reihe))
zeilen.append("Legende: [☠] = du · [·] = Raum · [o] = Ort mit NPC · [!] = Gegner")
kompass = {}
for (x, y), rid in pos.items():
if (x, y) == (0, 0):
continue
if y == -1 and x == 0: r = "N"
elif y == 1 and x == 0: r = "S"
elif x == 1 and y == 0: r = "O"
elif x == -1 and y == 0: r = "W"
elif x > 0 and y < 0: r = "NO"
elif x < 0 and y < 0: r = "NW"
elif x > 0 and y > 0: r = "SO"
else: r = "SW"
kompass[r] = _raum_name(self.w.raeume[rid]["name"])
umgebung = "; ".join(f"{r}: {name}" for r, name in
sorted(kompass.items(), key=lambda kv: "NNOOSOWW".find(kv[0][0]) * 2 + len(kv[0])))
return "\n".join(zeilen) + "\nUmgebung: " + umgebung
# ---------- Gegenstände ----------
def cmd_nimm(self, arg):
if arg and _norm(arg) in ("alles", "all"):
with self.w.lock:
boden = list(self.w.raeume[self.p.raum]["items"])
if not boden:
return "Hier liegt nichts zum Nehmen."
frei = max(0, self.p.kapazitaet() - len(self.p.inventar))
nimm = boden[:frei]
liegen = boden[frei:]
self.w.raeume[self.p.raum]["items"] = liegen
self.p.inventar.extend(nimm)
if not nimm:
return (f"Dein Beutel ist voll ({len(self.p.inventar)}/{self.p.kapazitaet()}). "
"VERKAUF, ABLEGEN oder die Truhe im Haus schafft Platz.")
namen = ", ".join(self.w.items[i]["name"] for i in nimm)
mehr = "Gegenstände" if len(nimm) != 1 else "Gegenstand"
hinweis = (f" — Dein Beutel ist voll ({len(self.p.inventar)}/{self.p.kapazitaet()}), "
f"{len(liegen)} blieben liegen." if liegen else "")
return f"Du nimmst alles: {namen}. ({len(nimm)} {mehr})" + hinweis
if not arg:
return "Was denn nehmen? NIMM <Gegenstand> · NIMM ALLES"
with self.w.lock:
if len(self.p.inventar) >= self.p.kapazitaet():
return (f"Dein Beutel ist voll ({len(self.p.inventar)}/{self.p.kapazitaet()}). "
"VERKAUF, ABLEGEN oder die Truhe im Haus schafft Platz.")
iid = self._item_finden_boden(arg)
if not iid:
return "So etwas liegt hier nicht."
self.w.raeume[self.p.raum]["items"].remove(iid)
self.p.inventar.append(iid)
return f"Du nimmst {self.w.items[iid]['name']}."
def cmd_ablegen(self, arg):
if not arg:
return "Was ablegen? ABLEGEN <Gegenstand>"
iid = self._item_finden_inv(arg)
if not iid:
return "Das hast du nicht."
self.p.inventar.remove(iid)
if self.p.waffe == iid: self.p.waffe = None
if self.p.ruestung == iid: self.p.ruestung = None
with self.w.lock:
self.w.raeume[self.p.raum]["items"].append(iid)
return f"Du legst {self.w.items[iid]['name']} ab."
def cmd_inventar(self):
zeilen = [f"💰 {self.p.gold} Gold | ❤ {self.p.hp}/{self.p.max_hp} HP | Stufe {self.p.level} ({self.p.xp} XP)",
f"In deinem Beutel ({len(self.p.inventar)}/{self.p.kapazitaet()}):"]
if not self.p.inventar:
zeilen.append(" (leer)")
for iid in self.p.inventar:
it = self.w.items[iid]
mark = " [geführt]" if self.p.waffe == iid else " [getragen]" if self.p.ruestung == iid else ""
zeilen.append(f" • {it['name']} ({it['typ']}){mark}")
return "\n".join(zeilen)
def cmd_benutze(self, arg):
if not arg:
return "Was benutzen? BENUTZE <Gegenstand>"
iid = self._item_finden_inv(arg)
if not iid:
return "Das hast du nicht."
it = self.w.items[iid]
if it["typ"] == "Trank":
self.p.inventar.remove(iid)
heilung = it.get("heilung", 20)
self.p.hp = min(self.p.max_hp, self.p.hp + heilung)
return f"Du trinkst {it['name']} und heilst um {heilung} HP. ({self.p.hp}/{self.p.max_hp})"
if it["typ"] == "Nahrung":
self.p.inventar.remove(iid)
self.p.hp = min(self.p.max_hp, self.p.hp + 5)
return f"Du isst {it['name']} und fühlst dich gestärkt. ({self.p.hp}/{self.p.max_hp})"
if it["typ"] == "Getränk":
self.p.inventar.remove(iid)
teile = [f"Du trinkst {it['name']}."]
if it.get("heilung"):
self.p.hp = min(self.p.max_hp, self.p.hp + it["heilung"])
teile.append(f"+{it['heilung']} HP")
if it.get("mana"):
self.p.mana = min(self.p.max_mana, self.p.mana + it["mana"])
teile.append(f"+{it['mana']} Mana")
teile.append(f"({self.p.hp}/{self.p.max_hp} HP, {self.p.mana}/{self.p.max_mana} Mana)")
return " ".join(teile)
if it["typ"] == "Essen":
self.p.inventar.remove(iid)
heilung = it.get("heilung", 5)
self.p.hp = min(self.p.max_hp, self.p.hp + heilung)
return (f"Du isst {it['name']} und fühlst dich gestärkt. "
f"(+{heilung} HP, {self.p.hp}/{self.p.max_hp})")
if it["typ"] in ("Waffe", "Rüstung"):
return self.cmd_rueste(arg)
return f"{it['name']}: {it['beschreibung']}"
def cmd_rueste(self, arg):
if not arg:
return "Was ausrüsten? RÜSTE <Waffe/Rüstung>"
iid = self._item_finden_inv(arg)
if not iid:
return "Das hast du nicht im Inventar."
it = self.w.items[iid]
if it["typ"] == "Waffe":
self.p.waffe = iid
return f"Du führst jetzt {it['name']} (Schaden +{it['schaden']})."
if it["typ"] == "Rüstung":
self.p.ruestung = iid
return f"Du trägst jetzt {it['name']} (Verteidigung +{it['verteidigung']})."
return f"{it['name']} kann man nicht ausrüsten."
# ---------- Truhe im Haus am Markt ----------
def _im_haus(self):
return self.p.raum == HAUS_RAUM
def cmd_truhe(self):
if not self._im_haus():
return "Deine Truhe steht in deinem Haus am Marktplatz (Ausgang: haus)."
kopf = f"🧰 Deine Truhe ({len(self.p.truhe)}/{TRUHE_FAECHER} Fächer):"
if not self.p.truhe:
return kopf + "\n (leer) — LEGE <x> lagert etwas ein."
zeilen = [kopf] + [f" • {self.w.items[i]['name']} ({self.w.items[i]['typ']})"
for i in self.p.truhe]
zeilen.append("Herausnehmen: HOL <x> · Einlagern: LEGE <x>")
return "\n".join(zeilen)
def cmd_lege(self, arg):
if not self._im_haus():
return "Die Truhe ist in deinem Haus am Marktplatz (Ausgang: haus)."
if not arg:
return "Was einlagern? LEGE <Gegenstand>"
iid = self._item_finden_inv(arg)
if not iid:
return "Das hast du nicht bei dir."
if self.p.waffe == iid or self.p.ruestung == iid:
return "Ausgerüstete Gegenstände erst ABLEGEN, dann einlagern."
if len(self.p.truhe) >= TRUHE_FAECHER:
return f"Deine Truhe ist voll ({TRUHE_FAECHER}/{TRUHE_FAECHER} Fächer)."
self.p.inventar.remove(iid)
self.p.truhe.append(iid)
return f"Du legst {self.w.items[iid]['name']} in die Truhe. ({len(self.p.truhe)}/{TRUHE_FAECHER} Fächer belegt)"
def cmd_hol(self, arg):
if not self._im_haus():
return "Die Truhe ist in deinem Haus am Marktplatz (Ausgang: haus)."
if not arg:
return "Was herausnehmen? HOL <Gegenstand>"
wort = _norm(arg)
treffer = [iid for iid in self.p.truhe if wort in _norm(self.w.items[iid]["name"])]
if not treffer:
return "Das liegt nicht in deiner Truhe. (TRUHE zeigt den Inhalt)"
iid = treffer[0]
if len(self.p.inventar) >= self.p.kapazitaet():
return (f"Dein Beutel ist voll ({len(self.p.inventar)}/{self.p.kapazitaet()}). "
"VERKAUF oder ABLEGEN schafft Platz.")
self.p.truhe.remove(iid)
self.p.inventar.append(iid)
return f"Du holst {self.w.items[iid]['name']} aus der Truhe. ({len(self.p.truhe)}/{TRUHE_FAECHER} Fächer belegt)"
# ---------- Kampf ----------
def cmd_angriff(self, arg):
if not arg:
return "Wen angreifen? GREIF <Name> AN"
npc = self._npc_finden(arg.replace(" an", "").strip())
if not npc:
return "Hier kämpft niemand mit diesem Namen."
with self.w.lock:
self.p.kampf_ziel = npc["id"]
self._sende(f"{self.p.name} greift {npc['name']} an!")
return self._kampf_runde()
def _kampf_runde(self):
with self.w.lock:
npc = self.w.npcs.get(self.p.kampf_ziel)
if not npc or npc["hp"] <= 0:
self.p.kampf_ziel = None
return "Der Kampf ist vorbei."
out = []
schaden = max(1, self.p.schaden + random.randint(-1, 3) - npc["verteidigung"])
npc["hp"] -= schaden
out.append(f"⚔ Du triffst {npc['name']} für {schaden} Schaden. ({max(0,npc['hp'])}/{npc['max_hp']} HP)")
if npc["hp"] <= 0:
out.extend(self._npc_besiegen(npc))
return "\n".join(out)
sch = max(1, npc["schaden"] + random.randint(-1, 2) - self.p.verteidigung)
self.p.hp -= sch
out.append(f"💢 {npc['name']} trifft dich für {sch} Schaden. (Du: {max(0,self.p.hp)}/{self.p.max_hp} HP)")
if self.p.hp <= 0:
out.append(self._spieler_stirbt())
return "\n".join(out)
def _npc_besiegen(self, npc):
"""Gegner ist tot: XP, Beute, Diebesgold, Broadcast. Gibt Textzeilen zurück."""
npc["tot_seit"] = time.time()
raum = self.w.raeume[npc["raum"]]
self.p.kampf_ziel = None
xp = 10 + npc["max_hp"] // 2
out = [f"☠ {npc['name']} ist besiegt! (+{self._rasse_xp(xp)} XP)"]
lvl = self._xp_vergeben(xp)
if lvl: out.append(lvl)
if npc["beute"]:
for iid in npc["beute"]:
raum["items"].append(iid)
out.append("Beute: " + ", ".join(self.w.items[i]["name"] for i in npc["beute"]))
if self.p.gilde == "dieb":
fund = max(2, npc["max_hp"] // 4)
self.p.gold += fund
out.append(f"🥷 Deine flinken Hände finden {fund} Gold in der Beute.")
self._sende(f"{self.p.name} hat {npc['name']} besiegt!")
return out
def _spieler_stirbt(self):
"""Todesfolge: Auftachen auf dem Marktplatz mit 10 HP, halbes Bargeld weg —
Bankguthaben bleibt unangetastet."""
self.p.hp = 10
self.p.raum = self.w.start_raum
self.p.kampf_ziel = None
verlust = self.p.gold // 2
self.p.gold -= verlust
hinten = (f" Dein Bankguthaben ({self.p.bank} Gold) ist sicher."
if self.p.bank else "")
return (f"\n💀 Du bist gefallen... Der Dorfbader weckt dich auf dem Marktplatz. "
f"Du verlierst {verlust} Gold (Bargeld)." + hinten)
def cmd_fliehe(self):
if not self.p.kampf_ziel:
return "Vor wem denn fliehen?"
npc = self.w.npcs[self.p.kampf_ziel]
self.p.kampf_ziel = None
raum = self.w.raeume[self.p.raum]
if raum["exits"] and random.random() < 0.7:
ziel = random.choice(list(raum["exits"].values()))
if isinstance(ziel, dict):
ziel = ziel["raum"]
self.p.raum = ziel
return f"Du entkommst {npc['name']} und fliehst.\n" + self._raum_ansehen()
return "Die Flucht misslingt – du bleibst im Kampf!\n" + self._kampf_runde()
# ---------- Dialog & Quests ----------
def cmd_sprich(self, arg):
npc = self._npc_finden(arg.replace(" mit", "").strip()) if arg else None
if not npc:
freundliche = [n for n in self.w.npc_in_raum(self.p.raum) if not n["aggressiv"]]
if freundliche:
npc = freundliche[0]
else:
return "Hier ist niemand, mit dem man reden könnte."
out = [f'{npc["name"]} sagt: „{npc["beschreibung"]}“']
if npc.get("laden"):
out.append(f"📦 {npc['name']} handelt: KAUF <ware> · VERKAUF <gegenstand>")
out.append("Sortiment: " + ", ".join(
f"{self.w.items[i]['name']} ({p} G)" for i, p in npc["laden"]["sortiment"].items()))
q = npc.get("quest")
if q and q["id"] not in self.p.quests_fertig:
if q["id"] in self.p.quests_aktiv:
benoetigt = q.get("anzahl", 1)
out.append(f"[Quest {q['name']}] Fortschritt: {self.p.quests_aktiv[q['id']]}/{benoetigt} "
f"({self.w.items[q['item']]['name']})")
else:
self.p.quests_aktiv[q["id"]] = 0
out.append(f"★ NEUE QUEST: {q['name']} — {q['beschreibung']}")
if q.get("uebergabe"):
self.p.inventar.append(q["uebergabe"])
out.append(f"(Du erhältst: {self.w.items[q['uebergabe']]['name']})")
return "\n".join(out)
def cmd_gib(self, arg):
if not arg:
return "Benutzung: GIB <Gegenstand> <NPC>"
worte = arg.split()
if len(worte) < 2:
return "Benutzung: GIB <Gegenstand> <NPC>"
npc = self._npc_finden(worte[-1])
if not npc:
return "Diese Person ist nicht hier."
item_wort = " ".join(worte[:-1]).replace(" an ", " ")
iid = self._item_finden_inv(item_wort)
if not iid:
return "So etwas hast du nicht."
return self._quest_abgeben(npc, iid)
def _quest_definition(self, qid):
"""Questdefinition anhand der ID in der ganzen Welt suchen."""
for n in self.w.npcs.values():
q = n.get("quest")
if q and q["id"] == qid:
return q
return None
def _quest_abgeben(self, npc, iid):
# Alle aktiven Quests, die an diesen NPC gerichtet sind
# (bei Selbstquests gilt: ziel == eigene ID).
kandidaten = []
for qid in self.p.quests_aktiv:
q = self._quest_definition(qid)
if q and q.get("ziel", npc["id"]) == npc["id"]:
kandidaten.append(q)
if not kandidaten:
return (f"{npc['name']} schüttelt den Kopf. "
"(Das gehört zu keiner aktiven Quest – sieh mit QUESTS nach, wer was will.)")
passende = [q for q in kandidaten if q["item"] == iid]
if not passende:
wollen = ", ".join(self.w.items[q["item"]]["name"] for q in kandidaten)
if len(kandidaten) == 1:
return f"{npc['name']} will {wollen} haben."
return f"{npc['name']} erwartet: {wollen}. Was davon gibst du ihm?"
q = passende[0]
benoetigt = q.get("anzahl", 1)
zaehler = self.p.quests_aktiv[q["id"]] + 1
self.p.quests_aktiv[q["id"]] = zaehler
self.p.inventar.remove(iid)
if zaehler < benoetigt:
return (f"{npc['name']} nimmt {self.w.items[iid]['name']} dankend an. "
f"({zaehler}/{benoetigt} abgegeben)")
self.p.quests_fertig.append(q["id"])
del self.p.quests_aktiv[q["id"]]
out = [f"✔ QUEST ABGESCHLOSSEN: {q['name']}! ({npc['name']} dankt dir.)"]
for belohnung in q["belohnung"]:
self.p.inventar.append(belohnung)
it = self.w.items[belohnung]
zusatz = (f" (Schaden +{it['schaden']})" if it["typ"] == "Waffe" else
f" (Verteidigung +{it['verteidigung']})" if it["typ"] == "Rüstung" else "")
out.append(f" Belohnung: {it['name']}{zusatz}")
self.p.gold += q.get("gold", 0)
if q.get("gold"):
out.append(f" (+{q['gold']} Gold)")
lvl = self._xp_vergeben(q.get("xp", 25))
out.append(f" (+{self._rasse_xp(q.get('xp', 25))} XP)")
if lvl: out.append(lvl)
return "\n".join(out)
def cmd_quests(self):
if not self.p.quests_aktiv:
return "Du hast keine aktiven Quests. Sprich mit den Dorfbewohnern (SPRICH <Name>)!"
out = ["Deine Quests:"]
for qid, zaehler in self.p.quests_aktiv.items():
q = next(n["quest"] for n in self.w.npcs.values() if n.get("quest") and n["quest"]["id"] == qid)
ziel = next((n["name"] for n in self.w.npcs.values() if n["id"] == q["ziel"]), "?")
out.append(f" ★ {q['name']}: {q['beschreibung']} → abgeben bei: {ziel} "
f"({zaehler}/{q.get('anzahl', 1)})")
return "\n".join(out)
# ---------- Handel ----------
def _haendler(self):
for n in self.w.npc_in_raum(self.p.raum):
if n.get("laden"):
return n
return None
def cmd_kauf(self, arg):
npc = self._haendler()
if not npc:
return "Hier gibt niemandem etwas zu kaufen."
if self._nacht_ist() and not npc["laden"].get("nacht") and npc["id"] != "kraeuterhexe2":
return ("Die Läden haben geschlossen – nur die Kräuterhexe in Falkenmoor "
"und die Kneipen verkaufen bei Nacht. Kehre im Morgenlicht zurück!")
if not arg:
return "Was kaufen? KAUF <Ware>"
for iid, preis in npc["laden"]["sortiment"].items():
if _norm(arg) in _norm(self.w.items[iid]["name"]):
if self.p.rasse == "ork":
preis = (preis * 11 + 9) // 10 # Das Dorf traut Orks nicht über den Weg (10 % Aufschlag)
if self.p.gold < preis:
return f"{self.w.items[iid]['name']} kostet {preis} Gold – so viel hast du nicht."
self.p.gold -= preis
self.p.inventar.append(iid)
return f"Du kaufst {self.w.items[iid]['name']} für {preis} Gold. ({self.p.gold} G übrig)"
return "Das führt man hier nicht."
def cmd_verkauf(self, arg):
npc = self._haendler()
if not npc:
return "Hier kauft niemand etwas."
if not arg:
return "Was verkaufen? VERKAUF <Gegenstand>"
iid = self._item_finden_inv(arg)
if not iid:
return "Das hast du nicht."
it = self.w.items[iid]
if it["typ"] in ("Quest",):
return "Questgegenstände kannst du nicht verkaufen."
preis = max(1, it.get("wert", 5) // 2)
self.p.inventar.remove(iid)
if self.p.waffe == iid: self.p.waffe = None
if self.p.ruestung == iid: self.p.ruestung = None
self.p.gold += preis
return f"Du verkaufst {it['name']} für {preis} Gold. ({self.p.gold} G)"
# ---------- Gott-Administration (nur _ADMIN_NAMEN) ----------
def _ist_gott(self):
return self.p.name in _ADMIN_NAMEN
def _kein_gott(self):
return ("Ein Blitz zuckt über den Himmel und eine Stimme grollt: "
"„Diese Macht steht dir nicht zu.“")
def cmd_gottpost(self):
if not self._ist_gott():
return self._kein_gott()
briefe = db.mail_an_gott()
if not briefe:
return "🕊 Kein Sterblicher hat Gott zuletzt geschrieben."
out = ["🕊 POST AN GOTT (neueste zuerst):"]
for b in briefe:
out.append(f" ✉ {b['absender']} ({b['zeit']}): {b['text']}")
return "\n".join(out)
def cmd_gottwer(self):
if not self._ist_gott():
return self._kein_gott()
out = ["🕊 ALLWISSENHEIT — Helden dieses Reiches:"]
for name in db.alle_spieler():
daten = db.laden(name)
raum = _raum_name(self.w.raeume[daten.get("raum", self.w.start_raum)]["name"]) \
if daten.get("raum") in self.w.raeume else "?"
out.append(f" • {name} — Stufe {daten.get('level', 1)}, "
f"{daten.get('hp', '?')}/{daten.get('max_hp', '?')} HP, "
f"{daten.get('gold', 0)} Gold, Ort: {raum}"
+ (" (online)" if name in ONLINE else ""))
return "\n".join(out)
def cmd_gottspruch(self, arg):
if not self._ist_gott():
return self._kein_gott()
worte = arg.split(maxsplit=1)
if len(worte) < 2:
return "Benutzung: GOTTSPRUCH <name> <text> — Gott spricht zu einem Helden."
empfaenger_wort, text = worte[0], worte[1].strip()[:400]
treffer = [n for n in db.alle_spieler() if _norm(n).startswith(_norm(empfaenger_wort))]
if not treffer:
return "Ein solcher Held ist nicht registriert."
if len(treffer) > 1:
return "Name nicht eindeutig: " + ", ".join(treffer)
db.mail_senden("Gott", treffer[0], text)
self.sende_alle(f"🌩 Ein Donnergrollen hallt über Falkenmoor — Gott hat gesprochen.", ausser=None)
return f"🕊 Gott spricht zu {treffer[0]}: „{text}“ (im Postfach des Helden)"
def cmd_gottheil(self):
if not self._ist_gott():
return self._kein_gott()
self.p.hp = self.p.max_hp
self._sende(f"Ein warmes Licht erfüllt {self.p.name}.")
return f"✨ Göttliche Heilung: {self.p.hp}/{self.p.max_hp} HP."
def _gott_spieler(self, wort):
"""Registrierten Spieler per Präfix finden; gibt (name, daten, fehler) zurück."""
treffer = [n for n in db.alle_spieler() if _norm(n).startswith(_norm(wort))]
if not treffer:
return None, None, "Ein solcher Held ist nicht registriert."
if len(treffer) > 1:
return None, None, "Name nicht eindeutig: " + ", ".join(treffer)
name = treffer[0]
return name, db.laden(name), None
def _gott_fluestern(self, name, text):
"""Direkte Nachricht an einen online-Spieler."""
eintrag = ONLINE.get(name)
if eintrag and eintrag.get("ws"):
try:
eintrag["ws"].send(text)
except Exception:
pass
def _gott_speichern(self, name, daten):
db.speichern(name, daten)
def cmd_gottgold(self, arg):
if not self._ist_gott():
return self._kein_gott()
worte = arg.split()
if len(worte) != 2 or not worte[1].lstrip("-").isdigit():
return "Benutzung: GOTTGOLD <name> <menge> — Gold aus dem Himmel (auch negativ möglich)"
name, daten, fehler = self._gott_spieler(worte[0])
if fehler:
return fehler
menge = int(worte[1])
daten["gold"] = max(0, daten.get("gold", 0) + menge)
self._gott_speichern(name, daten)
if name in ONLINE:
ONLINE[name]["spieler"].gold = daten["gold"]
self._gott_fluestern(name, f"🌩 Ein Goldregen fällt vom Himmel: {menge:+d} Gold! (Neu: {daten['gold']})")
return f"💰 {name} erhält {menge:+d} Gold (Stand: {daten['gold']})."
def cmd_gotttrank(self, arg):
if not self._ist_gott():
return self._kein_gott()
worte = arg.split()
if len(worte) != 2 or not worte[1].isdigit():
return "Benutzung: GOTTTRANK <name> <anzahl> — Heiltränke für einen Helden"
name, daten, fehler = self._gott_spieler(worte[0])
if fehler:
return fehler
anzahl = max(1, min(20, int(worte[1])))
daten.setdefault("inventar", []).extend(["i_heiltrank"] * anzahl)
self._gott_speichern(name, daten)
if name in ONLINE:
ONLINE[name]["spieler"].inventar.extend(["i_heiltrank"] * anzahl)
self._gott_fluestern(name, f"✨ Göttliche Gabe: {anzahl} Heiltränke erscheinen in deinem Beutel!")
return f"🧪 {name} erhält {anzahl} Heiltränke."
def cmd_gottzug(self, arg):
"""Spieler sanft zum Start zurückholen (befreit aus jeder Lage)."""
if not self._ist_gott():
return self._kein_gott()
if not arg:
return "Benutzung: GOTTZUG <name> — holt den Helden zum Marktplatz zurück"
name, daten, fehler = self._gott_spieler(arg.split()[0])
if fehler:
return fehler
daten["raum"] = self.w.start_raum
daten["hp"] = max(daten.get("hp", 10), 10)
self._gott_speichern(name, daten)
if name in ONLINE:
ONLINE[name]["spieler"].raum = self.w.start_raum
ONLINE[name]["spieler"].kampf_ziel = None
self._gott_fluestern(name, "🌩 Eine göttliche Hand hebt dich sanft auf und trägt dich zum Marktplatz.")
return f"🌪 {name} wurde zum Marktplatz versetzt."
def cmd_gotthole(self, arg):
"""Spieler in den eigenen Raum holen."""
if not self._ist_gott():
return self._kein_gott()
if not arg:
return "Benutzung: GOTTHOLE <name> — holt den Helden zu dir"
name, daten, fehler = self._gott_spieler(arg.split()[0])
if fehler:
return fehler
daten["raum"] = self.p.raum
self._gott_speichern(name, daten)
if name in ONLINE:
ONLINE[name]["spieler"].raum = self.p.raum
ONLINE[name]["spieler"].kampf_ziel = None
self._gott_fluestern(name, f"🌩 Du wirst von einem Lichtwirbel erfasst und erscheinst bei {self.p.name}.")
return f"🌪 {name} erscheint in deinem Raum."
def cmd_gottzorn(self, arg):
"""Blitzschlag: halbiert die HP des Ziels (mindestens 1 lassen)."""
if not self._ist_gott():
return self._kein_gott()
if not arg:
return "Benutzung: GOTTZORN <name> — ein Blitz straft den Helden (halbe HP)"
name, daten, fehler = self._gott_spieler(arg.split()[0])
if fehler:
return fehler
schaden = max(1, daten.get("hp", 20) // 2)
daten["hp"] = max(1, daten.get("hp", 20) - schaden)
self._gott_speichern(name, daten)
if name in ONLINE:
ONLINE[name]["spieler"].hp = daten["hp"]
self.sende_alle(f"🌩 Ein Blitz zuckt vom Himmel und trifft {name}! Der Boden bebt.", ausser=None)
self._gott_fluestern(name, f"🌩 Göttlicher Zorn trifft dich: -{schaden} HP! (Du: {daten['hp']}/{daten.get('max_hp', '?')})")
return f"⚡ {name} vom Blitz getroffen: -{schaden} HP (übrig: {daten['hp']})."
def cmd_gottitem(self, arg):
"""Item aus dem Himmel (eigenes Inventar)."""
if not self._ist_gott():
return self._kein_gott()
if not arg:
return ("Benutzung: GOTTITEM <gegenstand> — lässt einen Gegenstand vom Himmel fallen "
"(z.B. gottitem langschwert)")
iid = self._item_finden_himmel(arg)
if not iid:
return "So ein Ding existiert nicht in dieser Welt. (Tipp: gottitem drachenschwert)"
self.p.inventar.append(iid)
self._sende(f"Ein Gegenstand fällt in einem Lichtstrahl vom Himmel.")
return f"🌟 Vom Himmel gefallen: {self.w.items[iid]['name']}."
def _item_finden_himmel(self, suchwort):
suchwort = _norm(suchwort)
for iid, it in self.w.items.items():
if suchwort in _norm(it["name"]):
return iid
return None
def cmd_gottmonster(self, arg):
"""Erschafft ein Monster im aktuellen Raum (Vorlage per Namenssuche oder zufällig)."""
if not self._ist_gott():
return self._kein_gott()
vorlagen = [n for n in self.w.npcs.values() if n["aggressiv"] and not n["id"].startswith("gott_")]
if arg:
sw = _norm(arg)
vorlagen = [n for n in vorlagen if sw in _norm(n["name"])] or vorlagen
vorlage = random.choice(vorlagen)
with self.w.lock:
nr = 1
while f"gott_monster_{nr}" in self.w.npcs:
nr += 1
nid = f"gott_monster_{nr}"
self.w.npcs[nid] = {"id": nid, "name": vorlage["name"], "raum": self.p.raum,
"hp": vorlage["max_hp"], "max_hp": vorlage["max_hp"],
"schaden": vorlage["schaden"], "verteidigung": vorlage["verteidigung"],
"aggressiv": True, "beschreibung": "Aus nichts erschaffen – ein Werk göttlichen Zorns.",
"quest": None, "beute": vorlage.get("beute", []), "tot_seit": None}
self.w.raeume[self.p.raum]["npcs"].append(nid)
self._sende(f"Der Himmel reißt auf – {vorlage['name']} materialisiert sich in einem Blitz!")
return f"🐉 Erschaffen: {vorlage['name']} (HP {vorlage['max_hp']}) ist nun in diesem Raum."
# ---------- Magie ----------
def cmd_zauber(self, arg):
if not arg:
out = [f"✨ Zauber (Mana: {self.p.mana}/{self.p.max_mana}):"]
for name, (lvl, kosten, _, beschreibung) in _ZAUBER.items():
offen = "✔" if self.p.level >= lvl else f"✗ (ab Stufe {lvl})"
kosten_eff = kosten - 1 if (self.p.gilde == "magier" or self.p.rasse == "elf") and kosten > 1 else kosten
out.append(f" [{name}] {beschreibung} — {kosten_eff} Mana {offen}")
out.append("Anwenden: ZAUBER <name> [gegner]")
return "\n".join(out)
worte = arg.split(maxsplit=1)
name = _norm(worte[0])
ziel_arg = worte[1].strip() if len(worte) > 1 else ""
treffer = [z for z in _ZAUBER if z.startswith(name)]
if not treffer:
return "Diesen Zauber kennst du nicht. (ZAUBER zeigt alle)"
name = treffer[0]
lvl, kosten, wirkung, _ = _ZAUBER[name]
if self.p.level < lvl:
return f"⟡ {name.capitalize()} lernt man erst ab Stufe {lvl}."
kosten = kosten - 1 if (self.p.gilde == "magier" or self.p.rasse == "elf") and kosten > 1 else kosten
if self.p.mana < kosten:
return f"⟡ Nicht genug Mana ({self.p.mana}/{kosten}). Zeit heilt… oder ein Heiltrank aus dem nächsten Kampf."
self.p.mana -= kosten
if wirkung == "port":
self.p.kampf_ziel = None
self._sende(f"{self.p.name} löst sich in Wirbelwind auf.")
self.p.raum = self.w.start_raum
return "✨ Der Heimwind trägt dich zum Marktplatz.\n" + self._raum_ansehen()
if wirkung == "heil25":
self.p.hp = min(self.p.max_hp, self.p.hp + 25)
self._sende(f"Ein sanfter Glanz umhüllt {self.p.name}.")
return f"✨ Heilung: {self.p.hp}/{self.p.max_hp} HP."
npc = self._npc_finden(ziel_arg) if ziel_arg else None
if ziel_arg and not npc:
self.p.mana += kosten
return "Hier ist kein solches Ziel."
if not npc:
# ohne Ziel: erstbesten Gegner im Raum
npc = next((n for n in self.w.npc_in_raum(self.p.raum) if n["aggressiv"]), None)
if not npc:
self.p.mana += kosten # Mana zurück, kein Ziel vorhanden
return "Hier gibt es nichts zu bekämpfen."
# Angriffszauber: Magieschaden trifft sofort; lebt der Gegner noch,
# schlägt er zurück (der Zauber war die Aktion dieser Runde).
schaden = int(wirkung[3:]) + self.p.level
self.p.kampf_ziel = npc["id"]
self._sende(f"{self.p.name} webt einen Zauber gegen {npc['name']}!")
with self.w.lock:
npc["hp"] -= schaden
if npc["hp"] <= 0:
out = [f"✨ {name.capitalize()} trifft {npc['name']} für {schaden} Schaden."]
out.extend(self._npc_besiegen(npc))
return "\n".join(out)
sch = max(1, npc["schaden"] + random.randint(-1, 2) - self.p.verteidigung)
self.p.hp -= sch
out = [f"✨ {name.capitalize()} trifft {npc['name']} für {schaden} Schaden. "
f"({npc['hp']}/{npc['max_hp']} HP)",
f"💢 {npc['name']} trifft dich für {sch} Schaden. (Du: {max(0,self.p.hp)}/{self.p.max_hp} HP)"]
if self.p.hp <= 0:
out.append(self._spieler_stirbt())
return "\n".join(out)
# ---------- Gilden ----------
def cmd_gilde(self, arg):
if not arg:
out = ["⚔ Die Gilden von Falkenmoor:"]
for g, info in _GILDEN.items():
out.append(f" • {g.capitalize()}: {info}")
out.append("Beitreten (am Gildenhaus im Rathaus): GILDE BEITRETEN <name>")
out.append("Gilden-Chat: GILDENRUF <text>")
if self.p.gilde:
out.append(f"Du bist: {self.p.gilde.capitalize()}")
return "\n".join(out)
worte = arg.split(maxsplit=1)
if _norm(worte[0]) in ("beitreten", "beitret", "beigehen") and len(worte) > 1:
name = _norm(worte[1].split()[0])
treffer = [g for g in _GILDEN if g.startswith(name)]
if not treffer:
return "Eine solche Gilde gibt es nicht. (GILDE zeigt alle)"
if len(treffer) > 1:
return "Name nicht eindeutig: " + ", ".join(treffer)
if not any(n["id"] == "gildenmeister" for n in self.w.npc_in_raum(self.p.raum)):
return "Der Gildenmeister erwartet dich im Rathaus von Falkenmoor."
gilde = treffer[0]
if self.p.gilde == gilde:
return f"Du bist bereits {gilde.capitalize()}."
if self.p.gilde:
return (f"Du bist schon {self.p.gilde.capitalize()}. Ein Gildenwechsel "
"kostet 100 Gold — sag GILDE WECHSELN <name>, wenn du sicher bist.")
self.p.gilde = gilde
if gilde == "krieger":
self.p.max_hp += 10; self.p.hp = self.p.max_hp
if gilde == "magier":
self.p.max_mana += 10; self.p.mana = self.p.max_mana
self._sende(f"{self.p.name} tritt feierlich der Gilde der {gilde.capitalize()} bei!")
return (f"⚔ Willkommen in der Gilde der {gilde.capitalize()}! "
f"(HP: {self.p.hp}/{self.p.max_hp}, Mana: {self.p.mana}/{self.p.max_mana})")
if _norm(worte[0]) == "wechseln" and len(worte) > 1:
if not self.p.gilde:
return "Du bist in keiner Gilde."
if self.p.gold < 100:
return "Ein Gildenwechsel kostet 100 Gold — so viel hast du nicht."
name = _norm(worte[1].split()[0])
treffer = [g for g in _GILDEN if g.startswith(name)]
if not treffer or len(treffer) > 1 or treffer[0] == self.p.gilde:
return "So geht das nicht. (GILDE zeigt die Gilden)"
self.p.gold -= 100
alt = self.p.gilde
self._gilde_bonus_entfernen(alt)
self.p.gilde = None
ergebnis = self.cmd_gilde("beitreten " + treffer[0])
if "Willkommen" in ergebnis:
return (f"Du wechselst von der Gilde der {alt.capitalize()} "
f"(Bonus entfernt) — {ergebnis}")
return ergebnis
return "Benutzung: GILDE · GILDE BEITRETEN <name> · GILDE WECHSELN <name> · GILDENRUF <text>"
def _gilde_bonus_entfernen(self, gilde):
"""Gildenbonus beim Verlassen zurückgeben (HP/Mana nie unter 1 fallen lassen)."""
if gilde == "krieger":
self.p.max_hp = max(10, self.p.max_hp - 10)
self.p.hp = min(self.p.hp, self.p.max_hp)
elif gilde == "magier":
self.p.max_mana = max(0, self.p.max_mana - 10)
self.p.mana = min(self.p.mana, self.p.max_mana)
def cmd_gildenruf(self, arg):
if not self.p.gilde:
return "Nur Gildenmitglieder können den Gildenruf nutzen."
if not arg:
return "Was rufen? GILDENRUF <text>"
with _online_lock:
namen = [n for n, e in ONLINE.items()
if e["spieler"].gilde == self.p.gilde and n != self.p.name]
for n in namen:
eintrag = ONLINE.get(n)
if eintrag and eintrag.get("ws"):
try:
eintrag["ws"].send(f"🛡 [{self.p.gilde.capitalize()}] {self.p.name}: {arg}")
except Exception:
pass
return f"🛡 [{self.p.gilde.capitalize()}] Du: {arg}"
# ---------- Emotes ----------
def cmd_emote(self, befehl, arg):
selbst, raum_text, braucht_ziel = _EMOTES[_norm(befehl)]
ziel = ""
if braucht_ziel and arg:
ziel_name = arg.split()[0]
with _online_lock:
hier = [n for n, e in ONLINE.items() if n != self.p.name]
hier += [n["name"] for n in self.w.npc_in_raum(self.p.raum)]
treffer = [n for n in hier if _norm(n).startswith(_norm(ziel_name))]
if len(treffer) > 1:
return "Wen meinst du? " + ", ".join(treffer)
if not treffer:
return f"{ziel_name} ist nicht hier."
ziel = treffer[0]
elif braucht_ziel:
ziel = "in die Runde"
ziel_zu = f" {ziel} zu" if ziel and ziel != "in die Runde" else (" allen zu" if ziel == "in die Runde" else "")
ziel_vor = f" vor {ziel}" if ziel else ""
try:
self._sende(raum_text.format(name=self.p.name, ziel_zu=ziel_zu))
except KeyError:
self._sende(raum_text.format(name=self.p.name))
return (selbst.format(ziel_zu=ziel_zu.replace(" zu", ""), ziel_vor=ziel_vor)
if "{" in selbst else selbst)
# ---------- Anschlagbrett ----------
def _am_brett(self):
return any(n["id"] == "anschlagbrett" for n in self.w.npc_in_raum(self.p.raum))
def cmd_brett(self, arg):
if not self._am_brett():
return "Hier hängt kein Anschlagbrett. Am Marktplatz von Falkenmoor findest du eins."
if not arg:
eintraege = db.brett_lesen()
if not eintraege:
return ("📋 Das Anschlagbrett ist leer.\n"
"Notiz anheften: BRETT <text> · entfernen: BRETT LOESCHE <nr>")
out = ["📋 ANSCHLAGBRETT (neueste zuerst):"]
for i, e in enumerate(eintraege, 1):
out.append(f" [{i}] {e['autor']} ({e['zeit']}): {e['text']}")
out.append("Notiz: BRETT <text> · eigene entfernen: BRETT LOESCHE <nr>")
return "\n".join(out)
worte = arg.split(maxsplit=1)
if _norm(worte[0]) in ("loesche", "loesch", "entferne", "weg"):
if len(worte) < 2 or not worte[1].strip().isdigit():
return "Benutzung: BRETT LOESCHE <nummer>"
nr = int(worte[1])
eintraege = db.brett_lesen()
if nr < 1 or nr > len(eintraege):
return f"Es gibt keinen Eintrag Nr. {nr} (siehe BRETT)."
return db.brett_loeschen(eintraege[nr - 1]["id"], self.p.name,
ist_gott=self._ist_gott())
text = arg.strip()[:300]
db.brett_schreiben(self.p.name, text)
self._sende(f"{self.p.name} heftet eine Notiz an das Anschlagbrett.")
return "📋 Deine Notiz hängt am Brett."
# ---------- Postsystem ----------
def _am_briefkasten(self):
return any(n["id"] == "briefkasten" for n in self.w.npc_in_raum(self.p.raum))
def cmd_post(self, arg):
if not self._am_briefkasten():
return ("Hier steht kein Briefkasten. Am Marktplatz von Falkenmoor, "
"direkt beim Brunnen, findest du einen.")
if not arg:
briefe = db.mail_postfach(self.p.name)
out = []
if briefe:
out.append("📬 Dein Postfach:")
for b in briefe[-10:]:
out.append(f" ✉ Von {b['absender']} ({b['zeit']}): {b['text']}")
else:
out.append("📬 Dein Postfach ist leer.")
out.append("Registrierte Helden: " + ", ".join(db.alle_spieler()) + ", Gott")
out.append("Schreiben: POST <name> <text>")
return "\n".join(out)
worte = arg.split(maxsplit=1)
if len(worte) < 2 or not worte[1].strip():
return "Benutzung: POST <name> <text>"
empfaenger_wort, text = worte[0], worte[1].strip()[:400]
if _norm(empfaenger_wort) == "gott":
db.mail_senden(self.p.name, "Gott", text)
# Hugo erhält automatisch eine Kopie jeder Botschaft an Gott
if "Hugo" in db.alle_spieler() and self.p.name != "Hugo":
db.mail_senden(f"{self.p.name} → Gott", "Hugo", text)
db.mail_senden("Gott", self.p.name,
"Dein Gebet wurde erhört. Gott hat deine Botschaft gelesen "
"und wird sie im rechten Moment bedenken. 🕊")
return ("📨 Dein Bote reitet gen Himmel – Gott empfängt jede Botschaft. "
"Eine Antwort ist bereits in deinem Postfach.")
namen = db.alle_spieler()
exakt = [n for n in namen if n.lower() == empfaenger_wort.lower()]
treffer = exakt or [n for n in namen if _norm(n).startswith(_norm(empfaenger_wort))]
if not treffer:
return (f"Kein Held namens '{empfaenger_wort}' ist registriert. "
"Registrierte Helden: " + (", ".join(namen) or "(keine)") + ", Gott")
if len(treffer) > 1:
return "Name nicht eindeutig – gemeint sein könnten: " + ", ".join(treffer)
empfaenger = treffer[0]
if empfaenger == self.p.name:
return "Dir selbst kannst du keine Post schicken – sprich mit dir selbst, das hilft auch."
db.mail_senden(self.p.name, empfaenger, text)
return f"📨 Dein Bote macht sich auf den Weg zu {empfaenger}!"
# ---------- Bank (Stadtkasse im Rathaus) ----------
def _am_schalter(self):
return any(n["id"] == "bankier" for n in self.w.npc_in_raum(self.p.raum))
def _betrag(self, arg):
"""Betrag aus Argument lesen: Zahl oder 'alles'. None bei Unsinn."""
wort = _norm(arg.strip())
if wort == "alles":
return "alles"
if wort.isdigit() and int(wort) > 0:
return int(wort)
return None
def cmd_einzahlen(self, arg):
if not self._am_schalter():
return "Hier ist kein Bankschalter. Die Stadtkasse ist im Rathaus von Falkenmoor."
betrag = self._betrag(arg)
if betrag is None:
return "Benutzung: EINZAHLEN <x> (oder EINZAHLEN ALLES)"
menge = self.p.gold if betrag == "alles" else betrag
if menge <= 0 or menge > self.p.gold:
return "So viel Gold hast du nicht in der Tasche."
self.p.gold -= menge
self.p.bank += menge
return (f"🏦 Du zahlst {menge} Gold in die Stadtkasse ein. "
f"(Bargeld: {self.p.gold} G · Guthaben: {self.p.bank} G)")
def cmd_abheben(self, arg):
if not self._am_schalter():
return "Hier ist kein Bankschalter. Die Stadtkasse ist im Rathaus von Falkenmoor."
betrag = self._betrag(arg)
if betrag is None:
return "Benutzung: ABHEBEN <x> (oder ABHEBEN ALLES)"
menge = self.p.bank if betrag == "alles" else betrag
if menge <= 0 or menge > self.p.bank:
return "So viel Gold ist nicht auf deinem Konto."
self.p.bank -= menge
self.p.gold += menge
return (f"🏦 Du hebst {menge} Gold ab. "
f"(Bargeld: {self.p.gold} G · Guthaben: {self.p.bank} G)")
def cmd_kontostand(self):
return (f"🏦 Bankguthaben: {self.p.bank} Gold · Bargeld: {self.p.gold} Gold. "
f"Einzahlen und Abheben geht an der Stadtkasse im Rathaus.")
# ---------- Rassen ----------
_RASSEN_LORE = {
"zwerg": {"de": "🛡 Du bist ein ZWERG aus dem Graukamm — Steinhatz und Brauerei-Meister. "
"Dein Körper ist Fels: +10 Max-HP, +1 Verteidigung. Dafür bleibt der "
"Zauberstab dir fremd: −5 Max-Mana.",
"en": "🛡 You are a DWARF of the Greycrag — hewn from stone itself: "
"+10 max HP, +1 defense. Magic, however, stays foreign: −5 max mana."},
"elf": {"de": "✨ Du bist ein ELF aus den Alten Wäldern. Magie fließt durch dich: "
"+10 Max-Mana, Zauber kosten 1 weniger. Dein Leib ist zart: −5 Max-HP.",
"en": "✨ You are an ELF of the Ancient Woods. Magic flows through you: "
"+10 max mana, spells cost 1 less. Your frame is frail: −5 max HP."},
"ork": {"de": "⚔ Du bist ein ORK der Graufluss-Höhlen. Deine Faust spricht: +2 Schaden. "
"Dafür ist dein Körper grob geschmiedet: −5 Max-HP — und das Dorf traut dir "
"nicht über den Weg (Händler: +10 % Preise).",
"en": "⚔ You are an ORC of the Greyriver caves. Your fist does the talking: "
"+2 damage. Your body is roughly forged: −5 max HP — and the village "
"doesn't trust you (shop prices +10%)."},
"mensch": {"de": "🌾 Du bist ein MENSCH aus Falkenmoor — anpassungsfähig wie alle dein Stamm: "
"+10 Gold Siedlerbonus und +10 % XP. Nichts mehr, nichts weniger.",
"en": "🌾 You are a HUMAN of Falkenmoor — adaptable like all your kin: "
"+10 gold settler's bonus and +10% XP. Nothing more, nothing less."},
}
def cmd_rasse(self, arg):
en = self.p.sprache == "en"
if self.p.rasse:
return self._RASSEN_LORE[self.p.rasse].get(
self.p.sprache, self._RASSEN_LORE[self.p.rasse]["de"])
if not arg:
if en:
return ("Choose your race — once, and for all time:\n"
" 🛡 ZWERG (dwarf): +10 max HP, +1 defense · −5 max mana\n"
" ✨ ELF: +10 max mana, spells cost 1 less · −5 max HP\n"
" ⚔ ORK (orc): +2 damage · −5 max HP · shop prices +10%\n"
" 🌾 MENSCH (human): +10 gold bonus · +10% XP\n"
"Choose: RASSE <zwerg|elf|ork|mensch>")
return ("Wähle deine Rasse — einmal, für immer:\n"
" 🛡 ZWERG: +10 Max-HP, +1 Verteidigung · −5 Max-Mana\n"
" ✨ ELF: +10 Max-Mana, Zauber kosten 1 weniger · −5 Max-HP\n"
" ⚔ ORK: +2 Schaden · −5 Max-HP · Händler +10 % Preise\n"
" 🌾 MENSCH: +10 Gold Siedlerbonus · +10 % XP\n"
"Wählen: RASSE <zwerg|elf|ork|mensch>")
wahl = _RASSEN_ALIASE.get(_norm(arg).split()[0], "")
if not wahl:
return ("Diese Rasse kennt Falkenmoor nicht. "
"Wählbar: zwerg, elf, ork, mensch." if not en else
"Falkenmoor doesn't know that race. Available: zwerg (dwarf), elf, ork, mensch (human).")
# Boni anwenden — einmalig
if wahl == "zwerg":
self.p.max_hp += 10; self.p.hp += 10
self.p.basis_verteidigung += 1
self.p.max_mana = max(0, self.p.max_mana - 5)
self.p.mana = min(self.p.mana, self.p.max_mana)
elif wahl == "elf":
self.p.max_mana += 10; self.p.mana = self.p.max_mana
self.p.max_hp = max(10, self.p.max_hp - 5)
self.p.hp = min(self.p.hp, self.p.max_hp)
elif wahl == "ork":
self.p.max_hp = max(10, self.p.max_hp - 5)
self.p.hp = min(self.p.hp, self.p.max_hp)
elif wahl == "mensch":
self.p.gold += 10
self.p.rasse = wahl
return self._RASSEN_LORE[wahl].get(self.p.sprache, self._RASSEN_LORE[wahl]["de"])
# ---------- Kommunikation ----------
def cmd_sagen(self, arg):
if not arg:
return "Was sagen? SAG <Text>"
self._sende(f"💬 {self.p.name}: {arg}")
return f'Du sagst: „{arg}“'
def cmd_rufen(self, arg):
if not arg:
return "Was rufen? RUFE <Text>"
self.sende_alle(f"📢 {self.p.name} ruft: {arg}", ausser=self.p.name)
return f"Du rufst: „{arg}“"
def cmd_spieler(self):
with _online_lock:
gegenwaertig = [(name, e["spieler"].level) for name, e in ONLINE.items()]
if not gegenwaertig:
return "Außer dir ist gerade niemand hier."
return "Helden online: " + ", ".join(
f"{name} (Stufe {lvl})" for name, lvl in gegenwaertig)
# ---------- Status & Hilfe ----------
def cmd_status(self):
waffe = self.w.items[self.p.waffe]["name"] if self.p.waffe else "Fäuste"
ruestung = self.w.items[self.p.ruestung]["name"] if self.p.ruestung else "nur Stoff"
rasse_txt = f" · Rasse: {self.p.rasse.capitalize()}" if self.p.rasse else ""
return (f"👤 {self.p.name} — Stufe {self.p.level} ({self.p.xp} XP){rasse_txt}\n"
f"❤ {self.p.hp}/{self.p.max_hp} HP | ⚔ Schaden {self.p.schaden} | 🛡 Verteidigung {self.p.verteidigung}\n"
f"Waffe: {waffe} | Rüstung: {ruestung} | 💰 {self.p.gold} Gold | 🏦 {self.p.bank} Gold (Bank)\n"
f"Ort: {_raum_name(self.w.raeume[self.p.raum]['name'])}")
def cmd_hilfe(self):
if self.p.sprache == "en":
out = uebersetz.hilfe("en")
if self._ist_gott():
out += ("\n\n🕊 GODLY POWER (only yours):\nGOTTPOST · GOTTWER · GOTTSPRUCH "
"<name> <text> · GOTTHEIL · GODMAP · GOTTGOLD <name> <±amount> · "
"GOTTTRANK · GOTTITEM · GOTTZUG · GOTTHOLE · GOTTZORN · GOTTMONSTER")
return out
return (
"═══ FALKENMOOR — BEFEHLE ═══\n"
"Bewegung: NORDEN/SÜDEN/OSTEN/WESTEN (auch n/s/o/w) · UMSCHAUEN\n"
"Dinge: UNTERSUCHE <x> · BETRACHTE <x> · SCHAUE <x> — Details (auch: x <x>)\n"
"Gegenstände: NIMM <x> · NIMM ALLES · ABLEGEN <x> · BENUTZE/TRINKE <x> · RÜSTE <x> · INVENTAR\n"
"Haus: TRUHE · LEGE <x> · HOL <x> — dein Haus am Markt (Ausgang: haus)\n"
"Kampf: GREIF <name> AN · FLIEHE\n"
"Rede: SPRICH <name> · GIB <gegenstand> <npc>\n"
"Handel: KAUF <ware> · VERKAUF <gegenstand> (bei Händlern)\n"
"Bank: EINZAHLEN <x> · ABHEBEN <x> · KONTOSTAND — Stadtkasse im Rathaus\n"
"Sprache: SPRACHE EN · SPRACHE DE — englische Befehle: take, buy, kill, flee …\n"
"Chat: SAG <text> (Raum) · RUFE <text> (alle) · SPIELER (wer online ist)\n"
"Post: POST (Postfach) · POST <name> <text> — am Briefkasten auf dem Marktplatz\n"
"Brett: BRETT (lesen) · BRETT <text> (Notiz) — am Anschlagbrett auf dem Marktplatz\n"
"Emotes: LACH · WEINE · TANZ · VERBEUGE [name] · ZWINKERE <name> · GRUESS [name] …\n"
"Magie: ZAUBER (Liste) · ZAUBER <name> [gegner] — kostet Mana (regeneriert beim Reisen)\n"
"Gilden: GILDE · GILDE BEITRETEN <name> (im Rathaus) · GILDENRUF <text>\n"
"Rasse: RASSE · RASSE <zwerg|elf|ork|mensch> — einmalig wählbar\n"
"Quests: QUESTS · STATUS · HILFE · QUIT (Abmelden)\n"
"Tipp: Nachts sind die Läden geschlossen — nur die Kräuterhexe und die Kneipen "
"schenken bei Nacht aus. Verschlossene Wege brauchen den passenden Schlüssel!"
+ ("\n🕊 GOTTESMACHT (nur für dich):\n"
"GOTTPOST (Post an Gott) · GOTTWER (alle Helden) · "
"GOTTSPRUCH <name> <text> · GOTTHEIL (Heilung) · GODMAP (Karte)\n"
"GOTTGOLD <name> <±menge> · GOTTTRANK <name> <anzahl> · GOTTITEM <gegenstand>\n"
"GOTTZUG <name> (zum Markt) · GOTTHOLE <name> (zu dir) · "
"GOTTZORN <name> (Blitz) · GOTTMONSTER [suchwort] (erschaffen)"
if self._ist_gott() else "")
)Mehr aus der Kategorie „Falkencode“
- Falkencode: app.py — Webserver & WebSocket 09.09.2026
- Falkencode: db.py — Konten, Spielstand, Post 09.09.2026
- Falkencode: uebersetz.py — Der DE/EN-Katalog 09.09.2026
- Falkencode: gen_world.py — Der Weltgenerator 09.09.2026