Fortschritts-Quest v1.0 — Automatisches RPG mit lokalem LLM
This commit is contained in:
+241
@@ -0,0 +1,241 @@
|
||||
"""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 = 15
|
||||
|
||||
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} (+{quest_gold} Gold, +{quest_xp} XP)[/]")
|
||||
if leveled:
|
||||
self._add_log(f"[bold yellow]⬆️ Stufe {self.character.level} erreicht![/]")
|
||||
self.character.heal()
|
||||
# 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.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![/]"))
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user