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:
arch_agent
2026-07-25 23:22:21 +02:00
parent cd69fa1ef0
commit b1aa8e3162
3 changed files with 204 additions and 24 deletions
+102
View File
@@ -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
View File
@@ -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
View File
@@ -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")