cc172d71c5
- max_log 15→30, alle Einträge werden gerendert (nicht nur 12) - Quest-Abschluss: Belohnung → Reise → Ankunft → neue Quest - 'Du machst dich auf den Weg...' → 'Ankunft in: X' → 'Neue Quest' - 2 Sekunden Pause nach Quest-Abschluss zum Lesen
247 lines
10 KiB
Python
247 lines
10 KiB
Python
"""Game-Engine: Auto-Progress, Kämpfe, Loot, Quests, Shop."""
|
|
|
|
import random
|
|
import time
|
|
from character import Character, Item, RACES, CLASSES
|
|
from llm import NameCache
|
|
|
|
class Monster:
|
|
def __init__(self, name: str, level: int, hp: int, attack: int, defense: int, xp: int, gold: int):
|
|
self.name = name
|
|
self.level = level
|
|
self.hp = hp
|
|
self.attack = attack
|
|
self.defense = defense
|
|
self.xp = xp
|
|
self.gold = gold
|
|
|
|
def is_alive(self):
|
|
return self.hp > 0
|
|
|
|
class GameEngine:
|
|
def __init__(self, character: Character):
|
|
self.character = character
|
|
self.cache = NameCache()
|
|
self.cache.refill_async()
|
|
self.current_location = self.cache.get_location()
|
|
self.current_quest = self.cache.get_quest(character.level)
|
|
self.quest_progress = 0
|
|
self.quest_target = random.randint(3, 8)
|
|
self.log = []
|
|
self.max_log = 30
|
|
|
|
def _add_log(self, msg: str):
|
|
self.log.insert(0, msg)
|
|
if len(self.log) > self.max_log:
|
|
self.log = self.log[:self.max_log]
|
|
|
|
def _spawn_monster(self) -> Monster:
|
|
name = self.cache.get_monster(self.character.level, self.current_location)
|
|
level = max(1, self.character.level + random.randint(-1, 1))
|
|
hp = 10 + level * 5 + random.randint(0, 10)
|
|
attack = level * 2 + random.randint(0, 3)
|
|
defense = level + random.randint(0, 2)
|
|
xp = 5 + level * 3 + random.randint(0, 5)
|
|
gold = 1 + level + random.randint(0, 3)
|
|
return Monster(name, level, hp, attack, defense, xp, gold)
|
|
|
|
def _generate_loot(self, monster: Monster) -> Item:
|
|
item_types = ["weapon", "armor", "helmet", "boots", "accessory", "junk"]
|
|
item_type = random.choices(item_types, weights=[15, 15, 10, 10, 8, 42])[0]
|
|
|
|
# Rarity based on luck and level
|
|
roll = random.randint(1, 100) + self.character.lck
|
|
if roll > 95:
|
|
rarity = "legendär"
|
|
elif roll > 80:
|
|
rarity = "episch"
|
|
elif roll > 60:
|
|
rarity = "selten"
|
|
else:
|
|
rarity = "gewöhnlich"
|
|
|
|
name = self.cache.get_item(item_type, self.character.level, rarity)
|
|
|
|
rarity_mult = {"gewöhnlich": 1, "selten": 1.5, "episch": 2.5, "legendär": 4}
|
|
mult = rarity_mult.get(rarity, 1)
|
|
|
|
base_power = self.character.level + random.randint(0, 3)
|
|
power = max(1, int(base_power * mult))
|
|
value = max(1, int((monster.gold + 2) * mult * random.uniform(0.8, 1.2)))
|
|
|
|
return Item(name, item_type, power, value, rarity)
|
|
|
|
def _combat(self, monster: Monster) -> dict:
|
|
"""Führt einen automatischen Kampf durch. Gibt Ergebnis-Dict zurück."""
|
|
rounds = 0
|
|
while monster.is_alive() and self.character.is_alive():
|
|
rounds += 1
|
|
# Character attacks
|
|
char_dmg = max(1, self.character.attack - monster.defense + random.randint(-2, 2))
|
|
char_dmg = min(char_dmg, monster.hp)
|
|
monster.hp -= char_dmg
|
|
|
|
if not monster.is_alive():
|
|
break
|
|
|
|
# Monster attacks
|
|
mon_dmg = max(1, monster.attack - self.character.defense + random.randint(-2, 2))
|
|
actual = self.character.take_damage(mon_dmg)
|
|
|
|
if not self.character.is_alive():
|
|
return {"result": "death", "rounds": rounds, "monster": monster.name}
|
|
|
|
return {"result": "victory", "rounds": rounds, "monster": monster.name}
|
|
|
|
def _complete_quest(self):
|
|
self.character.quests_done += 1
|
|
quest_gold = 5 + self.character.level * 3 + random.randint(0, 10)
|
|
quest_xp = 10 + self.character.level * 5 + random.randint(0, 10)
|
|
self.character.gold += quest_gold
|
|
leveled = self.character.gain_xp(quest_xp)
|
|
self._add_log(f"[bold green]✅ Quest abgeschlossen: {self.current_quest}[/]")
|
|
self._add_log(f"[dim]Belohnung: +{quest_gold} Gold, +{quest_xp} XP[/]")
|
|
if leveled:
|
|
self._add_log(f"[bold yellow]⬆️ Stufe {self.character.level} erreicht![/]")
|
|
self.character.heal()
|
|
# Travel to new location before new quest
|
|
self._add_log(f"[blue]🌍 Du machst dich auf den Weg...[/]")
|
|
self.current_location = self.cache.get_location()
|
|
self._add_log(f"[blue]🌍 Ankunft in: {self.current_location}[/]")
|
|
# New quest
|
|
self.current_quest = self.cache.get_quest(self.character.level)
|
|
self.quest_progress = 0
|
|
self.quest_target = random.randint(3, 8)
|
|
self._add_log(f"[cyan]📜 Neue Quest: {self.current_quest}[/]")
|
|
self._add_log(f"[dim]Ziel: Besiege {self.quest_target} Gegner[/]")
|
|
|
|
def step(self) -> dict:
|
|
"""Ein Schritt des Auto-Progress. Gibt Event-Info zurück."""
|
|
self.character.steps += 1
|
|
events = []
|
|
|
|
# Random: sometimes travel
|
|
if random.randint(1, 50) == 1:
|
|
self.current_location = self.cache.get_location()
|
|
events.append(("travel", f"[blue]🌍 Du reist nach: {self.current_location}[/]"))
|
|
self._add_log(events[-1][1])
|
|
|
|
# Spawn monster
|
|
monster = self._spawn_monster()
|
|
events.append(("encounter", f"[red]⚔️ {monster.name} (Stufe {monster.level}) erscheint![/]"))
|
|
self._add_log(events[-1][1])
|
|
|
|
# Fight
|
|
result = self._combat(monster)
|
|
|
|
if result["result"] == "victory":
|
|
self.character.kills += 1
|
|
events.append(("victory", f"[green]⚔️ Besiegst {monster.name} ({result['rounds']} Runden)[/]"))
|
|
self._add_log(events[-1][1])
|
|
|
|
# XP + Gold
|
|
self.character.gold += monster.gold
|
|
leveled = self.character.gain_xp(monster.xp)
|
|
events.append(("loot_xp", f"[dim]+{monster.xp} XP, +{monster.gold} Gold[/]"))
|
|
self._add_log(events[-1][1])
|
|
|
|
if leveled:
|
|
self._add_log(f"[bold yellow]⬆️ Stufe {self.character.level} erreicht![/]")
|
|
self.character.heal()
|
|
events.append(("levelup", f"[bold yellow]⬆️ STUFE {self.character.level}![/]"))
|
|
|
|
# Loot drop
|
|
if random.randint(1, 100) <= 70: # 70% drop chance
|
|
item = self._generate_loot(monster)
|
|
self._add_log(f"[magenta]📦 Loot: {item}[/]")
|
|
events.append(("loot", f"[magenta]📦 {item}[/]", item))
|
|
self._auto_equip(item)
|
|
else:
|
|
# Always some gold
|
|
extra_gold = random.randint(1, 3)
|
|
self.character.gold += extra_gold
|
|
|
|
# Quest progress
|
|
self.quest_progress += 1
|
|
if self.quest_progress >= self.quest_target:
|
|
self._complete_quest()
|
|
events.append(("quest_done", f"[green]✅ Quest erfüllt![/]", True)) # True = pause flag
|
|
|
|
elif result["result"] == "death":
|
|
self.character.deaths += 1
|
|
events.append(("death", f"[bold red]💀 Von {monster.name} besiegt! Respawn...[/]"))
|
|
self._add_log(events[-1][1])
|
|
# Respawn: lose 10% gold, heal
|
|
lost_gold = max(1, self.character.gold // 10)
|
|
self.character.gold -= lost_gold
|
|
self.character.heal()
|
|
self._add_log(f"[red]Verlierst {lost_gold} Gold beim Sterben.[/]")
|
|
|
|
return {"events": events}
|
|
|
|
def _auto_equip(self, item: Item):
|
|
"""Prüft ob Item besser ist und rüstet es automatisch aus."""
|
|
slot = None
|
|
if item.item_type in ("weapon", "armor", "helmet", "boots", "accessory"):
|
|
slot = item.item_type
|
|
|
|
if slot:
|
|
current = self.character.equipped[slot]
|
|
if current is None or item.power > current.power:
|
|
if current:
|
|
self.character.inventory.append(current)
|
|
self.character.equipped[slot] = item
|
|
self._add_log(f"[cyan]🎽 Ausgerüstet: {item}[/]")
|
|
else:
|
|
self.character.inventory.append(item)
|
|
else:
|
|
self.character.inventory.append(item)
|
|
|
|
# Auto-sell junk to keep inventory clean
|
|
if item.item_type == "junk":
|
|
self.character.gold += item.value
|
|
if item in self.character.inventory:
|
|
self.character.inventory.remove(item)
|
|
self._add_log(f"[dim]💰 Verkauft: {item.name} (+{item.value} Gold)[/]")
|
|
|
|
def _sell_junk(self):
|
|
"""Verkauft alle Schrott-Items."""
|
|
sold = 0
|
|
total_gold = 0
|
|
for item in self.character.inventory[:]:
|
|
if item.item_type == "junk":
|
|
self.character.inventory.remove(item)
|
|
self.character.gold += item.value
|
|
total_gold += item.value
|
|
sold += 1
|
|
if sold > 0:
|
|
self._add_log(f"[dim]💰 {sold} Schrott verkauft (+{total_gold} Gold)[/]")
|
|
|
|
def get_state(self) -> dict:
|
|
return {
|
|
"character": self.character.to_dict(),
|
|
"current_location": self.current_location,
|
|
"current_quest": self.current_quest,
|
|
"quest_progress": self.quest_progress,
|
|
"quest_target": self.quest_target,
|
|
"log": self.log,
|
|
}
|
|
|
|
def save(self, path: str):
|
|
import json
|
|
with open(path, "w") as f:
|
|
json.dump(self.get_state(), f, ensure_ascii=False, indent=2)
|
|
|
|
@classmethod
|
|
def load(cls, path: str) -> "GameEngine":
|
|
import json
|
|
with open(path) as f:
|
|
d = json.load(f)
|
|
char = Character.from_dict(d["character"])
|
|
engine = cls(char)
|
|
engine.current_location = d.get("current_location", engine.current_location)
|
|
engine.current_quest = d.get("current_quest", engine.current_quest)
|
|
engine.quest_progress = d.get("quest_progress", 0)
|
|
engine.quest_target = d.get("quest_target", 5)
|
|
engine.log = d.get("log", [])
|
|
return engine |