v2.2 — Status-Effekte + Log-Scrolling
Status-Effekte (D&D 5e): - Gift (🐍): Spinne/Naga/Schlange — Schaden pro Runde - Brennen (🔥): Drache/Feuerdämon — Feuerschaden pro Runde - Blutung (🩸): Vampir/Todes — Schaden pro Runde - Schwächungsfluch (💜): Hexe/Fluch — -2 ATK/AC - Furcht (😱): Geist/Schrecken — verhindert Angriff - Regeneration (💚): Heilung über Zeit - Buff/Debuff wirken sich auf effective_ac/effective_attack aus Entgiftung: - Gegengift-Trank im Shop (25G) — entfernt Gift/Fluch - Auto-Nutzung bei Vergiftung - Lange Rast reinigt auch alle Effekte Log-Scrolling: - Event-Log: chronologisch (neueste unten), 50 Einträge - Loot-Log: chronologisch, 50 Einträge - Dice-Log: chronologisch, 30 Einträge - UI zeigt letzte 20/15/12 Einträge — Scroll-Effekt
This commit is contained in:
+102
@@ -148,6 +148,9 @@ class DnDCharacter:
|
|||||||
self.max_mp = self._calc_max_mp()
|
self.max_mp = self._calc_max_mp()
|
||||||
self.mp = self.max_mp
|
self.mp = self.max_mp
|
||||||
|
|
||||||
|
# Status effects
|
||||||
|
self.status_effects = [] # list of {type, duration, power, name}
|
||||||
|
|
||||||
# Stats tracking
|
# Stats tracking
|
||||||
self.kills = 0
|
self.kills = 0
|
||||||
self.deaths = 0
|
self.deaths = 0
|
||||||
@@ -245,6 +248,103 @@ class DnDCharacter:
|
|||||||
self.mp -= cost
|
self.mp -= cost
|
||||||
self.spell_slots -= 1
|
self.spell_slots -= 1
|
||||||
|
|
||||||
|
# === Status Effects ===
|
||||||
|
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]
|
||||||
|
self.status_effects.append({
|
||||||
|
"type": effect_type, "duration": duration, "power": power, "name": name
|
||||||
|
})
|
||||||
|
|
||||||
|
def has_effect(self, effect_type: str) -> bool:
|
||||||
|
return any(e["type"] == effect_type for e in self.status_effects)
|
||||||
|
|
||||||
|
def get_effect(self, effect_type: str):
|
||||||
|
for e in self.status_effects:
|
||||||
|
if e["type"] == effect_type:
|
||||||
|
return e
|
||||||
|
return None
|
||||||
|
|
||||||
|
def tick_effects(self) -> list:
|
||||||
|
"""Tickt alle Effekte, gibt Liste der abgelaufenen und gewirkten zurück."""
|
||||||
|
results = []
|
||||||
|
for effect in self.status_effects[:]:
|
||||||
|
effect["duration"] -= 1
|
||||||
|
|
||||||
|
# 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 effect["duration"] <= 0:
|
||||||
|
self.status_effects.remove(effect)
|
||||||
|
results.append(f"[dim]✨ {effect['name']} ist abgeklungen.[/]")
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
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:
|
||||||
|
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
|
||||||
|
|
||||||
|
@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
|
||||||
|
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"]
|
||||||
|
return max(0, bonus)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_summary(self) -> str:
|
||||||
|
"""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"], "❓")
|
||||||
|
parts.append(f"{icon}{e['duration']}")
|
||||||
|
return " ".join(parts)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attack_bonus(self) -> int:
|
def attack_bonus(self) -> int:
|
||||||
"""Attack roll modifier: STR/DEX mod + proficiency."""
|
"""Attack roll modifier: STR/DEX mod + proficiency."""
|
||||||
@@ -331,6 +431,7 @@ class DnDCharacter:
|
|||||||
"inventory": [i.to_dict() for i in self.inventory],
|
"inventory": [i.to_dict() for i in self.inventory],
|
||||||
"gold": self.gold,
|
"gold": self.gold,
|
||||||
"potions": self.potions, "potion_threshold": self.potion_threshold,
|
"potions": self.potions, "potion_threshold": self.potion_threshold,
|
||||||
|
"status_effects": self.status_effects,
|
||||||
"kills": self.kills, "deaths": self.deaths, "quests_done": self.quests_done,
|
"kills": self.kills, "deaths": self.deaths, "quests_done": self.quests_done,
|
||||||
"steps": self.steps, "crit_hits": self.crit_hits, "crit_misses": self.crit_misses, "nat_20s": self.nat_20s,
|
"steps": self.steps, "crit_hits": self.crit_hits, "crit_misses": self.crit_misses, "nat_20s": self.nat_20s,
|
||||||
"potions_used": self.potions_used, "gold_spent": self.gold_spent, "items_bought": self.items_bought,
|
"potions_used": self.potions_used, "gold_spent": self.gold_spent, "items_bought": self.items_bought,
|
||||||
@@ -352,6 +453,7 @@ class DnDCharacter:
|
|||||||
c.gold = d["gold"]
|
c.gold = d["gold"]
|
||||||
c.potions = d.get("potions", {"healing": 2, "mana": 0, "greater_healing": 0, "strength": 0})
|
c.potions = d.get("potions", {"healing": 2, "mana": 0, "greater_healing": 0, "strength": 0})
|
||||||
c.potion_threshold = d.get("potion_threshold", 0.4)
|
c.potion_threshold = d.get("potion_threshold", 0.4)
|
||||||
|
c.status_effects = d.get("status_effects", [])
|
||||||
c.kills = d["kills"]; c.deaths = d["deaths"]; c.quests_done = d["quests_done"]
|
c.kills = d["kills"]; c.deaths = d["deaths"]; c.quests_done = d["quests_done"]
|
||||||
c.steps = d["steps"]; c.crit_hits = d["crit_hits"]; c.crit_misses = d["crit_misses"]; c.nat_20s = d["nat_20s"]
|
c.steps = d["steps"]; c.crit_hits = d["crit_hits"]; c.crit_misses = d["crit_misses"]; c.nat_20s = d["nat_20s"]
|
||||||
c.potions_used = d.get("potions_used", 0); c.gold_spent = d.get("gold_spent", 0); c.items_bought = d.get("items_bought", 0)
|
c.potions_used = d.get("potions_used", 0); c.gold_spent = d.get("gold_spent", 0); c.items_bought = d.get("items_bought", 0)
|
||||||
|
|||||||
+92
-20
@@ -65,6 +65,23 @@ class DnDMonster:
|
|||||||
self.damage_dice = random.choice(["1d6", "1d8", "1d10", "2d6", "1d12"])
|
self.damage_dice = random.choice(["1d6", "1d8", "1d10", "2d6", "1d12"])
|
||||||
self.damage_mod = max(0, level // 3)
|
self.damage_mod = max(0, level // 3)
|
||||||
|
|
||||||
|
# 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"]):
|
||||||
|
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"]):
|
||||||
|
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"]):
|
||||||
|
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"]):
|
||||||
|
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"]):
|
||||||
|
self.inflicts.append({"type": "fear", "chance": 20, "duration": 2, "power": 2, "name": "Furcht"})
|
||||||
|
# Sometimes 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"})
|
||||||
|
|
||||||
# XP and Gold (D&D 5e CR table, simplified)
|
# XP and Gold (D&D 5e CR table, simplified)
|
||||||
cr_xp = {0.25: 50, 0.5: 100, 1: 200, 2: 450, 3: 700, 4: 1100, 5: 1800,
|
cr_xp = {0.25: 50, 0.5: 100, 1: 200, 2: 450, 3: 700, 4: 1100, 5: 1800,
|
||||||
6: 2300, 7: 2900, 8: 3900, 9: 5000, 10: 5900}
|
6: 2300, 7: 2900, 8: 3900, 9: 5000, 10: 5900}
|
||||||
@@ -80,7 +97,7 @@ class DnDMonster:
|
|||||||
return self.hp > 0
|
return self.hp > 0
|
||||||
|
|
||||||
def attack(self, target_ac: int, dice_log: list) -> dict:
|
def attack(self, target_ac: int, dice_log: list) -> dict:
|
||||||
"""Monster greift an mit d20."""
|
"""Monster greift an mit d20, kann Status-Effekte zufügen."""
|
||||||
atk_roll = roll_d20(self.attack_mod)
|
atk_roll = roll_d20(self.attack_mod)
|
||||||
dice_log.append(f"[red]🎲 {self.name} greift an: d20{atk_roll['rolls']}{'+' if self.attack_mod >= 0 else ''}{self.attack_mod} = {atk_roll['total']} vs AC {target_ac}[/]")
|
dice_log.append(f"[red]🎲 {self.name} greift an: d20{atk_roll['rolls']}{'+' if self.attack_mod >= 0 else ''}{self.attack_mod} = {atk_roll['total']} vs AC {target_ac}[/]")
|
||||||
|
|
||||||
@@ -90,11 +107,11 @@ class DnDMonster:
|
|||||||
|
|
||||||
if fumble:
|
if fumble:
|
||||||
dice_log.append(f"[red]💀 Kritischer Fehlschlag! {self.name} stolpert![/]")
|
dice_log.append(f"[red]💀 Kritischer Fehlschlag! {self.name} stolpert![/]")
|
||||||
return {"hit": False, "damage": 0, "crit": False, "fumble": True}
|
return {"hit": False, "damage": 0, "crit": False, "fumble": True, "effect": None}
|
||||||
|
|
||||||
if not hit:
|
if not hit:
|
||||||
dice_log.append(f"[yellow]💨 {self.name} verfehlt! (AC {target_ac})[/]")
|
dice_log.append(f"[yellow]💨 {self.name} verfehlt! (AC {target_ac})[/]")
|
||||||
return {"hit": False, "damage": 0, "crit": False, "fumble": False}
|
return {"hit": False, "damage": 0, "crit": False, "fumble": False, "effect": None}
|
||||||
|
|
||||||
# Damage roll
|
# Damage roll
|
||||||
dice_str = self.damage_dice
|
dice_str = self.damage_dice
|
||||||
@@ -102,7 +119,6 @@ class DnDMonster:
|
|||||||
dmg_roll = roll(sides, count, self.damage_mod)
|
dmg_roll = roll(sides, count, self.damage_mod)
|
||||||
|
|
||||||
if crit:
|
if crit:
|
||||||
# Double dice on crit
|
|
||||||
extra = roll(sides, count, 0)
|
extra = roll(sides, count, 0)
|
||||||
total_dmg = dmg_roll["total"] + extra["total"] - self.damage_mod
|
total_dmg = dmg_roll["total"] + extra["total"] - self.damage_mod
|
||||||
dice_log.append(f"[bold red]💥 KRITISCH! {dice_str}+{self.damage_mod} = {dmg_roll['total']} + {extra['total']} (crit) = {total_dmg} Schaden![/]")
|
dice_log.append(f"[bold red]💥 KRITISCH! {dice_str}+{self.damage_mod} = {dmg_roll['total']} + {extra['total']} (crit) = {total_dmg} Schaden![/]")
|
||||||
@@ -110,19 +126,27 @@ class DnDMonster:
|
|||||||
total_dmg = dmg_roll["total"]
|
total_dmg = dmg_roll["total"]
|
||||||
dice_log.append(f"[red]⚔️ {self.name} trifft: {dice_str}+{self.damage_mod} = {total_dmg} Schaden[/]")
|
dice_log.append(f"[red]⚔️ {self.name} trifft: {dice_str}+{self.damage_mod} = {total_dmg} Schaden[/]")
|
||||||
|
|
||||||
return {"hit": True, "damage": total_dmg, "crit": crit, "fumble": False}
|
# Check for status effect infliction
|
||||||
|
effect_inflicted = None
|
||||||
|
for inf in self.inflicts:
|
||||||
|
if random.randint(1, 100) <= inf["chance"]:
|
||||||
|
effect_inflicted = inf
|
||||||
|
dice_log.append(f"[yellow]⚠️ {self.name} wirkt {inf['name']}! ({inf['type']}, {inf['duration']} Runden)[/]")
|
||||||
|
break
|
||||||
|
|
||||||
|
return {"hit": True, "damage": total_dmg, "crit": crit, "fumble": False, "effect": effect_inflicted}
|
||||||
|
|
||||||
|
|
||||||
class DiceLog:
|
class DiceLog:
|
||||||
"""Verwaltet den Würfel-Log für die UI."""
|
"""Verwaltet den Würfel-Log für die UI."""
|
||||||
def __init__(self, max_entries: int = 25):
|
def __init__(self, max_entries: int = 30):
|
||||||
self.entries = []
|
self.entries = []
|
||||||
self.max_entries = max_entries
|
self.max_entries = max_entries
|
||||||
|
|
||||||
def add(self, msg: str):
|
def add(self, msg: str):
|
||||||
self.entries.insert(0, msg)
|
self.entries.append(msg)
|
||||||
if len(self.entries) > self.max_entries:
|
if len(self.entries) > self.max_entries:
|
||||||
self.entries = self.entries[:self.max_entries]
|
self.entries = self.entries[-self.max_entries:]
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.entries = []
|
self.entries = []
|
||||||
@@ -136,9 +160,11 @@ class GameEngine:
|
|||||||
self.character = character
|
self.character = character
|
||||||
self.dice_log = DiceLog(max_entries=25)
|
self.dice_log = DiceLog(max_entries=25)
|
||||||
self.event_log = []
|
self.event_log = []
|
||||||
self.max_event_log = 20
|
self.max_event_log = 50 # increased for scrolling
|
||||||
|
self.event_scroll = 0 # 0 = newest at bottom, scroll up to see older
|
||||||
self.loot_log = []
|
self.loot_log = []
|
||||||
self.max_loot_log = 20
|
self.max_loot_log = 50
|
||||||
|
self.loot_scroll = 0
|
||||||
|
|
||||||
self.current_location = random.choice(FALLBACK_LOCATIONS)
|
self.current_location = random.choice(FALLBACK_LOCATIONS)
|
||||||
self.current_quest = random.choice(FALLBACK_QUESTS)
|
self.current_quest = random.choice(FALLBACK_QUESTS)
|
||||||
@@ -162,14 +188,14 @@ class GameEngine:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def _add_log(self, msg: str):
|
def _add_log(self, msg: str):
|
||||||
self.event_log.insert(0, msg)
|
self.event_log.append(msg)
|
||||||
if len(self.event_log) > self.max_event_log:
|
if len(self.event_log) > self.max_event_log:
|
||||||
self.event_log = self.event_log[:self.max_event_log]
|
self.event_log = self.event_log[-self.max_event_log:]
|
||||||
|
|
||||||
def _add_loot_log(self, msg: str):
|
def _add_loot_log(self, msg: str):
|
||||||
self.loot_log.insert(0, msg)
|
self.loot_log.append(msg)
|
||||||
if len(self.loot_log) > self.max_loot_log:
|
if len(self.loot_log) > self.max_loot_log:
|
||||||
self.loot_log = self.loot_log[:self.max_loot_log]
|
self.loot_log = self.loot_log[-self.max_loot_log:]
|
||||||
|
|
||||||
def _spawn_monster(self) -> DnDMonster:
|
def _spawn_monster(self) -> DnDMonster:
|
||||||
name = random.choice(FALLBACK_MONSTERS)
|
name = random.choice(FALLBACK_MONSTERS)
|
||||||
@@ -254,13 +280,27 @@ class GameEngine:
|
|||||||
return {"hit": True, "damage": total_dmg, "crit": crit, "fumble": False}
|
return {"hit": True, "damage": total_dmg, "crit": crit, "fumble": False}
|
||||||
|
|
||||||
def _combat(self, monster: DnDMonster) -> dict:
|
def _combat(self, monster: DnDMonster) -> dict:
|
||||||
"""D&D 5e Combat: abwechselnde Runden."""
|
"""D&D 5e Combat: abwechselnde Runden mit Status-Effekten."""
|
||||||
rounds = 0
|
rounds = 0
|
||||||
self.dice_log.add(f"[bold]━━━ Kampf: {self.character.name} vs {monster.name} (AC {monster.ac}, HP {monster.hp}) ━━━[/]")
|
self.dice_log.add(f"[bold]━━━ Kampf: {self.character.name} vs {monster.name} (AC {monster.ac}, HP {monster.hp}) ━━━[/]")
|
||||||
|
|
||||||
while monster.is_alive and self.character.is_alive:
|
while monster.is_alive and self.character.is_alive:
|
||||||
rounds += 1
|
rounds += 1
|
||||||
|
|
||||||
|
# Tick status effects at start of round
|
||||||
|
if self.character.status_effects:
|
||||||
|
effect_results = self.character.tick_effects()
|
||||||
|
for er in effect_results:
|
||||||
|
self.dice_log.add(er)
|
||||||
|
if not self.character.is_alive:
|
||||||
|
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
|
||||||
|
|
||||||
# Caster tries spell first (50% chance per round)
|
# Caster tries spell first (50% chance per round)
|
||||||
if rounds == 1 and self.character.max_mp > 0 and random.random() < 0.5:
|
if rounds == 1 and self.character.max_mp > 0 and random.random() < 0.5:
|
||||||
if self._cast_spell_if_possible(monster):
|
if self._cast_spell_if_possible(monster):
|
||||||
@@ -268,7 +308,7 @@ class GameEngine:
|
|||||||
self.dice_log.add(f"[bold green]☠️ {monster.name} durch Zauber besiegt![/]")
|
self.dice_log.add(f"[bold green]☠️ {monster.name} durch Zauber besiegt![/]")
|
||||||
break
|
break
|
||||||
|
|
||||||
# Player attacks (initiative: DEX check)
|
# Player attacks
|
||||||
result = self._player_attack(monster)
|
result = self._player_attack(monster)
|
||||||
if result["hit"]:
|
if result["hit"]:
|
||||||
monster.hp = max(0, monster.hp - result["damage"])
|
monster.hp = max(0, monster.hp - result["damage"])
|
||||||
@@ -278,15 +318,20 @@ class GameEngine:
|
|||||||
|
|
||||||
# Monster attacks
|
# Monster attacks
|
||||||
if monster.is_alive:
|
if monster.is_alive:
|
||||||
m_result = monster.attack(self.character.ac, self.dice_log.entries)
|
m_result = monster.attack(self.character.effective_ac, self.dice_log.entries)
|
||||||
self.dice_log.entries = self.dice_log.entries # update ref
|
self.dice_log.entries = self.dice_log.entries
|
||||||
if m_result["hit"]:
|
if m_result["hit"]:
|
||||||
self.character.hp = max(0, self.character.hp - m_result["damage"])
|
self.character.hp = max(0, self.character.hp - m_result["damage"])
|
||||||
|
# Apply status effect
|
||||||
|
if m_result.get("effect"):
|
||||||
|
eff = m_result["effect"]
|
||||||
|
self.character.add_effect(eff["type"], eff["duration"], eff["power"], eff["name"])
|
||||||
|
self._add_log(f"[yellow]⚠️ {eff['name']} erhalten! ({eff['duration']} Runden)[/]")
|
||||||
if not self.character.is_alive:
|
if not self.character.is_alive:
|
||||||
self.dice_log.add(f"[bold red]💀 Du wurdest besiegt![/]")
|
self.dice_log.add(f"[bold red]💀 Du wurdest besiegt![/]")
|
||||||
return {"result": "death", "rounds": rounds, "monster": monster.name}
|
return {"result": "death", "rounds": rounds, "monster": monster.name}
|
||||||
|
|
||||||
self.dice_log.add(f"[dim]── Runde {rounds} ── HP: {self.character.hp}/{self.character.max_hp} | Monster: {monster.hp}/{monster.max_hp} ──[/]")
|
self.dice_log.add(f"[dim]── Runde {rounds} ── HP: {self.character.hp}/{self.character.max_hp} | Monster: {monster.hp}/{monster.max_hp} | Status: {self.character.status_summary} ──[/]")
|
||||||
|
|
||||||
return {"result": "victory", "rounds": rounds, "monster": monster.name}
|
return {"result": "victory", "rounds": rounds, "monster": monster.name}
|
||||||
|
|
||||||
@@ -462,14 +507,41 @@ class GameEngine:
|
|||||||
self._add_loot_log(f"[blue]🧪 Kaufe Manatrank (-{mana_price}G)[/]")
|
self._add_loot_log(f"[blue]🧪 Kaufe Manatrank (-{mana_price}G)[/]")
|
||||||
bought_something = True
|
bought_something = True
|
||||||
|
|
||||||
|
# 7. Antidote (Entgiftung) — always useful
|
||||||
|
antidote_price = 25
|
||||||
|
max_antidotes = 3
|
||||||
|
while c.potions.get("antidote", 0) < max_antidotes and c.gold - antidote_price >= gold_reserve:
|
||||||
|
c.gold -= antidote_price
|
||||||
|
c.potions["antidote"] = c.potions.get("antidote", 0) + 1
|
||||||
|
c.gold_spent += antidote_price
|
||||||
|
c.items_bought += 1
|
||||||
|
self._add_loot_log(f"[green]🧪 Kaufe Gegengift (-{antidote_price}G)[/]")
|
||||||
|
bought_something = True
|
||||||
|
|
||||||
if bought_something:
|
if bought_something:
|
||||||
self._add_log(f"[yellow]🏪 Shop besucht — Ausrüstung und Tränke gekauft[/]")
|
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[/]")
|
self.dice_log.add(f"[yellow]🏪 Shop: {c.items_bought} Items, Rest-Gold: {c.gold}G[/]")
|
||||||
|
|
||||||
def _use_potions_if_needed(self):
|
def _use_potions_if_needed(self):
|
||||||
"""Benutzt automatisch Heiltränke wenn HP niedrig ist."""
|
"""Benutzt automatisch Tränke wenn nötig."""
|
||||||
c = self.character
|
c = self.character
|
||||||
|
|
||||||
|
# Antidote if poisoned
|
||||||
|
if c.has_effect("poison") and c.potions.get("antidote", 0) > 0:
|
||||||
|
removed = c.cleanse()
|
||||||
|
c.potions["antidote"] -= 1
|
||||||
|
c.potions_used += 1
|
||||||
|
self.dice_log.add(f"[green]🧪 Gegengift benutzt! {removed} Effekte entfernt.[/]")
|
||||||
|
self._add_log(f"[green]🧪 Gegengift benutzt — Vergiftung geheilt![/]")
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
c.potions["antidote"] -= 1
|
||||||
|
c.potions_used += 1
|
||||||
|
self.dice_log.add(f"[green]🧪 Gegengift gegen Fluch! {removed} Effekte entfernt.[/]")
|
||||||
|
self._add_log(f"[green]🧪 Fluch aufgehoben![/]")
|
||||||
|
|
||||||
# Use greater healing first if very low
|
# Use greater healing first if very low
|
||||||
if c.hp < c.max_hp * 0.2 and c.potions.get("greater_healing", 0) > 0:
|
if c.hp < c.max_hp * 0.2 and c.potions.get("greater_healing", 0) > 0:
|
||||||
heal = c.use_potion("greater_healing")
|
heal = c.use_potion("greater_healing")
|
||||||
|
|||||||
+10
-4
@@ -112,6 +112,9 @@ def render_game(engine: GameEngine) -> Layout:
|
|||||||
f"💖{c.potions.get('greater_healing',0)}"
|
f"💖{c.potions.get('greater_healing',0)}"
|
||||||
char_table.add_row("Tränke", pot_str)
|
char_table.add_row("Tränke", pot_str)
|
||||||
|
|
||||||
|
# Status effects
|
||||||
|
char_table.add_row("Status", c.status_summary)
|
||||||
|
|
||||||
# Stats row
|
# Stats row
|
||||||
stats_str = " ".join(f"{k}:{c.stats[k]}" for k in ["STR","DEX","CON","INT","WIS","CHA"])
|
stats_str = " ".join(f"{k}:{c.stats[k]}" for k in ["STR","DEX","CON","INT","WIS","CHA"])
|
||||||
char_table.add_row("Stats", stats_str)
|
char_table.add_row("Stats", stats_str)
|
||||||
@@ -134,23 +137,26 @@ def render_game(engine: GameEngine) -> Layout:
|
|||||||
quest_text.append(f"📜 {engine.current_quest}\n", style="cyan")
|
quest_text.append(f"📜 {engine.current_quest}\n", style="cyan")
|
||||||
quest_text.append(f"Fortschritt: {engine.quest_progress}/{engine.quest_target}", style="yellow")
|
quest_text.append(f"Fortschritt: {engine.quest_progress}/{engine.quest_target}", style="yellow")
|
||||||
|
|
||||||
# === Event Log ===
|
# === Event Log (scrollable — newest at bottom) ===
|
||||||
event_text = Text()
|
event_text = Text()
|
||||||
for entry in engine.event_log:
|
visible_events = engine.event_log[-20:] # show last 20
|
||||||
|
for entry in visible_events:
|
||||||
t = Text.from_markup(entry)
|
t = Text.from_markup(entry)
|
||||||
event_text.append(t)
|
event_text.append(t)
|
||||||
event_text.append("\n")
|
event_text.append("\n")
|
||||||
|
|
||||||
# === Loot Log ===
|
# === Loot Log ===
|
||||||
loot_text = Text()
|
loot_text = Text()
|
||||||
for entry in engine.loot_log:
|
visible_loot = engine.loot_log[-15:]
|
||||||
|
for entry in visible_loot:
|
||||||
t = Text.from_markup(entry)
|
t = Text.from_markup(entry)
|
||||||
loot_text.append(t)
|
loot_text.append(t)
|
||||||
loot_text.append("\n")
|
loot_text.append("\n")
|
||||||
|
|
||||||
# === Dice Log (Würfel-Fenster) ===
|
# === Dice Log (Würfel-Fenster) ===
|
||||||
dice_text = Text()
|
dice_text = Text()
|
||||||
for entry in engine.dice_log.get_all():
|
visible_dice = engine.dice_log.get_all()[-12:]
|
||||||
|
for entry in visible_dice:
|
||||||
t = Text.from_markup(entry)
|
t = Text.from_markup(entry)
|
||||||
dice_text.append(t)
|
dice_text.append(t)
|
||||||
dice_text.append("\n")
|
dice_text.append("\n")
|
||||||
|
|||||||
Reference in New Issue
Block a user