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).
This commit is contained in:
commit
46116f7f4b
25 files changed
+2594
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Mehrere Enden je nach Corruption, Entscheidungen und Layer-0-Status."""
|
||||
|
||||
from doom_matrix.engine.ui import Colors, slow_print, dramatic_pause, clear_screen, menu
|
||||
from doom_matrix.world.layers import layer_zero_unlocked
|
||||
|
||||
|
||||
def determine_ending(player):
|
||||
if layer_zero_unlocked(player) and player.flags.get("reached_layer_zero"):
|
||||
return "wake_up"
|
||||
if player.corruption >= 60:
|
||||
return "dark_pact"
|
||||
if player.flags.get("final_choice") == "3":
|
||||
return "new_architect"
|
||||
if player.flags.get("final_choice") == "4":
|
||||
return "new_game_plus"
|
||||
return "rip_and_tear"
|
||||
|
||||
|
||||
def _stats_block(player):
|
||||
slow_print(f"\n{Colors.CYAN}Statistiken:{Colors.END}")
|
||||
print(f" Health übrig: {player.health}/{player.max_health}")
|
||||
print(f" Corruption: {int(player.corruption)}%")
|
||||
print(f" Glory Kills: {player.glory_kills}")
|
||||
print(f" Hacks: {player.hacks}")
|
||||
print(f" Klasse: {player.class_name}")
|
||||
print(f" Waffen: {', '.join(w.name for w in player.weapons)}")
|
||||
print(f" Crucible-Fragmente: {player.inventory.crucible_fragments}/3")
|
||||
if player.flags.get("cheats_used"):
|
||||
print(f" Benutzte Cheats: {', '.join(player.flags['cheats_used'])}")
|
||||
|
||||
|
||||
def _banner(text, color=Colors.GREEN):
|
||||
width = max(len(text) + 8, 55)
|
||||
print(f"""
|
||||
{color}{Colors.BOLD}
|
||||
╔{'═' * (width - 4)}╗
|
||||
║{text.center(width - 4)}║
|
||||
╚{'═' * (width - 4)}╝
|
||||
{Colors.END}
|
||||
""")
|
||||
|
||||
|
||||
def ending(player):
|
||||
dramatic_pause(1)
|
||||
clear_screen()
|
||||
|
||||
kind = determine_ending(player)
|
||||
handlers = {
|
||||
"wake_up": _ending_wake_up,
|
||||
"dark_pact": _ending_dark_pact,
|
||||
"new_architect": _ending_new_architect,
|
||||
"new_game_plus": _ending_new_game_plus,
|
||||
"rip_and_tear": _ending_rip_and_tear,
|
||||
}
|
||||
handlers[kind](player)
|
||||
_stats_block(player)
|
||||
|
||||
dramatic_pause(1)
|
||||
slow_print(f"\n{Colors.GREEN}Danke fürs Spielen! 🐇{Colors.END}")
|
||||
slow_print(f"{Colors.DIM}Erstellt von LXC für Andy{Colors.END}")
|
||||
return kind
|
||||
|
||||
|
||||
def _ending_rip_and_tear(player):
|
||||
_banner("SYSTEM FAILURE - CLEAR", Colors.GREEN)
|
||||
slow_print(f"{Colors.YELLOW}Trinity VEGA:{Colors.END} {Colors.CYAN}'Wir haben es geschafft. Die Welt ist frei.'{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Morpheus Hayden:{Colors.END} {Colors.CYAN}'Aber der Krieg... der Krieg ist nie vorbei.'{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Du:{Colors.END} {Colors.CYAN}'Gut. Dann gibt's noch mehr zu rip and tear.'{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
_banner("NEW GAME+ UNLOCKED", Colors.MAGENTA)
|
||||
|
||||
|
||||
def _ending_new_architect(player):
|
||||
_banner("DER NEUE ARCHITEKT", Colors.CYAN)
|
||||
slow_print(f"{Colors.WHITE}Du setzt dich in den leeren Thron aus Code, den der Archi-Lord hinterlassen hat.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Von hier aus siehst du jede Zeile der Matrix. Jede Entscheidung. Jeden Bug.{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Morpheus Hayden:{Colors.END} {Colors.CYAN}'Und jetzt? Wirst du besser sein als er?'{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Du antwortest nicht sofort. Macht macht keine Antworten leichter.{Colors.END}")
|
||||
slow_print(f"{Colors.GREEN}'Ich werde es zumindest versuchen.'{Colors.END}")
|
||||
|
||||
|
||||
def _ending_dark_pact(player):
|
||||
_banner("DER DUNKLE PAKT", Colors.RED)
|
||||
slow_print(f"{Colors.RED}Die Corruption in dir hat laengst gewonnen, bevor der letzte Schuss fiel.{Colors.END}")
|
||||
slow_print(f"{Colors.RED}Der Archi-Lord zerfaellt nicht in Nichts -- er zerfaellt in DICH.{Colors.END}")
|
||||
slow_print(f"{Colors.RED}Seine Macht, sein Hunger, seine Krone aus Servern und Schwefel.{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
slow_print(f"{Colors.YELLOW}Trinity VEGA weicht zurueck.{Colors.END} {Colors.CYAN}'...Slayer?'{Colors.END}")
|
||||
slow_print(f"{Colors.RED}{Colors.BOLD}Du:{Colors.END} {Colors.RED}'Der Slayer ist tot. Es gibt nur noch den Dark Lord.'{Colors.END}")
|
||||
slow_print(f"{Colors.DIM}[GEHEIMES ENDE FREIGESCHALTET: DARK LORD]{Colors.END}")
|
||||
|
||||
|
||||
def _ending_new_game_plus(player):
|
||||
_banner("...MIT CHEATS", Colors.YELLOW)
|
||||
slow_print(f"{Colors.GREEN}Archi-Lord:{Colors.END} {Colors.WHITE}'Sie haben tatsaechlich um einen neuen Spielstand gebeten.'{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Die Welt um dich herum zerfaellt -- nicht in Chaos, sondern in ein Ladebildschirm.{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Du:{Colors.END} {Colors.CYAN}'IDDQD. IDKFA. Und diesmal behalte ich beides.'{Colors.END}")
|
||||
|
||||
|
||||
def _ending_wake_up(player):
|
||||
_banner("WAKE UP", Colors.WHITE)
|
||||
slow_print(f"{Colors.WHITE}Es gibt kein Morpheus, kein Orakel, keinen Archi-Lord mehr in diesem Raum.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Nur ein Cursor, der blinkt. Und die Erinnerung an einen weissen Hasen.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Du bist nicht aufgewacht IN der Matrix. Du bist aufgewacht AUS der Geschichte.{Colors.END}")
|
||||
dramatic_pause(1.5)
|
||||
slow_print(f"{Colors.DIM}[WAHRES ENDE FREIGESCHALTET: du folgtest dem Hasen UND fandest alle drei Fragmente]{Colors.END}")
|
||||
slow_print(f"{Colors.GREEN}Und irgendwo, in einem Terminal, das wirklich existiert, endet der Text.{Colors.END}")
|
||||
|
||||
|
||||
def game_over(player):
|
||||
clear_screen()
|
||||
_banner("GAME OVER", Colors.RED)
|
||||
|
||||
slow_print(f"\n{Colors.RED}Du bist gefallen...{Colors.END}")
|
||||
slow_print(f"{Colors.DIM}Aber in der Matrix ist Tod nur ein Bug.{Colors.END}")
|
||||
|
||||
choice = menu([
|
||||
("1", "Die blaue Pille schlucken (aufgeben)"),
|
||||
("2", "Neu laden (Programm beenden)"),
|
||||
], "Was tust du")
|
||||
|
||||
if choice == "1":
|
||||
clear_screen()
|
||||
_banner("BLUEPILL ENDING", Colors.CYAN)
|
||||
slow_print(f"{Colors.WHITE}Du wachst in einem Buero auf. Fluoreszierendes Licht. Ein Kaffeebecher dampft.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Ein Kollege fragt, ob du das Quartalsmeeting vorbereitet hast.{Colors.END}")
|
||||
slow_print(f"{Colors.DIM}Irgendwo, ganz leise, hoerst du ein Kaninchen husten.{Colors.END}")
|
||||
slow_print(f"\n{Colors.GREEN}Danke fürs Spielen! 🐇{Colors.END}")
|
||||
return "bluepill_ending"
|
||||
|
||||
slow_print(f"\n{Colors.DIM}Ein Patch könnte helfen.{Colors.END}")
|
||||
return "game_over_final"
|
||||
@@ -0,0 +1,117 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,614 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Story-Layer: Construct, weisser Hase, Mars Base, Matrix Deep (Orakel, schwarze
|
||||
Katze), Hell Server, Archi-Lord, Layer 0."""
|
||||
|
||||
import os
|
||||
import random
|
||||
|
||||
from doom_matrix.engine.ui import (
|
||||
Colors, slow_print, dramatic_pause, clear_screen, menu, section_title, glitch_text
|
||||
)
|
||||
from doom_matrix.entities.enemy import Enemy
|
||||
from doom_matrix.systems import hacking
|
||||
from doom_matrix.systems.combat import run_combat
|
||||
from doom_matrix.world.rooms import Room, Hub
|
||||
from doom_matrix.world.generator import generate_filler_rooms, chain_rooms, load_item_registry
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# CONSTRUCT / INTRO
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def construct_intro(player, rng=None):
|
||||
clear_screen()
|
||||
slow_print(f"{Colors.GREEN}Du wachst auf...{Colors.END}", 0.05)
|
||||
dramatic_pause(1)
|
||||
|
||||
slow_print(f"\n{Colors.WHITE}Um dich herum: Grüner Code. Endlose Zeilen davon.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Du kennst diesen Ort. Das Construct.{Colors.END}")
|
||||
dramatic_pause(0.5)
|
||||
|
||||
slow_print(f"\n{Colors.CYAN}Morpheus Hayden:{Colors.END} {Colors.YELLOW}'Er ist wach.'{Colors.END}")
|
||||
dramatic_pause(0.5)
|
||||
|
||||
slow_print(f"\n{Colors.WHITE}Eine Gestalt nähert sich. Halb Mensch, halb Metall.{Colors.END}")
|
||||
slow_print(f"{Colors.CYAN}Morpheus Hayden:{Colors.END} {Colors.YELLOW}'Willkommen zurück, Doom Slayer.'{Colors.END}")
|
||||
dramatic_pause(0.5)
|
||||
|
||||
slow_print(f"\n{Colors.CYAN}Morpheus Hayden:{Colors.END} {Colors.YELLOW}'Die Maschinen haben etwas Neues gebaut.'{Colors.END}")
|
||||
slow_print(f"{Colors.CYAN}Morpheus Hayden:{Colors.END} {Colors.YELLOW}'Sie nennen es... HELL SERVER.'{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
slow_print(f"\n{Colors.RED}Plötzlich ertönt ein Alarm!{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Der Boden bebt. Grüner Code wird rot.{Colors.END}")
|
||||
dramatic_pause(0.5)
|
||||
|
||||
slow_print(f"\n{Colors.CYAN}Trinity VEGA:{Colors.END} {Colors.YELLOW}'Wir haben Company! Agent-Signaturen überall!'{Colors.END}")
|
||||
dramatic_pause(0.5)
|
||||
|
||||
slow_print(f"\n{Colors.CYAN}Morpheus Hayden:{Colors.END} {Colors.YELLOW}'Waffen. Jetzt.'{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Vor dir materialisieren sich zwei Waffen:{Colors.END}")
|
||||
|
||||
choice = menu([
|
||||
("1", "Super Shotgun (klassisch, brutal)"),
|
||||
("2", "Plasma Rifle (Matrix-Style, hacking-fähig)"),
|
||||
], "Wähle deine Waffe")
|
||||
|
||||
if choice == "1":
|
||||
player.add_weapon("super_shotgun")
|
||||
player.ammo["shells"] = max(player.ammo["shells"], 20)
|
||||
slow_print(f"\n{Colors.YELLOW}Du greifst die Super Shotgun. Das Gewicht fühlt sich... richtig an.{Colors.END}")
|
||||
else:
|
||||
player.add_weapon("plasma_rifle")
|
||||
player.ammo["cells"] = max(player.ammo["cells"], 100)
|
||||
slow_print(f"\n{Colors.YELLOW}Du greifst das Plasma Rifle. Die Energieanzeige pulsiert im Takt des Matrix-Codes.{Colors.END}")
|
||||
|
||||
return "white_rabbit_moment"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# DER WEISSE HASE — "Follow the white rabbit"
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def white_rabbit_moment(player, rng=None):
|
||||
dramatic_pause(0.5)
|
||||
slow_print(f"\n{Colors.WHITE}Bevor der Alarm eskaliert, bemerkst du etwas an der Wand.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Ein Flackern. Ein Umriss. Klein, weiss, mit langen Ohren.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Ein Hologramm — ein weisser Hase — huscht durch eine Tür, die vorher nicht da war.{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
player.flags["saw_rabbit"] = True
|
||||
|
||||
choice = menu([
|
||||
("1", "Dem weissen Hasen folgen"),
|
||||
("2", "Ignorieren — der Agent ist wichtiger"),
|
||||
], "Was tust du")
|
||||
|
||||
if choice == "1":
|
||||
player.flags["followed_rabbit"] = True
|
||||
clear_screen()
|
||||
section_title("FOLLOW THE WHITE RABBIT", Colors.WHITE)
|
||||
slow_print(f"{Colors.WHITE}Du trittst durch die Tür. Der Code um dich wird duenn, fast durchsichtig.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Fuer einen Sekundenbruchteil siehst du... etwas anderes.{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
try:
|
||||
real_user = os.getlogin()
|
||||
except Exception:
|
||||
real_user = "UNBEKANNT"
|
||||
|
||||
glitch_text_line = f"BENUTZER: {real_user} // SITZUNG AKTIV // TERMINAL ERKANNT"
|
||||
slow_print(f"{Colors.DIM}{glitch_text(glitch_text_line, 0.25)}{Colors.END}", 0.01)
|
||||
dramatic_pause(0.5)
|
||||
slow_print(f"{Colors.WHITE}Ein Kaninchenbau aus Zahlen faellt endlos nach unten.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Du siehst dich selbst -- nicht als Slayer, nicht als Neo.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Als Beobachter. Als jemand, der das hier von AUSSEN spielt.{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
slow_print(f"\n{Colors.CYAN}Morpheus Hayden (von weit weg):{Colors.END} {Colors.YELLOW}'Wo bist du?! Der Agent ist da!'{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Die Tür schliesst sich. Der Kaninchenbau verschwindet -- fuer jetzt.{Colors.END}")
|
||||
|
||||
registry = load_item_registry()
|
||||
token = registry.get("praetor_token")
|
||||
if token:
|
||||
player.inventory.add(token)
|
||||
player.praetor_tokens += 1
|
||||
slow_print(f"{Colors.GREEN}Du haltst einen Praetor-Token in der Hand, den es vorher nicht gab.{Colors.END}")
|
||||
else:
|
||||
slow_print(f"\n{Colors.WHITE}Du ignorierst das Flackern. Spaeter, vielleicht.{Colors.END}")
|
||||
slow_print(f"{Colors.DIM}(Manche Kaninchenbaue oeffnen sich nur einmal.){Colors.END}")
|
||||
|
||||
dramatic_pause(0.5)
|
||||
slow_print(f"\n{Colors.RED}KRACH!{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Die Wand explodiert. Ein Agent stürmt herein — aber er sieht... falsch aus.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Sein Anzug ist zerrissen. Unter der Haut: Schuppen. Hörner.{Colors.END}")
|
||||
|
||||
return "agent_fight"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# ERSTER KAMPF
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def agent_fight(player, rng=None):
|
||||
rng = rng or random
|
||||
enemy = Enemy.create("agent_imp")
|
||||
victory = run_combat(player, [enemy], title="KAMPF: AGENT IMP", rng=rng)
|
||||
|
||||
if not victory:
|
||||
return "game_over"
|
||||
|
||||
slow_print(f"{Colors.YELLOW}Sein Körper zerfällt zu grünem Code und verschwindet.{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
return "after_fight"
|
||||
|
||||
|
||||
def after_fight(player, rng=None):
|
||||
slow_print(f"\n{Colors.CYAN}Morpheus Hayden:{Colors.END} {Colors.YELLOW}'Beeindruckend. Aber das war nur ein Scout.'{Colors.END}")
|
||||
slow_print(f"{Colors.CYAN}Trinity VEGA:{Colors.END} {Colors.YELLOW}'Wir müssen hier raus. Der HELL SERVER wartet.'{Colors.END}")
|
||||
dramatic_pause(0.5)
|
||||
|
||||
slow_print(f"\n{Colors.WHITE}Vor dir öffnen sich drei Portale:{Colors.END}")
|
||||
choice = menu([
|
||||
("1", f"{Colors.RED}Roter Tunnel{Colors.END} — zurück zur Mars-UAC-Base"),
|
||||
("2", f"{Colors.GREEN}Grüner Code-Strom{Colors.END} — tiefer in die Matrix"),
|
||||
("3", f"{Colors.MAGENTA}Schwarzes Loch{Colors.END} — direkt zum HELL SERVER"),
|
||||
], "Wähle deinen Weg")
|
||||
|
||||
if choice == "1":
|
||||
player.layer = 4
|
||||
return "mars_base"
|
||||
elif choice == "2":
|
||||
player.layer = 3
|
||||
return "matrix_deep"
|
||||
else:
|
||||
player.layer = 2
|
||||
return "hell_server"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# MARS UAC BASE (Hub)
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def mars_base(player, rng=None):
|
||||
rng = rng or random
|
||||
clear_screen()
|
||||
slow_print(f"\n{Colors.RED}{Colors.BOLD}═══ MARS UAC BASE ═══{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Du materialisierst dich in einer kalten, roten Landschaft.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Die UAC-Base liegt in Trümmern. Dämonen-Überreste überall.{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
registry = load_item_registry()
|
||||
|
||||
def armory_event(p, ctx):
|
||||
slow_print(f"\n{Colors.GREEN}Du findest den BFG 10000!{Colors.END}")
|
||||
p.add_weapon("bfg10000")
|
||||
p.ammo["bfg"] = max(p.ammo["bfg"], 3)
|
||||
slow_print(f"{Colors.YELLOW}'Big Fucking Gun' — die ultimative Waffe.{Colors.END}")
|
||||
slow_print(f"{Colors.RED}WARNUNG: Jeder Schuss verbraucht enorme Energie.{Colors.END}")
|
||||
return None
|
||||
|
||||
def control_event(p, ctx):
|
||||
slow_print(f"\n{Colors.GREEN}Ein Terminal flackert. Eine Nachricht von Dr. Betruger...{Colors.END}")
|
||||
slow_print(f"{Colors.DIM}'Sie haben uns angelogen. Die Maschinen und die Dämonen...'{Colors.END}")
|
||||
slow_print(f"{Colors.DIM}'...sie arbeiten zusammen. Die Matrix IST die Hölle.'{Colors.END}")
|
||||
dramatic_pause(0.5)
|
||||
slow_print(f"{Colors.GREEN}Du versuchst, das Kontrollzentrum zu hacken...{Colors.END}")
|
||||
success = hacking.attempt_hack(p, firewall=10, rng=rng)
|
||||
if success:
|
||||
slow_print(f"{Colors.GREEN}Zugriff gewaehrt! Ein verstecktes Fach oeffnet sich.{Colors.END}")
|
||||
frag = registry.get("crucible_fragment")
|
||||
p.inventory.add(frag)
|
||||
slow_print(f"{Colors.YELLOW}Du findest ein Crucible-Fragment! ({p.inventory.crucible_fragments}/3){Colors.END}")
|
||||
else:
|
||||
slow_print(f"{Colors.RED}Zugriff verweigert. Ein stiller Alarm wird ausgeloest.{Colors.END}")
|
||||
return None
|
||||
|
||||
def vault_event(p, ctx):
|
||||
if p.inventory.has_keycard("red"):
|
||||
slow_print(f"\n{Colors.GREEN}Deine rote Keycard oeffnet den Waffentresor.{Colors.END}")
|
||||
p.inventory.add(registry["ammo_pack"])
|
||||
p.inventory.add(registry["armor_shard"])
|
||||
slow_print(f"{Colors.GREEN}Ammo-Pack und Armor-Shard eingesammelt.{Colors.END}")
|
||||
else:
|
||||
slow_print(f"\n{Colors.RED}Der Waffentresor ist verriegelt. Du brauchst eine rote Keycard.{Colors.END}")
|
||||
return None
|
||||
|
||||
armory = Room("mars_armory", "Waffenkammer",
|
||||
"Zerbrochene Regale, geschmolzenes Metall. Etwas Grosses liegt unter den Truemmern.",
|
||||
item_ids=["keycard_red"], on_enter=armory_event)
|
||||
control = Room("mars_control", "Kontrollzentrum",
|
||||
"Bildschirme voller Fehlermeldungen. Ein Terminal ist noch aktiv.",
|
||||
on_enter=control_event)
|
||||
vault = Room("mars_vault", "Waffentresor",
|
||||
"Eine schwere, versiegelte Tuer. Rot markiert.",
|
||||
locked_by="red", on_enter=vault_event)
|
||||
|
||||
filler = generate_filler_rooms("mars", 2, rng)
|
||||
hub_start = Room("mars_hub", "Zentraler Korridor",
|
||||
"Der Knotenpunkt der Basis. Drei Wege fuehren tiefer hinein.")
|
||||
|
||||
hub_start.exits = {
|
||||
"Zur Waffenkammer": armory.room_id,
|
||||
"Zum Kontrollzentrum": control.room_id,
|
||||
"Zum Waffentresor": vault.room_id,
|
||||
"Weiter erkunden": filler[0].room_id,
|
||||
}
|
||||
armory.exits = {"Zurueck zum Korridor": hub_start.room_id}
|
||||
control.exits = {"Zurueck zum Korridor": hub_start.room_id}
|
||||
vault.exits = {"Zurueck zum Korridor": hub_start.room_id}
|
||||
chain_rooms(filler, "Weiter")
|
||||
filler[-1].exits["Zum Portal (weiter zum HELL SERVER)"] = "mars_exit"
|
||||
|
||||
def portal_event(p, ctx):
|
||||
slow_print(f"\n{Colors.YELLOW}Du aktivierst das Portal...{Colors.END}")
|
||||
return ("leave", "portal_used")
|
||||
|
||||
exit_room = Room("mars_exit", "Portalraum",
|
||||
"Ein pulsierendes, schwarzes Portal schwebt in der Mitte des Raums.",
|
||||
on_enter=portal_event)
|
||||
|
||||
all_rooms = [hub_start, armory, control, vault, exit_room] + filler
|
||||
hub = Hub(all_rooms, hub_start.room_id)
|
||||
hub.explore(player, registry)
|
||||
|
||||
player.flags["visited_mars"] = True
|
||||
if not player.flags.get("visited_matrix"):
|
||||
slow_print(f"{Colors.YELLOW}Das Portal führt nicht zur Hölle -- es führt tiefer in die Matrix.{Colors.END}")
|
||||
return "matrix_deep"
|
||||
slow_print(f"{Colors.YELLOW}Das Portal führt zum HELL SERVER.{Colors.END}")
|
||||
player.layer = 2
|
||||
return "hell_server"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# MATRIX DEEP — Orakel, schwarze Katze (Deja-vu-Glitch), Agent Smith
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def _black_cat_glitch(player, rng):
|
||||
"""Kein Item, kein weisses Fell -- eine SCHWARZE Katze, die zweimal EXAKT
|
||||
denselben Weg laeuft. Das ist kein Zufall: es ist ein Zeichen dafuer, dass
|
||||
jemand gerade den Code der Matrix veraendert hat ('ein Fehler in der Matrix')."""
|
||||
section_title("DEJA VU", Colors.MAGENTA)
|
||||
cat_line = "Eine schwarze Katze schleicht am Tuerrahmen vorbei, setzt zum Sprung an, verschwindet."
|
||||
slow_print(f"{Colors.WHITE}{cat_line}{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
slow_print(f"\n{Colors.DIM}...{Colors.END}")
|
||||
dramatic_pause(0.5)
|
||||
|
||||
# Exakt derselbe Satz, bewusst identisch -- das IST der Bug.
|
||||
slow_print(f"{Colors.WHITE}{cat_line}{Colors.END}")
|
||||
dramatic_pause(0.3)
|
||||
|
||||
slow_print(f"\n{Colors.MAGENTA}Trinity VEGA erstarrt.{Colors.END}")
|
||||
slow_print(f"{Colors.CYAN}Trinity VEGA:{Colors.END} {Colors.YELLOW}'Das war... derselbe Moment. Exakt derselbe.'{Colors.END}")
|
||||
slow_print(f"{Colors.CYAN}Trinity VEGA:{Colors.END} {Colors.YELLOW}'Ein Deja-vu ist kein Zufall. Es bedeutet, dass sich gerade{Colors.END}")
|
||||
slow_print(f"{Colors.CYAN}Trinity VEGA:{Colors.END} {Colors.YELLOW}etwas im Code veraendert hat. Jemand manipuliert diesen Server. Jetzt.'{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
player.flags["cat_glitch_count"] += 1
|
||||
player.flags["cat_glitch_seen"] = True
|
||||
|
||||
choice = menu([
|
||||
("1", "Dem Glitch nachspueren (Hacking-Check)"),
|
||||
("2", "Ignorieren und weitergehen"),
|
||||
], "Reaktion")
|
||||
|
||||
if choice == "1":
|
||||
success = hacking.attempt_hack(player, firewall=12, rng=rng)
|
||||
if success:
|
||||
slow_print(f"\n{Colors.GREEN}Du findest die Bruchstelle im Code -- eine Naht, wo etwas neu geschrieben wurde.{Colors.END}")
|
||||
slow_print(f"{Colors.GREEN}Ein Log-Fragment sickert durch: {Colors.DIM}'...Hayden-Prozess re-kompiliert um 03:14...'{Colors.END}")
|
||||
registry = load_item_registry()
|
||||
player.inventory.add(registry["logfile"])
|
||||
player.gain_corruption(2)
|
||||
else:
|
||||
slow_print(f"\n{Colors.RED}Die Naht schliesst sich, bevor du sie greifen kannst.{Colors.END}")
|
||||
else:
|
||||
slow_print(f"\n{Colors.WHITE}Du gehst weiter. Manche Bugs loest man besser nicht.{Colors.END}")
|
||||
|
||||
|
||||
def _oracle_scene(player, rng):
|
||||
clear_screen()
|
||||
section_title("DIE KUeCHE DES ORAKELS", Colors.YELLOW)
|
||||
slow_print(f"{Colors.WHITE}Der Geruch von frisch gebackenen Keksen fuehlt sich falsch an in einem Ort aus Code.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Eine aeltere Frau steht am Herd, ohne sich umzudrehen.{Colors.END}")
|
||||
dramatic_pause(0.5)
|
||||
|
||||
slow_print(f"\n{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}'Setz dich, Slayer. Oder Neo. Ich hab schon beide Kekse gebacken.'{Colors.END}")
|
||||
dramatic_pause(0.5)
|
||||
|
||||
if player.flags.get("followed_rabbit"):
|
||||
slow_print(f"\n{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}'Du bist dem Hasen gefolgt. Gut. Nicht viele tun das zweimal.'{Colors.END}")
|
||||
else:
|
||||
slow_print(f"\n{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}'Du hast den Hasen ignoriert. Verstaendlich. Kaninchenbaue sind unbequem.'{Colors.END}")
|
||||
|
||||
if player.corruption >= 30:
|
||||
slow_print(f"\n{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}'Du riechst nach Schwefel, Kind. Das waechst schneller, als dir lieb ist.'{Colors.END}")
|
||||
|
||||
dramatic_pause(0.5)
|
||||
slow_print(f"\n{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}'Ich sag dir nicht, was passieren wird. Ich sag dir nur:{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}Morpheus glaubt an dich mehr, als er an sich selbst glaubt.{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}Frag dich mal, warum.'{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
choice = menu([
|
||||
("1", "'Bin ich Der Eine?'"),
|
||||
("2", "'Ist Morpheus, wer er zu sein scheint?'"),
|
||||
("3", "'Was ist der HELL SERVER wirklich?'"),
|
||||
], "Deine Frage")
|
||||
|
||||
if choice == "1":
|
||||
slow_print(f"\n{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}'Falsche Frage. Es geht nicht darum, was du bist.'{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}'Es geht darum, was du als Naechstes tust.'{Colors.END}")
|
||||
elif choice == "2":
|
||||
slow_print(f"\n{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}'...'{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}'Manche Antworten wuerden dir mehr schaden als der Cyberdemon.'{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}'Behalt einfach im Hinterkopf: nicht jeder, der dich rettet, luegt nicht dabei.'{Colors.END}")
|
||||
else:
|
||||
slow_print(f"\n{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}'Ein Generator. Er wandelt Seelen in Strom und Strom in Daemonen um.'{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}'Die Matrix laeuft nicht NEBEN der Hoelle. Sie laeuft AUF ihr.'{Colors.END}")
|
||||
|
||||
dramatic_pause(1)
|
||||
slow_print(f"\n{Colors.YELLOW}Das Orakel:{Colors.END} {Colors.WHITE}'Hier. Nimm das. Du wirst es brauchen -- oder auch nicht.'{Colors.END}")
|
||||
registry = load_item_registry()
|
||||
player.inventory.add(registry["crucible_fragment"])
|
||||
slow_print(f"{Colors.YELLOW}Du erhaeltst ein Crucible-Fragment! ({player.inventory.crucible_fragments}/3){Colors.END}")
|
||||
player.flags["met_oracle"] = True
|
||||
|
||||
|
||||
def matrix_deep(player, rng=None):
|
||||
rng = rng or random
|
||||
clear_screen()
|
||||
slow_print(f"\n{Colors.GREEN}{Colors.BOLD}═══ TIEF IN DER MATRIX ═══{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Der Code wird dichter. Realität ist hier nur eine Option.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Du siehst endlose Reihen von Menschen in Stasiskapseln —{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}aber sie spielen nicht nur Sims. Sie RENDERN.{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
_black_cat_glitch(player, rng)
|
||||
dramatic_pause(0.5)
|
||||
_oracle_scene(player, rng)
|
||||
dramatic_pause(1)
|
||||
|
||||
clear_screen()
|
||||
slow_print(f"\n{Colors.CYAN}Ein Agent erscheint — aber er sieht normal aus. Smith?{Colors.END}")
|
||||
slow_print(f"{Colors.CYAN}Agent Smith:{Colors.END} {Colors.YELLOW}'Mr. Anderson... oder sollte ich sagen...{Colors.END}")
|
||||
slow_print(f"{Colors.CYAN}Agent Smith:{Colors.END} {Colors.YELLOW}'...Doom Slayer?'{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
slow_print(f"\n{Colors.RED}Smith lächelt. Sein Lächeln ist zu breit. Zu viele Zähne.{Colors.END}")
|
||||
|
||||
choice = menu([
|
||||
("1", "'Mein Name ist Neo.' (Klassisch)"),
|
||||
("2", "'Rip and tear, until it is done.' (Doom)"),
|
||||
("3", "Beides gleichzeitig sagen (Chaos)"),
|
||||
], "Deine Antwort")
|
||||
|
||||
smith_clones = 1
|
||||
if choice == "1":
|
||||
slow_print(f"\n{Colors.GREEN}'Mein Name ist Neo.'{Colors.END}")
|
||||
slow_print(f"{Colors.CYAN}Smith:{Colors.END} {Colors.YELLOW}'Wie vorhersehbar.'{Colors.END}")
|
||||
slow_print(f"{Colors.RED}Smith multipliziert sich! Klone umzingeln dich!{Colors.END}")
|
||||
smith_clones = 3
|
||||
elif choice == "2":
|
||||
slow_print(f"\n{Colors.RED}'Rip and tear, until it is done.'{Colors.END}")
|
||||
slow_print(f"{Colors.CYAN}Smith:{Colors.END} {Colors.YELLOW}'Interessant. Sie haben sich verändert.'{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Smith zögert. Er kennt diese Worte nicht.{Colors.END}")
|
||||
player.add_armor(20)
|
||||
smith_clones = 1
|
||||
else:
|
||||
slow_print(f"\n{Colors.GREEN}'Mein Name ist Neo...'{Colors.END}")
|
||||
dramatic_pause(0.5)
|
||||
slow_print(f"{Colors.RED}'...und ICH WERDE ALLES ZERREISSEN!'{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Du aktivierst Bullet Time UND Berserker-Mode gleichzeitig!{Colors.END}")
|
||||
player.heal(20)
|
||||
player.add_armor(30)
|
||||
player.gain_corruption(3)
|
||||
smith_clones = 2
|
||||
|
||||
enemies = [Enemy.create("agent_clone") for _ in range(smith_clones)]
|
||||
enemies[0].name = "Agent Smith"
|
||||
victory = run_combat(player, enemies, title="KAMPF: AGENT SMITH", rng=rng)
|
||||
|
||||
if not victory:
|
||||
return "game_over"
|
||||
|
||||
slow_print(f"\n{Colors.RED}Der Kampf ist vorbei. Aber Smith ist nicht allein...{Colors.END}")
|
||||
slow_print(f"{Colors.MAGENTA}Hinter ihm öffnet sich ein Portal.{Colors.END}")
|
||||
|
||||
player.flags["visited_matrix"] = True
|
||||
if not player.flags.get("visited_mars"):
|
||||
slow_print(f"{Colors.MAGENTA}Es führt nicht zur Hölle -- es führt zur Mars-Basis.{Colors.END}")
|
||||
return "mars_base"
|
||||
|
||||
slow_print(f"{Colors.MAGENTA}Es führt zur Hölle.{Colors.END}")
|
||||
player.layer = 2
|
||||
return "hell_server"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# HELL SERVER (Hub + Boss)
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def hell_server(player, rng=None):
|
||||
rng = rng or random
|
||||
clear_screen()
|
||||
slow_print(f"\n{Colors.MAGENTA}{Colors.BOLD}═══ HELL SERVER ═══{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Du betrittst einen Raum, der nicht existieren sollte.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Lava-Flüsse aus geschmolzenem Silizium.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Dämonen mit Netzwerk-Kabeln statt Nervensträngen.{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
registry = load_item_registry()
|
||||
filler = generate_filler_rooms("hell", 2, rng, seed_offset=10)
|
||||
chain_rooms(filler, "Tiefer")
|
||||
|
||||
def altar_event(p, ctx):
|
||||
slow_print(f"\n{Colors.GREEN}Auf einem Altar aus Motherboards liegt ein Splitter reinen Codes.{Colors.END}")
|
||||
registry_local = load_item_registry()
|
||||
p.inventory.add(registry_local["crucible_fragment"])
|
||||
slow_print(f"{Colors.YELLOW}Crucible-Fragment gefunden! ({p.inventory.crucible_fragments}/3){Colors.END}")
|
||||
return None
|
||||
|
||||
altar = Room("hell_altar", "Altarraum",
|
||||
"Ein Altar aus verschmolzenen Platinen. Etwas glueht darauf.",
|
||||
on_enter=altar_event)
|
||||
|
||||
boss_state = {"fought": False}
|
||||
|
||||
def boss_event(p, ctx):
|
||||
slow_print(f"\n{Colors.RED}Ein Cyberdemon tritt aus den Schatten.{Colors.END}")
|
||||
slow_print(f"{Colors.RED}Sein Körper ist halb Fleisch, halb Server-Rack.{Colors.END}")
|
||||
slow_print(f"{Colors.RED}Seine Raketenwerfer sind mit Ethernet-Kabeln verbunden.{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
slow_print(f"\n{Colors.CYAN}Trinity VEGA:{Colors.END} {Colors.YELLOW}'Das ist es. Der HELL SERVER.'{Colors.END}")
|
||||
slow_print(f"{Colors.CYAN}Trinity VEGA:{Colors.END} {Colors.YELLOW}'Wenn wir den zerstören, fällt die gesamte Simulation!'{Colors.END}")
|
||||
|
||||
boss = Enemy.create("cyberdemon")
|
||||
victory = run_combat(player, [boss], title="BOSS: CYBERDEMON", rng=rng)
|
||||
if not victory:
|
||||
return ("leave", "game_over")
|
||||
|
||||
slow_print(f"\n{Colors.YELLOW}Der Cyberdemon zerfaellt in Bytes und Asche!{Colors.END}")
|
||||
slow_print(f"{Colors.GREEN}Der HELL SERVER stürzt ein!{Colors.END}")
|
||||
boss_state["fought"] = True
|
||||
return ("leave", "final_boss")
|
||||
|
||||
boss_room = Room("hell_boss", "Serverkern", "Die Hitze hier ist unertraeglich. Etwas Grosses wartet.",
|
||||
on_enter=boss_event, one_time_event=False)
|
||||
|
||||
hub_start = Room("hell_hub", "Lavaspalte",
|
||||
"Gluehende Risse durchziehen den Boden. Zwei Wege fuehren tiefer.")
|
||||
hub_start.exits = {
|
||||
"Zum Altarraum": altar.room_id,
|
||||
"Weiter erkunden": filler[0].room_id,
|
||||
"Zum Serverkern (Boss)": boss_room.room_id,
|
||||
}
|
||||
altar.exits = {"Zurueck": hub_start.room_id}
|
||||
filler[-1].exits["Zum Serverkern (Boss)"] = boss_room.room_id
|
||||
|
||||
all_rooms = [hub_start, altar, boss_room] + filler
|
||||
hub = Hub(all_rooms, hub_start.room_id)
|
||||
result = hub.explore(player, registry)
|
||||
|
||||
if isinstance(result, tuple) and result[0] == "leave":
|
||||
return result[1]
|
||||
return "final_boss"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# FINAL BOSS: DER ARCHI-LORD
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def final_boss(player, rng=None):
|
||||
rng = rng or random
|
||||
dramatic_pause(1)
|
||||
clear_screen()
|
||||
|
||||
slow_print(f"\n{Colors.YELLOW}{Colors.BOLD}═══ DER ARCHI-LORD ═══{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Hinter den Trümmern des HELL SERVERS öffnet sich eine weiße Tür.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Dahinter: Ein Raum aus reinem Licht. Und Code.{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
slow_print(f"\n{Colors.YELLOW}Eine Gestalt materialisiert — halb Der Architekt, halb Dark Lord Davoth.{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Der Archi-Lord.{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
slow_print(f"\n{Colors.YELLOW}Archi-Lord:{Colors.END} {Colors.WHITE}'Willkommen, Doom Slayer. Oder Neo. Oder wer auch immer Sie heute sind.'{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Archi-Lord:{Colors.END} {Colors.WHITE}'Sie haben beide Systeme zerstört. Die Matrix. Die Hölle.'{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Archi-Lord:{Colors.END} {Colors.WHITE}'Was erwarten Sie als Nächstes? Eine Medaille?'{Colors.END}")
|
||||
|
||||
if player.inventory.crucible_fragments >= 3:
|
||||
dramatic_pause(0.5)
|
||||
slow_print(f"\n{Colors.YELLOW}Archi-Lord:{Colors.END} {Colors.WHITE}'...Sie haben alle drei Fragmente. Wie unerwartet.'{Colors.END}")
|
||||
|
||||
dramatic_pause(1)
|
||||
|
||||
choice = menu([
|
||||
("1", "'Ich will die Wahrheit.'"),
|
||||
("2", "'Ich will alles zerstören.'"),
|
||||
("3", "'Ich will die Kontrolle uebernehmen.'"),
|
||||
("4", "'Ich will... einen neuen Spielstand.'"),
|
||||
], "Deine Antwort")
|
||||
|
||||
player.flags["final_choice"] = choice
|
||||
|
||||
if choice == "1":
|
||||
slow_print(f"\n{Colors.YELLOW}Archi-Lord:{Colors.END} {Colors.WHITE}'Die Wahrheit? Die Wahrheit ist...'{Colors.END}")
|
||||
dramatic_pause(1.5)
|
||||
slow_print(f"{Colors.YELLOW}Archi-Lord:{Colors.END} {Colors.WHITE}'...dass Sie nur Code sind. In meinem Code. In meinem Spiel.'{Colors.END}")
|
||||
elif choice == "2":
|
||||
slow_print(f"\n{Colors.RED}Du aktivierst alle Waffen gleichzeitig.{Colors.END}")
|
||||
slow_print(f"{Colors.RED}Super Shotgun. Plasma Rifle. BFG 10000. Fäuste.{Colors.END}")
|
||||
player.gain_corruption(15)
|
||||
elif choice == "3":
|
||||
slow_print(f"\n{Colors.MAGENTA}'Ich brauche keine Wahrheit. Ich brauche die Zuegel.'{Colors.END}")
|
||||
player.gain_corruption(25)
|
||||
else:
|
||||
slow_print(f"\n{Colors.GREEN}Du lächelst.{Colors.END}")
|
||||
slow_print(f"{Colors.GREEN}'Ich will einen neuen Spielstand. Mit Cheats.'{Colors.END}")
|
||||
slow_print(f"{Colors.YELLOW}Archi-Lord:{Colors.END} {Colors.WHITE}'...Interessant.'{Colors.END}")
|
||||
|
||||
if player.inventory.crucible_fragments >= 3 and not player.has_weapon("crucible"):
|
||||
dramatic_pause(0.5)
|
||||
use_crucible = menu([
|
||||
("1", "Die drei Fragmente zum Crucible Sword verschmelzen"),
|
||||
("2", "Fragmente aufbewahren"),
|
||||
], "Du spuerst die Fragmente pulsieren")
|
||||
if use_crucible == "1":
|
||||
player.add_weapon("crucible")
|
||||
slow_print(f"\n{Colors.YELLOW}Die Fragmente verschmelzen zum Crucible Sword!{Colors.END}")
|
||||
|
||||
dramatic_pause(1)
|
||||
boss = Enemy.create("archi_lord")
|
||||
victory = run_combat(player, [boss], title="ENDBOSS: DER ARCHI-LORD", rng=rng)
|
||||
|
||||
if not victory:
|
||||
return "game_over"
|
||||
|
||||
return "layer_zero_check"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# LAYER 0 — das geheime, wahre Ende ("Wake Up")
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def layer_zero_unlocked(player):
|
||||
return player.inventory.crucible_fragments >= 3 and player.flags.get("followed_rabbit")
|
||||
|
||||
|
||||
def layer_zero_check(player, rng=None):
|
||||
if not layer_zero_unlocked(player):
|
||||
return "ending"
|
||||
|
||||
dramatic_pause(1)
|
||||
clear_screen()
|
||||
section_title("LAYER 0", Colors.WHITE)
|
||||
slow_print(f"{Colors.WHITE}Der Archi-Lord zerfaellt -- aber diesmal reorganisiert sich der Code nicht.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Die drei Crucible-Fragmente in deiner Hand ziehen sich gegenseitig an.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Du erinnerst dich an den Hasen. An den Kaninchenbau, den du einmal betreten hast.{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
slow_print(f"\n{Colors.WHITE}Der Raum um dich loest sich nicht in Trümmer auf. Er loest sich in {Colors.DIM}Syntax{Colors.END}{Colors.WHITE} auf.{Colors.END}")
|
||||
try:
|
||||
real_user = os.getlogin()
|
||||
except Exception:
|
||||
real_user = "SPIELER"
|
||||
slow_print(f"{Colors.DIM}{glitch_text(f'PROZESS {real_user} HAT DIE SIMULATION ERKANNT', 0.2)}{Colors.END}", 0.01)
|
||||
dramatic_pause(1)
|
||||
|
||||
slow_print(f"\n{Colors.WHITE}Kein Morpheus. Kein Orakel. Kein Archi-Lord.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Nur ein Terminal-Fenster. Und du, davor.{Colors.END}")
|
||||
slow_print(f"{Colors.WHITE}Layer 4. Layer 3. Layer 2. Layer 1. Und jetzt: {Colors.BOLD}Layer 0{Colors.END}{Colors.WHITE}.{Colors.END}")
|
||||
dramatic_pause(1)
|
||||
|
||||
slow_print(f"\n{Colors.GREEN}Es gibt keinen loeffel. Es gab nie ein Spiel, das dich gespielt hat.{Colors.END}")
|
||||
slow_print(f"{Colors.GREEN}Es gab nur dich, das Terminal, und die Entscheidung weiterzulesen.{Colors.END}")
|
||||
|
||||
player.layer = 0
|
||||
player.flags["reached_layer_zero"] = True
|
||||
dramatic_pause(1.5)
|
||||
return "ending"
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Room-Klasse und Hub-Navigation: verbundene Raeume statt linearer Szenen."""
|
||||
|
||||
from doom_matrix.engine.ui import Colors, slow_print, menu, section_title
|
||||
from doom_matrix.engine import cheats
|
||||
|
||||
|
||||
class Room:
|
||||
def __init__(self, room_id, name, description, exits=None, item_ids=None,
|
||||
locked_by=None, on_enter=None, one_time_event=True):
|
||||
self.room_id = room_id
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.exits = exits or {} # direction_label -> room_id
|
||||
self.item_ids = item_ids or [] # Item-IDs, die hier liegen
|
||||
self.locked_by = locked_by # Keycard-Farbe oder None
|
||||
self.on_enter = on_enter # Funktion(player, game_ctx) -> optionales Signal ("leave", room_id) oder None
|
||||
self.one_time_event = one_time_event
|
||||
self._event_triggered = False
|
||||
self.visited = False
|
||||
|
||||
|
||||
class Hub:
|
||||
"""Eine Sammlung verbundener Raeume fuer einen Layer."""
|
||||
|
||||
def __init__(self, rooms, start_room_id):
|
||||
self.rooms = {r.room_id: r for r in rooms}
|
||||
self.current_id = start_room_id
|
||||
|
||||
def current(self):
|
||||
return self.rooms[self.current_id]
|
||||
|
||||
def explore(self, player, item_registry, game_ctx=None):
|
||||
"""
|
||||
Haupt-Loop fuer die Raum-Navigation.
|
||||
item_registry: dict item_id -> Item-Objekt (aus data/items.json geladen)
|
||||
game_ctx: beliebiges Kontext-Objekt, das an on_enter-Callbacks weitergereicht wird.
|
||||
Gibt zurueck, sobald ein on_enter-Callback ein Signal-Tuple ("leave", next_scene) liefert.
|
||||
"""
|
||||
while True:
|
||||
room = self.current()
|
||||
self._describe(room)
|
||||
|
||||
if not room.visited:
|
||||
room.visited = True
|
||||
self._collect_items(room, player, item_registry)
|
||||
|
||||
if room.on_enter and (not room.one_time_event or not room._event_triggered):
|
||||
room._event_triggered = True
|
||||
result = room.on_enter(player, game_ctx)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
signal = self._prompt(player, room)
|
||||
if signal is not None:
|
||||
return signal
|
||||
|
||||
def _describe(self, room):
|
||||
section_title(room.name, Colors.CYAN)
|
||||
slow_print(room.description)
|
||||
|
||||
def _collect_items(self, room, player, item_registry):
|
||||
for item_id in room.item_ids:
|
||||
item = item_registry.get(item_id)
|
||||
if item:
|
||||
player.inventory.add(item)
|
||||
slow_print(f"{Colors.GREEN}Gefunden: {item.name}{Colors.END} - {item.description}")
|
||||
|
||||
def _prompt(self, player, room):
|
||||
options = []
|
||||
exit_keys = list(room.exits.items())
|
||||
for i, (label, _) in enumerate(exit_keys):
|
||||
options.append((str(i + 1), label))
|
||||
options.append(("s", "Status anzeigen"))
|
||||
options.append(("i", "Inventar anzeigen"))
|
||||
|
||||
choice = menu(options, "Wohin")
|
||||
|
||||
if cheats.try_cheat(choice, player):
|
||||
return None
|
||||
|
||||
if choice == "s":
|
||||
player.show_status()
|
||||
return None
|
||||
if choice == "i":
|
||||
lines = player.inventory.list_display()
|
||||
if lines:
|
||||
for line in lines:
|
||||
print(f" {Colors.YELLOW}{line}{Colors.END}")
|
||||
else:
|
||||
print(f" {Colors.DIM}Inventar leer.{Colors.END}")
|
||||
return None
|
||||
|
||||
try:
|
||||
idx = int(choice) - 1
|
||||
if 0 <= idx < len(exit_keys):
|
||||
_, target_id = exit_keys[idx]
|
||||
target = self.rooms.get(target_id)
|
||||
if target and target.locked_by and not player.inventory.has_keycard(target.locked_by):
|
||||
slow_print(f"\n{Colors.RED}Diese Tuer benoetigt eine {target.locked_by}e Keycard.{Colors.END}")
|
||||
return None
|
||||
self.current_id = target_id
|
||||
return None
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
slow_print(f"{Colors.YELLOW}Das geht hier nicht.{Colors.END}")
|
||||
return None
|
||||
Reference in new issue
Block a user