Files
Doom-Matrix/doom_matrix/world/generator.py
T
fedora_openclaw 46116f7f4b Initial commit: DOOM x MATRIX System Failure text adventure
Modulares Python-Textadventure mit Kampfsystem (Stagger, Bullet Time,
Glory Kills, Hacking-Skill-Checks), Hub-Raum-Navigation, prozeduralen
Encountern, 3 Charakterklassen, mehreren Enden und Story-Elementen
(weißer Hase, Orakel, schwarze-Katze-Deja-vu-Glitch).
2026-07-05 18:56:44 +02:00

118 lines
4.1 KiB
Python

#!/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