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
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Generische Kampf-Engine: Stagger, Bullet Time (Focus), Glory Kills, Hacking."""
|
||||
|
||||
import random
|
||||
|
||||
from doom_matrix.engine.ui import Colors, slow_print, dramatic_pause, bar, menu
|
||||
from doom_matrix.engine import cheats
|
||||
from doom_matrix.entities.items import type_multiplier
|
||||
from doom_matrix.entities.enemy import Enemy
|
||||
from doom_matrix.systems import hacking
|
||||
|
||||
|
||||
GLORY_HEAL = 25
|
||||
FOCUS_COST_BULLET_TIME = 25
|
||||
FOCUS_REGEN_PER_TURN = 8
|
||||
|
||||
|
||||
def _init_status(enemy):
|
||||
if not hasattr(enemy, "frozen"):
|
||||
enemy.frozen = False
|
||||
if not hasattr(enemy, "jammed"):
|
||||
enemy.jammed = 0
|
||||
if not hasattr(enemy, "turned"):
|
||||
enemy.turned = False
|
||||
|
||||
|
||||
def _pick_target(enemies):
|
||||
alive = [e for e in enemies if e.is_alive]
|
||||
if len(alive) == 1:
|
||||
return alive[0]
|
||||
print(f"\n{Colors.RED}Ziele:{Colors.END}")
|
||||
for i, e in enumerate(alive):
|
||||
stagger_tag = f" {Colors.YELLOW}[STAGGERED]{Colors.END}" if e.staggered else ""
|
||||
print(f" {Colors.GREEN}[{i + 1}]{Colors.END} {e.name} - {bar(e.hp, e.max_hp, Colors.RED)}{stagger_tag}")
|
||||
choice = input(f"\n{Colors.CYAN}Ziel waehlen: {Colors.END}").strip()
|
||||
try:
|
||||
idx = int(choice) - 1
|
||||
if 0 <= idx < len(alive):
|
||||
return alive[idx]
|
||||
except ValueError:
|
||||
pass
|
||||
return alive[0]
|
||||
|
||||
|
||||
def _weapon_menu(player):
|
||||
print(f"\n{Colors.GREEN}Waffen:{Colors.END}")
|
||||
usable = []
|
||||
for w in player.weapons:
|
||||
if w.ammo_type == "melee":
|
||||
usable.append(w)
|
||||
tag = "unbegrenzt"
|
||||
else:
|
||||
have = player.ammo.get(w.ammo_type, 0)
|
||||
tag = f"{have} {w.ammo_type}"
|
||||
if have >= w.ammo_cost:
|
||||
usable.append(w)
|
||||
print(f" {Colors.GREEN}[{len(usable) if w in usable else '-'}]{Colors.END} {w.name} ({tag})")
|
||||
options = [(str(i + 1), w.name) for i, w in enumerate(usable)]
|
||||
choice = menu(options, "Waffe")
|
||||
try:
|
||||
idx = int(choice) - 1
|
||||
if 0 <= idx < len(usable):
|
||||
return usable[idx]
|
||||
except ValueError:
|
||||
pass
|
||||
return usable[0] if usable else player.get_weapon("fists")
|
||||
|
||||
|
||||
def _fire_weapon(player, weapon, enemy, rng):
|
||||
dmg = rng.randint(*weapon.damage_range)
|
||||
dmg = int(dmg * type_multiplier(weapon.damage_type, enemy.type))
|
||||
if weapon.ammo_type != "melee":
|
||||
player.ammo[weapon.ammo_type] = max(0, player.ammo[weapon.ammo_type] - weapon.ammo_cost)
|
||||
return dmg
|
||||
|
||||
|
||||
def _enemy_attack(player, enemy, rng):
|
||||
if enemy.frozen:
|
||||
slow_print(f"\n{Colors.GREEN}{enemy.name} ist eingefroren -- kein Angriff diese Runde!{Colors.END}")
|
||||
enemy.frozen = False
|
||||
return
|
||||
if enemy.turned:
|
||||
slow_print(f"\n{Colors.GREEN}{enemy.name} greift, verwirrt, ins Leere.{Colors.END}")
|
||||
enemy.turned = False
|
||||
return
|
||||
|
||||
dmg = rng.randint(*enemy.attack_range)
|
||||
if enemy.jammed:
|
||||
dmg = int(dmg * 0.5)
|
||||
enemy.jammed -= 1
|
||||
|
||||
remaining, absorbed = player.take_damage(dmg)
|
||||
if absorbed:
|
||||
slow_print(f"\n{Colors.RED}{enemy.name} greift an! Armor absorbiert {absorbed} Schaden.{Colors.END}")
|
||||
if remaining:
|
||||
slow_print(f"{Colors.RED}Du erleidest {remaining} Schaden!{Colors.END}")
|
||||
if not absorbed and not remaining:
|
||||
slow_print(f"\n{Colors.GREEN}{enemy.name} greift an -- God Mode absorbiert alles!{Colors.END}")
|
||||
|
||||
|
||||
def run_combat(player, enemies, title="KAMPF", rng=None, on_reinforcement=None):
|
||||
"""
|
||||
enemies: Liste von Enemy-Objekten.
|
||||
Gibt True (Sieg) oder False (Tod, sofern kein Auto-Revive) zurueck.
|
||||
"""
|
||||
rng = rng or random
|
||||
for e in enemies:
|
||||
_init_status(e)
|
||||
|
||||
slow_print(f"\n{Colors.BOLD}{Colors.RED}═══ {title} ═══{Colors.END}")
|
||||
|
||||
while any(e.is_alive for e in enemies) and player.health > 0:
|
||||
print()
|
||||
for e in enemies:
|
||||
if e.is_alive:
|
||||
stagger_tag = f" {Colors.YELLOW}[STAGGERED - Glory Kill bereit!]{Colors.END}" if e.staggered else ""
|
||||
print(f"{Colors.RED}{e.name}:{Colors.END} {bar(e.hp, e.max_hp, Colors.RED)}"
|
||||
f" | Stagger {bar(e.stagger, e.stagger_max, Colors.YELLOW, 10)}{stagger_tag}")
|
||||
print(f"{Colors.CYAN}Du:{Colors.END} {bar(player.health, player.max_health, Colors.RED)} | "
|
||||
f"Armor {player.armor} | Focus {bar(player.focus, player.max_focus, Colors.GREEN, 10)}")
|
||||
|
||||
options = [
|
||||
("1", "Schiessen"),
|
||||
("2", f"Bullet Time (-{FOCUS_COST_BULLET_TIME} Focus, garantiertes Ausweichen + Krit)"),
|
||||
("3", "Glory Kill (nur bei gestaggertem Gegner)"),
|
||||
("4", f"Hack (Skill-Check, Hacking {player.hacking})"),
|
||||
]
|
||||
if player.inventory.has("medkit"):
|
||||
options.append(("5", "Medkit benutzen"))
|
||||
|
||||
action = menu(options, "Aktion")
|
||||
if cheats.try_cheat(action, player):
|
||||
continue
|
||||
bullet_time_active = False
|
||||
|
||||
if action == "1":
|
||||
weapon = _weapon_menu(player)
|
||||
target = _pick_target(enemies)
|
||||
dmg = _fire_weapon(player, weapon, target, rng)
|
||||
slow_print(f"\n{Colors.YELLOW}{weapon.name} trifft {target.name}!{Colors.END}")
|
||||
target.apply_damage(dmg)
|
||||
slow_print(f"{Colors.RED}-{dmg} HP für {target.name}!{Colors.END}")
|
||||
if target.staggered:
|
||||
slow_print(f"{Colors.YELLOW}{Colors.BOLD}{target.name} ist GESTAGGERT! Glory Kill moeglich!{Colors.END}")
|
||||
|
||||
elif action == "2":
|
||||
if player.focus >= FOCUS_COST_BULLET_TIME:
|
||||
player.focus -= FOCUS_COST_BULLET_TIME
|
||||
bullet_time_active = True
|
||||
slow_print(f"\n{Colors.GREEN}Zeit verlangsamt sich. Grüner Code umhüllt dich.{Colors.END}")
|
||||
weapon = _weapon_menu(player)
|
||||
target = _pick_target(enemies)
|
||||
dmg = int(_fire_weapon(player, weapon, target, rng) * 1.5)
|
||||
slow_print(f"{Colors.YELLOW}Kritischer Treffer in Bullet Time! {weapon.name} zerreisst {target.name}!{Colors.END}")
|
||||
target.apply_damage(dmg)
|
||||
slow_print(f"{Colors.RED}-{dmg} HP für {target.name}!{Colors.END}")
|
||||
else:
|
||||
slow_print(f"\n{Colors.RED}Nicht genug Focus fuer Bullet Time! Der Versuch kostet dich die Deckung.{Colors.END}")
|
||||
|
||||
elif action == "3":
|
||||
target = next((e for e in enemies if e.is_alive and e.staggered), None)
|
||||
if target:
|
||||
slow_print(f"\n{Colors.RED}{Colors.BOLD}GLORY KILL!{Colors.END}")
|
||||
slow_print(f"{Colors.RED}Du stürmst vor, packst {target.name} und reisst ihn auseinander!{Colors.END}")
|
||||
slow_print(f"{Colors.RED}Grüne Flüssigkeit spritzt — kein Blut. Code.{Colors.END}")
|
||||
target.hp = 0
|
||||
player.heal(GLORY_HEAL)
|
||||
player.glory_kills += 1
|
||||
slow_print(f"{Colors.GREEN}+{GLORY_HEAL} HP! Der Tod heilt dich.{Colors.END}")
|
||||
else:
|
||||
slow_print(f"\n{Colors.YELLOW}Kein Gegner ist gestaggert! Der Glory Kill schlaegt fehl.{Colors.END}")
|
||||
target = _pick_target(enemies)
|
||||
dmg = rng.randint(5, 10)
|
||||
player.take_damage(dmg)
|
||||
slow_print(f"{Colors.RED}{target.name} kontert! -{dmg} HP!{Colors.END}")
|
||||
|
||||
elif action == "4":
|
||||
target = _pick_target(enemies)
|
||||
success = hacking.attempt_hack(player, target.firewall, rng)
|
||||
if success:
|
||||
effect = hacking.choose_hack_effect()
|
||||
hacking.apply_hack_effect(effect, target, player, rng)
|
||||
else:
|
||||
slow_print(f"{Colors.RED}Das System hat deinen Hack erkannt! Firewall-Rueckstoss!{Colors.END}")
|
||||
dmg = rng.randint(10, 20)
|
||||
player.take_damage(dmg)
|
||||
slow_print(f"{Colors.RED}-{dmg} HP durch Rueckstoss!{Colors.END}")
|
||||
if hacking.trace_triggered(player):
|
||||
slow_print(f"\n{Colors.MAGENTA}{Colors.BOLD}TRACE KOMPLETT! Das System schickt Verstaerkung!{Colors.END}")
|
||||
if on_reinforcement:
|
||||
reinforcement = on_reinforcement()
|
||||
if reinforcement:
|
||||
_init_status(reinforcement)
|
||||
enemies.append(reinforcement)
|
||||
slow_print(f"{Colors.RED}{reinforcement.name} betritt den Kampf!{Colors.END}")
|
||||
|
||||
elif action == "5" and player.inventory.has("medkit"):
|
||||
item = player.inventory.consume("medkit")
|
||||
player.heal(item.value)
|
||||
slow_print(f"\n{Colors.GREEN}Medkit benutzt. +{item.value} HP!{Colors.END}")
|
||||
|
||||
else:
|
||||
slow_print(f"{Colors.YELLOW}Ungueltige Eingabe.{Colors.END}")
|
||||
continue
|
||||
|
||||
# Boss-Phasenwechsel pruefen
|
||||
for e in enemies:
|
||||
if e.is_alive and e.maybe_advance_phase():
|
||||
slow_print(f"\n{Colors.MAGENTA}{Colors.BOLD}{e.name} tritt in Phase {e.phase} ein! Er wird gefaehrlicher!{Colors.END}")
|
||||
e.clear_stagger()
|
||||
|
||||
# Fokus regeneriert leicht jede Runde
|
||||
player.focus = min(player.max_focus, player.focus + FOCUS_REGEN_PER_TURN)
|
||||
|
||||
# Gegner greifen an (nicht wenn Bullet Time garantiertes Ausweichen gibt)
|
||||
for e in enemies:
|
||||
if e.is_alive and player.health > 0:
|
||||
if bullet_time_active:
|
||||
slow_print(f"\n{Colors.GREEN}Du weichst {e.name}s Angriff muehelos aus — Bullet Time!{Colors.END}")
|
||||
continue
|
||||
dramatic_pause(0.3)
|
||||
_enemy_attack(player, e, rng)
|
||||
|
||||
if player.health <= 0:
|
||||
if player.try_auto_revive():
|
||||
slow_print(f"\n{Colors.MAGENTA}{Colors.BOLD}Die Backup-Pille aktiviert sich! Du wirst neu geladen...{Colors.END}")
|
||||
slow_print(f"{Colors.GREEN}HP wiederhergestellt auf {player.health}.{Colors.END}")
|
||||
else:
|
||||
return False
|
||||
|
||||
slow_print(f"\n{Colors.GREEN}{Colors.BOLD}ALLE GEGNER ELIMINIERT!{Colors.END}")
|
||||
return True
|
||||
Reference in new issue
Block a user