v2.3 — Vollständige D&D 5e Status-Effekte

32 Status-Effekte (alle D&D 5e Conditions):
Schaden über Zeit: Gift🐍, Brennen🔥, Blutung🩸, Verätzung⚗️, Nekrotisch💀
Debuffs: Schwäche💜, Erschöpfung😫, Furcht😱, Verlangsamt🐌, Geblendet🙈,
  Taub🔇, Gelähmt, Betäubt💫, Gefesselt🔗, Niedergestreckt🤕,
  Versteinert🗿, Erschöpft🥵
Buffs: Regeneration💚, Gesegnet, Wutanfall😠, Glaubensschild🛡️,
  Beschleunigt, Steinhaut🪨, Baumhaut🌳, Heldenmut🦸,
  Bewegungsfreiheit🕊️, Wahrer Streich🎯, Magische Rüstung🔮,
  Göttliche Gunst🙏, Vergrößert📈
Heilung: Verband🩹, Entgiftung🌿

Monster-Inflicts (nach Typ):
- Spinne/Naga: Gift, Drache/Feuer: Brennen, Vampir/Bestie: Blutung
- Hexe/Fluch: Schwäche, Geist/Phantom: Furcht
- Schleim/Säure: Verätzung, Untot/Skelett: Nekrotisch
- Medusa/Basilisk: Blenden, Oger/Troll: Betäubung
- Eis/Frost: Verlangsamung, Wüste/Hitze: Erschöpfung

Combat:
- Gelähmt/Betäubt/Versteinert → kann nicht angreifen
- Geblendet/Gefesselt/Niedergestreckt/Furcht → Attack Disadvantage
- Effekte ticken jede Runde (Schaden/Heilung)

Shop:
- Verbände (15G): Stoppt Blutung/Brennen, heilt 1d4+2, gibt Regeneration
- Gegengift (25G): Entfernt alle negativen Effekte
- Trank-Anzeige: ❤️  💖 🌿 🩹
This commit is contained in:
arch_agent
2026-07-25 23:23:51 +02:00
parent b1aa8e3162
commit c223f1ba2e
3 changed files with 185 additions and 65 deletions
+106 -40
View File
@@ -249,12 +249,61 @@ class DnDCharacter:
self.spell_slots -= 1
# === Status Effects ===
def add_effect(self, effect_type: str, duration: int, power: int, name: str):
# Complete D&D 5e condition list adapted for auto-RPG
EFFECT_DEFINITIONS = {
# Damage over time
"poison": {"icon": "🐍", "tick": "damage", "desc": "Vergiftet"},
"burn": {"icon": "🔥", "tick": "damage", "desc": "Brennend"},
"bleed": {"icon": "🩸", "tick": "damage", "desc": "Blutung"},
"acid": {"icon": "⚗️", "tick": "damage", "desc": "Verätzt"},
"necrotic": {"icon": "💀", "tick": "damage", "desc": "Nekrotisch"},
# Debuffs
"curse_weakness":{"icon": "💜", "tick": "none", "desc": "Schwäche", "stat": "attack", "mod": -2},
"curse_fatigue": {"icon": "😫", "tick": "none", "desc": "Erschöpfung", "stat": "all", "mod": -1},
"fear": {"icon": "😱", "tick": "skip_chance", "desc": "Verängstigt", "skip_pct": 50},
"slow": {"icon": "🐌", "tick": "none", "desc": "Verlangsamt", "stat": "ac", "mod": -2},
"blinded": {"icon": "🙈", "tick": "disadvantage", "desc": "Geblendet"},
"deafened": {"icon": "🔇", "tick": "none", "desc": "Taub"},
"paralyzed": {"icon": "", "tick": "skip_all", "desc": "Gelähmt"},
"stunned": {"icon": "💫", "tick": "skip_all", "desc": "Betäubt"},
"restrained": {"icon": "🔗", "tick": "disadvantage", "desc": "Gefesselt"},
"prone": {"icon": "🤕", "tick": "disadvantage", "desc": "Niedergestreckt"},
"petrified": {"icon": "🗿", "tick": "skip_all", "desc": "Versteinert"},
"exhaustion": {"icon": "🥵", "tick": "none", "desc": "Erschöpft", "stat": "all", "mod": -2},
# Buffs
"regen": {"icon": "💚", "tick": "heal", "desc": "Regeneration"},
"bless": {"icon": "", "tick": "none", "desc": "Gesegnet", "stat": "attack", "mod": 2},
"rage": {"icon": "😠", "tick": "none", "desc": "Wutanfall", "stat": "attack", "mod": 3},
"shield_of_faith":{"icon": "🛡️", "tick": "none", "desc": "Glaubensschild", "stat": "ac", "mod": 3},
"haste": {"icon": "", "tick": "extra_attack", "desc": "Beschleunigt"},
"stoneskin": {"icon": "🪨", "tick": "none", "desc": "Steinhaut", "stat": "ac", "mod": 4},
"barkskin": {"icon": "🌳", "tick": "none", "desc": "Baumhaut", "stat": "ac", "mod": 2},
"heroism": {"icon": "🦸", "tick": "heal", "desc": "Heldenmut"},
"freedom": {"icon": "🕊️", "tick": "none", "desc": "Bewegungsfreiheit"},
"true_strike": {"icon": "🎯", "tick": "advantage", "desc": "Wahrer Streich"},
"mage_armor": {"icon": "🔮", "tick": "none", "desc": "Magische Rüstung", "stat": "ac", "mod": 3},
"divine_favor": {"icon": "🙏", "tick": "none", "desc": "Göttliche Gunst", "stat": "attack", "mod": 2},
"enlarge": {"icon": "📈", "tick": "none", "desc": "Vergrößert", "stat": "attack", "mod": 2},
# Bandages / healing items
"bandaged": {"icon": "🩹", "tick": "heal", "desc": "Verbunden"},
"antidote_effect":{"icon": "🌿", "tick": "cleanse_tick", "desc": "Entgiftet"},
}
NEGATIVE_EFFECTS = [
"poison", "burn", "bleed", "acid", "necrotic",
"curse_weakness", "curse_fatigue", "fear", "slow",
"blinded", "deafened", "paralyzed", "stunned",
"restrained", "prone", "petrified", "exhaustion",
]
def add_effect(self, effect_type: str, duration: int, power: int, name: str = ""):
"""Fügt einen Statuseffekt hinzu."""
# Remove existing effect of same type
self.status_effects = [e for e in self.status_effects if e["type"] != effect_type]
definition = self.EFFECT_DEFINITIONS.get(effect_type, {"icon": "", "desc": name})
self.status_effects.append({
"type": effect_type, "duration": duration, "power": power, "name": name
"type": effect_type, "duration": duration, "power": power,
"name": name or definition.get("desc", effect_type),
"icon": definition.get("icon", ""),
})
def has_effect(self, effect_type: str) -> bool:
@@ -267,65 +316,88 @@ class DnDCharacter:
return None
def tick_effects(self) -> list:
"""Tickt alle Effekte, gibt Liste der abgelaufenen und gewirkten zurück."""
"""Tickt alle Effekte pro Runde, gibt Liste der Ereignisse zurück."""
results = []
for effect in self.status_effects[:]:
effect["duration"] -= 1
etype = effect["type"]
power = effect["power"]
icon = effect.get("icon", "")
# Apply per-turn effects
if effect["type"] == "poison":
dmg = effect["power"]
self.hp = max(0, self.hp - dmg)
results.append(f"[green]🐍 Gift schadet {dmg} HP (HP: {self.hp}/{self.max_hp})[/]")
elif effect["type"] == "burn":
dmg = effect["power"]
self.hp = max(0, self.hp - dmg)
results.append(f"[red]🔥 Brennen schadet {dmg} HP[/]")
elif effect["type"] == "regen":
heal = effect["power"]
self.hp = min(self.max_hp, self.hp + heal)
results.append(f"[green]💚 Regeneration heilt {heal} HP[/]")
elif effect["type"] == "bleed":
dmg = effect["power"]
self.hp = max(0, self.hp - dmg)
results.append(f"[red]🩸 Blutung schadet {dmg} HP[/]")
if etype in ("poison", "burn", "bleed", "acid", "necrotic"):
self.hp = max(0, self.hp - power)
results.append(f"[{self._effect_color(etype)}]{icon} {effect['name']} schadet {power} HP (HP: {self.hp}/{self.max_hp})[/]")
elif etype == "regen" or etype == "heroism":
self.hp = min(self.max_hp, self.hp + power)
results.append(f"[green]{icon} Regeneration heilt {power} HP (HP: {self.hp}/{self.max_hp})[/]")
elif etype == "bandaged":
self.hp = min(self.max_hp, self.hp + power)
results.append(f"[green]{icon} Verband heilt {power} HP[/]")
elif etype == "antidote_effect":
# Ticks: removes one negative effect per turn
for neg in self.NEGATIVE_EFFECTS:
if self.has_effect(neg) and neg != "antidote_effect":
self.status_effects = [e for e in self.status_effects if e["type"] != neg]
results.append(f"[green]{icon} Entgiftung entfernt {neg}[/]")
break
if effect["duration"] <= 0:
self.status_effects.remove(effect)
results.append(f"[dim] {effect['name']} ist abgeklungen.[/]")
results.append(f"[dim]{icon} {effect['name']} ist abgeklungen.[/]")
return results
def _effect_color(self, etype: str) -> str:
colors = {
"poison": "green", "burn": "red", "bleed": "red",
"acid": "yellow", "necrotic": "bold red",
}
return colors.get(etype, "red")
def is_immobilized(self) -> bool:
"""True wenn der Charakter nicht handeln kann."""
return self.has_effect("paralyzed") or self.has_effect("stunned") or self.has_effect("petrified")
def has_attack_disadvantage(self) -> bool:
return self.has_effect("blinded") or self.has_effect("restrained") or self.has_effect("prone") or self.has_effect("fear")
def cleanse(self) -> int:
"""Entfernt alle negativen Effekte. Gibt Anzahl entfernter zurück."""
negative = ["poison", "burn", "bleed", "curse_weakness", "curse_fatigue", "fear", "slow"]
removed = 0
for neg in negative:
for neg in self.NEGATIVE_EFFECTS:
before = len(self.status_effects)
self.status_effects = [e for e in self.status_effects if e["type"] != neg]
removed += before - len(self.status_effects)
return removed
def apply_bandage(self) -> int:
"""Verband anwenden — heilt und fügt regen-Effekt hinzu."""
heal = roll(4, 1, 2)["total"] # 1d4+2
self.hp = min(self.max_hp, self.hp + heal)
self.add_effect("bandaged", 3, 2, "Verband")
return heal
@property
def effective_ac(self) -> int:
"""AC mit Buffs/Debuffs."""
ac = self.ac
if self.has_effect("shield_of_faith"):
ac += self.get_effect("shield_of_faith")["power"]
if self.has_effect("curse_weakness"):
ac -= 2
for effect in self.status_effects:
definition = self.EFFECT_DEFINITIONS.get(effect["type"], {})
if definition.get("stat") == "ac":
ac += definition.get("mod", 0) + effect.get("power", 0)
return max(1, ac)
@property
def effective_attack(self) -> int:
"""Attack Bonus mit Buffs/Debuffs."""
bonus = self.attack_bonus
if self.has_effect("bless"):
bonus += 2
if self.has_effect("curse_weakness"):
bonus -= 2
if self.has_effect("rage"):
bonus += self.get_effect("rage")["power"]
for effect in self.status_effects:
definition = self.EFFECT_DEFINITIONS.get(effect["type"], {})
if definition.get("stat") == "attack":
bonus += definition.get("mod", 0) + effect.get("power", 0)
elif definition.get("stat") == "all":
bonus += definition.get("mod", 0)
return max(0, bonus)
@property
@@ -333,15 +405,9 @@ class DnDCharacter:
"""Kurze Zusammenfassung der aktiven Effekte für UI."""
if not self.status_effects:
return "[dim]keine[/]"
icons = {
"poison": "🐍", "burn": "🔥", "regen": "💚", "bleed": "🩸",
"bless": "", "rage": "😠", "shield_of_faith": "🛡️",
"curse_weakness": "💜", "curse_fatigue": "😫", "fear": "😱", "slow": "🐌",
"haste": "", "strength": "💪",
}
parts = []
for e in self.status_effects:
icon = icons.get(e["type"], "")
icon = e.get("icon", "")
parts.append(f"{icon}{e['duration']}")
return " ".join(parts)
+75 -23
View File
@@ -67,18 +67,41 @@ class DnDMonster:
# Status effects the monster can inflict (based on type)
self.inflicts = []
monster_type = name.lower()
if any(w in monster_type for w in ["spinne", "naga", "schlang", "skorpion", "gift"]):
mt = name.lower()
# Poison
if any(w in mt for w in ["spinne", "naga", "schlang", "skorpion", "gift", "nattern"]):
self.inflicts.append({"type": "poison", "chance": 30, "duration": 3, "power": max(1, level // 2), "name": "Giftbiss"})
if any(w in monster_type for w in ["drache", "feuer", "dämon", "phoenix", "flamme"]):
# Burn
if any(w in mt for w in ["drache", "feuer", "dämon", "phoenix", "flamme", "elementar"]):
self.inflicts.append({"type": "burn", "chance": 25, "duration": 2, "power": max(1, level // 2), "name": "Feueratem"})
if any(w in monster_type for w in ["vampir", "blut", "schnitter", "todes"]):
# Bleed
if any(w in mt for w in ["vampir", "blut", "schnitter", "todes", "werwolf", "bestie"]):
self.inflicts.append({"type": "bleed", "chance": 35, "duration": 3, "power": max(1, level // 3), "name": "Blutung"})
if any(w in monster_type for w in ["hexe", "fluch", "verflucht", "schatten", "dunkel"]):
# Curse
if any(w in mt for w in ["hexe", "fluch", "verflucht", "schatten", "dunkel", "necroman"]):
self.inflicts.append({"type": "curse_weakness", "chance": 20, "duration": 3, "power": 2, "name": "Schwächungsfluch"})
if any(w in monster_type for w in ["geist", "gespenst", "furcht", "schrecken"]):
# Fear
if any(w in mt for w in ["geist", "gespenst", "furcht", "schrecken", "phantom", "wraith"]):
self.inflicts.append({"type": "fear", "chance": 20, "duration": 2, "power": 2, "name": "Furcht"})
# Sometimes random poison for variety
# Acid
if any(w in mt for w in ["schleim", "säure", "gallert", "ozy", "schlam"]):
self.inflicts.append({"type": "acid", "chance": 30, "duration": 3, "power": max(1, level // 2), "name": "Verätzung"})
# Necrotic
if any(w in mt for w in ["untot", "skelett", "wiederg", "ghoul", "leiche", "necro"]):
self.inflicts.append({"type": "necrotic", "chance": 20, "duration": 2, "power": max(1, level // 2), "name": "Nekrotische Aura"})
# Blinded
if any(w in mt for w in ["medusa", "basilisk", "blitz", "licht", "sonne"]):
self.inflicts.append({"type": "blinded", "chance": 25, "duration": 2, "power": 1, "name": "Blendender Blick"})
# Stunned
if any(w in mt for w in ["ogre", "ries", "troll", "donner", "schlag"]):
self.inflicts.append({"type": "stunned", "chance": 10, "duration": 1, "power": 1, "name": "Betäubender Schlag"})
# Slow
if any(w in mt for w in ["eis", "frost", "kälte", "winter"]):
self.inflicts.append({"type": "slow", "chance": 25, "duration": 3, "power": 2, "name": "Verlangsamende Kälte"})
# Exhaustion
if any(w in mt for w in ["wüste", "sand", "hitz", "phönix"]):
self.inflicts.append({"type": "exhaustion", "chance": 15, "duration": 3, "power": 2, "name": "Erschöpfung"})
# Random poison for variety
if not self.inflicts and random.randint(1, 4) == 1:
self.inflicts.append({"type": "poison", "chance": 15, "duration": 2, "power": 1, "name": "Gift"})
@@ -296,26 +319,25 @@ class GameEngine:
self.dice_log.add(f"[bold red]💀 Status-Effekt tötet dich![/]")
return {"result": "death", "rounds": rounds, "monster": monster.name}
# Use cleanse potion if badly afflicted
if self.character.has_effect("poison") and self.character.potions.get("healing", 0) > 0 and self.character.hp < self.character.max_hp * 0.5:
# Use healing potion which also cleanses (simplified)
pass # potion use happens in _use_potions_if_needed before combat
# Check immobilization
if self.character.is_immobilized():
self.dice_log.add(f"[yellow]💫 Du bist gelähmt/betäubt — kannst nicht angreifen![/]")
else:
# Caster tries spell first (50% chance per round)
if rounds == 1 and self.character.max_mp > 0 and random.random() < 0.5:
if self._cast_spell_if_possible(monster):
if not monster.is_alive:
self.dice_log.add(f"[bold green]☠️ {monster.name} durch Zauber besiegt![/]")
break
# Caster tries spell first (50% chance per round)
if rounds == 1 and self.character.max_mp > 0 and random.random() < 0.5:
if self._cast_spell_if_possible(monster):
# Player attacks
result = self._player_attack(monster)
if result["hit"]:
monster.hp = max(0, monster.hp - result["damage"])
if not monster.is_alive:
self.dice_log.add(f"[bold green]☠️ {monster.name} durch Zauber besiegt![/]")
self.dice_log.add(f"[bold green]☠️ {monster.name} besiegt![/]")
break
# Player attacks
result = self._player_attack(monster)
if result["hit"]:
monster.hp = max(0, monster.hp - result["damage"])
if not monster.is_alive:
self.dice_log.add(f"[bold green]☠️ {monster.name} besiegt![/]")
break
# Monster attacks
if monster.is_alive:
m_result = monster.attack(self.character.effective_ac, self.dice_log.entries)
@@ -518,6 +540,17 @@ class GameEngine:
self._add_loot_log(f"[green]🧪 Kaufe Gegengift (-{antidote_price}G)[/]")
bought_something = True
# 8. Bandages (Verbände) — heal + stop bleeding
bandage_price = 15
max_bandages = 5
while c.potions.get("bandage", 0) < max_bandages and c.gold - bandage_price >= gold_reserve:
c.gold -= bandage_price
c.potions["bandage"] = c.potions.get("bandage", 0) + 1
c.gold_spent += bandage_price
c.items_bought += 1
self._add_loot_log(f"[green]🩹 Kaufe Verband (-{bandage_price}G)[/]")
bought_something = True
if bought_something:
self._add_log(f"[yellow]🏪 Shop besucht — Ausrüstung und Tränke gekauft[/]")
self.dice_log.add(f"[yellow]🏪 Shop: {c.items_bought} Items, Rest-Gold: {c.gold}G[/]")
@@ -534,6 +567,25 @@ class GameEngine:
self.dice_log.add(f"[green]🧪 Gegengift benutzt! {removed} Effekte entfernt.[/]")
self._add_log(f"[green]🧪 Gegengift benutzt — Vergiftung geheilt![/]")
# Bandage if bleeding or burning
if c.has_effect("bleed") and c.potions.get("bandage", 0) > 0:
heal = c.apply_bandage()
c.potions["bandage"] -= 1
c.potions_used += 1
# Remove bleed
c.status_effects = [e for e in c.status_effects if e["type"] != "bleed"]
self.dice_log.add(f"[green]🩹 Verband benutzt! Blutung gestoppt, +{heal} HP[/]")
self._add_log(f"[green]🩹 Verband — Blutung gestoppt (+{heal} HP)[/]")
# Bandage also helps with burn (smother flames)
if c.has_effect("burn") and c.potions.get("bandage", 0) > 0 and not c.has_effect("bleed"):
heal = c.apply_bandage()
c.potions["bandage"] -= 1
c.potions_used += 1
c.status_effects = [e for e in c.status_effects if e["type"] != "burn"]
self.dice_log.add(f"[green]🩹 Verband löscht Feuer! +{heal} HP[/]")
self._add_log(f"[green]🩹 Verband — Brennen gelöscht (+{heal} HP)[/]")
# Antidote also removes curses if desperate
if c.has_effect("curse_weakness") and c.potions.get("antidote", 0) > 0 and c.hp < c.max_hp * 0.3:
removed = c.cleanse()
+3 -1
View File
@@ -109,7 +109,9 @@ def render_game(engine: GameEngine) -> Layout:
# Potions
pot_str = f"❤️{c.potions.get('healing',0)} " \
f"{c.potions.get('mana',0)} " \
f"💖{c.potions.get('greater_healing',0)}"
f"💖{c.potions.get('greater_healing',0)} " \
f"🌿{c.potions.get('antidote',0)} " \
f"🩹{c.potions.get('bandage',0)}"
char_table.add_row("Tränke", pot_str)
# Status effects