#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Prozedurale Raum- und Encounter-Generierung (seed-basiert, teilbare Runs).""" import json import os import random from doom_matrix.engine.ui import Colors, slow_print from doom_matrix.entities.enemy import Enemy from doom_matrix.entities.items import Item from doom_matrix.systems.combat import run_combat from doom_matrix.world.rooms import Room _ITEMS_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "items.json") _ITEM_REGISTRY = None def load_item_registry(): global _ITEM_REGISTRY if _ITEM_REGISTRY is None: with open(_ITEMS_PATH, "r", encoding="utf-8") as f: data = json.load(f) _ITEM_REGISTRY = {d["id"]: Item.from_dict(d) for d in data} return _ITEM_REGISTRY THEMES = { "mars": { "flavor": [ "Ein verlassenes Labor. Monitore flackern mit UAC-Logos.", "Ein Korridor voller Kratzspuren -- etwas wurde hier durch die Wand geschleift.", "Ein Lueftungsschacht pfeift roten Marsstaub durch den Raum.", "Kisten mit der Aufschrift 'ARGENT ENERGY -- NICHT OEFFNEN' stapeln sich hier.", ], "enemies": ["imp", "pinky"], }, "matrix": { "flavor": [ "Ein Buerokomplex aus reinem Code. Die Decke besteht aus Zahlenkolonnen.", "Ein U-Bahn-Gleis, das nirgendwo hinfuehrt. Der naechste Zug kommt nie.", "Ein Zimmer voller alter Kabel-Telefone -- Fluchtpunkte, die niemand mehr nutzt.", "Ein endloser Flur mit identischen Tueren. Tuer 101 wiederholt sich dreimal.", ], "enemies": ["agent_imp", "agent_clone"], }, "hell": { "flavor": [ "Ein Serverraum, in dem die Racks aus Rippen bestehen.", "Ein See aus geschmolzenem Silizium, der im Rhythmus eines Herzschlags glueht.", "Kabelstraenge wachsen wie Wurzeln aus der Wand, angeschlossen an nichts.", "Ein Altar aus Motherboards, umgeben von erkaltetem Magma.", ], "enemies": ["cacodemon", "revenant"], }, } LOOT_TABLE = ["medkit", "armor_shard", "ammo_pack", "praetor_token", "logfile"] def _room_id(theme, index): return f"{theme}_gen_{index}" def _encounter_on_enter(theme, rng): def _on_enter(player, ctx): enemy_id = rng.choice(THEMES[theme]["enemies"]) enemy = Enemy.create(enemy_id) slow_print(f"\n{Colors.RED}Ein {enemy.name} versperrt dir den Weg!{Colors.END}") victory = run_combat(player, [enemy], title=f"KAMPF: {enemy.name}", rng=rng) if not victory: return ("leave", "game_over") return None return _on_enter def _loot_on_enter(theme, rng, item_registry): def _on_enter(player, ctx): if rng.random() < 0.7: item_id = rng.choice(LOOT_TABLE) item = item_registry.get(item_id) if item: player.inventory.add(item) slow_print(f"{Colors.GREEN}Du findest: {item.name}{Colors.END}") else: slow_print(f"{Colors.DIM}Nichts von Wert hier. Nur Staub und Stille.{Colors.END}") return None return _on_enter def generate_filler_rooms(theme, count, rng, seed_offset=0): """Erzeugt eine Kette prozeduraler Raeume fuer einen Layer (linear verkettet, zum Einhaengen zwischen festen Story-Raeumen).""" item_registry = load_item_registry() rooms = [] for i in range(count): idx = i + seed_offset room_id = _room_id(theme, idx) flavor = rng.choice(THEMES[theme]["flavor"]) is_encounter = rng.random() < 0.55 on_enter = _encounter_on_enter(theme, rng) if is_encounter else _loot_on_enter(theme, rng, item_registry) room = Room( room_id=room_id, name=f"Generierter Raum {idx + 1}", description=flavor, on_enter=on_enter, ) rooms.append(room) return rooms def chain_rooms(rooms, entry_label="Weiter"): """Verkettet eine Liste von Raeumen linear ueber 'exits'.""" for i, room in enumerate(rooms): if i + 1 < len(rooms): room.exits[entry_label] = rooms[i + 1].room_id return rooms