Files
cards/scripts/ui/battle_tutorial.gd
Team3 4c6bf96eeb Kampf fortsetzen, Gamepad im Deckbau, Deck-Codes, Kapitel 3, Elementbooster, englische Karten, Host-Wechsel und Zuschauer
- KI-Bewertung mit Bedrohung und Führenden-Bremse (62:38 gegen die alte im Duell, gleichauf zu viert)
- Balance: Sand +1/+1 statt Schild, Erde+Luft und Erde geschwächt, Wasser angehoben, Messlauf mit N=200 möglich
- Einzelspieler-Kämpfe werden laufend gespeichert (voller Spielstand, viewer -1) und im Menü fortgesetzt
- Karten, Deckzeilen und Kartenrücken fokussierbar: Gamepad in Deckbau, Werkstatt und Booster
- Kartennamen (name_en) und Kartentexte übersetzbar, Übersetzungstest deckt cards.gd mit ab
- Deck-Code DINGER1:id*anzahl mit Zwischenablage; acht neue Karten; Kampagne Kapitel 3; Elementbooster 120 Münzen
- Netzwerk: Nachfolge-Host mit vollem Spielstand, Neuverbindung der anderen, Zuschauer (Sicht -2); Bot kann hosten
- Große Schrift: Kartennamen, Kartentext, Knopfleisten, Gegnervorschau, Dialogtext
- Anleitung 1 garantiert ein 1-Energie-Wesen; Tests: DINGER_ONLY für einzelne Oberflächentests
- Oberflächentest auch auf Xephyr mit llvmpipe (wie Xvfb in der CI) grün

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPgKvy5W9WRNFHjkm5QsHx
2026-09-16 18:50:09 +02:00

150 lines
6.6 KiB
GDScript

class_name BattleTutorial
extends RefCounted
## Geführte Partien: Schritte, Bedingungen und Hervorhebungen (ausgelagert aus dem Kampfbildschirm).
const STEPS1 := [
["Willkommen! Unten liegt deine Hand. Jede Karte kostet Energie (◆), du hast im ersten Zug 1. Spiele ein Wesen mit Kosten 1 per Klick.",
"played_creature"],
["Wesen greifen erst ab dem nächsten Zug an. Beende deinen Zug mit dem Knopf rechts oder der Leertaste.", "turn_passed"],
["Grüne Rahmen zeigen angriffsbereite Wesen. Klicke ein Wesen und dann Fenn oder eines seiner Wesen. Wächter müssen zuerst fallen.", "attacked"],
["Resonanz: Wesen mit gleichem Element geben sich gegenseitig Angriff. Spiele ein zweites Feuer- oder Erde-Wesen.", "resonance"],
["Feldkombo: Ein Wesen neben einem eigenen Wesen mit anderem Element löst einen Effekt aus. Lava macht 1 Schaden an das stärkste gegnerische Wesen.", "combo"],
["Zauber wirken sofort. Funke oder Feuerball treffen auch Fenn direkt. Ziele wählst du per Klick, Rechtsklick bricht ab.", "spell"],
["Fenn verliert bei 0 Leben. Tausche im ersten Zug einer Partie schwache Starthände (Mulligan). Viel Erfolg!", ""],
]
const STEPS2 := [
["Mulligan: Im ersten Zug darfst du einmal Handkarten zurücklegen und neu ziehen. Entscheide in der Maske, was du behältst.", "mulligan"],
["Fallen liegen verdeckt und lösen im gegnerischen Zug aus. Lege die Frostfalle, sobald du 2 Energie hast.", "trap_set"],
["Beende den Zug und warte. Greift Mara dich oder ein Wesen an, verpufft der Angriff und ihr Wesen friert ein.", "trap_fired"],
["Feldkombo Dampf: Ein Wasser-Wesen neben einem Feuer-Wesen friert das stärkste gegnerische Wesen ein. Spiele beide nacheinander.", "combo_dampf"],
["Rückgängig (U) nimmt ein gerade gespieltes Wesen zurück, solange nichts ausgelöst wurde. Viel Erfolg gegen Mara!", ""],
]
var screen # BattleScreen
var part := 1
var step := 0
func _init(s, p: int = 1) -> void:
screen = s
part = p
func steps() -> Array:
return STEPS1 if part == 1 else STEPS2
func stage_index() -> int:
return 0 if part == 1 else 1
## Teil 1: ein Wesen für 1 Energie auf der Hand, damit der erste Schritt immer möglich ist.
## Teil 2: Lehrkarten auf die eigene Hand; Mara bekommt Wesen, damit sie sicher in die Falle läuft.
func inject_cards(g: Game) -> void:
if part == 1 and not g.human.hand.any(func(c): return c.is_creature() and c.cost == 1):
g.human.hand.append(Cards.by_id("f_kobold"))
if part == 2:
g.human.hand.append(Cards.by_id("w_falle"))
g.human.hand.append(Cards.by_id("w_reiter"))
for i in 2:
g.opponents[0].hand.append(Cards.by_id("w_reiter"))
func condition(kind: String) -> bool:
var g: Game = screen.game
var h: Game.Player = g.human
match kind:
"played_creature":
return g.log.any(func(l): return l.begins_with("Du spielt")) and h.board.size() > 0
"turn_passed":
return h.turns >= 2
"attacked":
return g.log.any(func(l): return l.begins_with(h.board[0].name + " greift") if not h.board.is_empty() else false) \
or g.log.any(func(l): return l.ends_with("greift Fenn an."))
"resonance":
return h.board.any(func(c): return c.resonance > 0)
"combo":
return g.log.any(func(l): return l.begins_with("Feldkombo"))
"spell":
return g.log.any(func(l): return l == "Du spielt Funke." or l == "Du spielt Feuerball." or l == "Du spielt Verhärten.")
"mulligan":
return h.mulligan_done
"trap_set":
return g.log.any(func(l): return l == "Du legt eine Falle.")
"trap_fired":
return g.log.any(func(l): return l.begins_with("Falle von Du"))
"combo_dampf":
return g.log.any(func(l): return l.begins_with("Feldkombo Dampf"))
return false
func advance() -> void:
while step < steps().size() - 1 and condition(steps()[step][1]):
step += 1
func next() -> bool:
if step < steps().size() - 1:
step += 1
return true
return false
func text() -> String:
return "%d/%d %s" % [step + 1, steps().size(), screen.tr(steps()[step][0])]
func finished() -> bool:
return step >= steps().size() - 1
## Rechtecke, die der aktuelle Schritt hervorhebt.
func highlight_rects(mine: bool) -> Array[Rect2]:
var rects: Array[Rect2] = []
if not mine or screen.any_overlay():
return rects
var g: Game = screen.game
var h: Game.Player = screen.perspective
match steps()[step][1]:
"played_creature":
for v in screen.hand_views:
if v.card.is_creature() and g.can_play(h, v.card)[0]:
rects.append(Rect2(v.position, v.size))
"turn_passed", "trap_fired":
rects.append(screen.b_end.get_rect())
"attacked":
for v in screen.own_views:
if v.creature.can_attack():
rects.append(Rect2(v.slot, v.size))
if screen.own_views.any(func(v): return v.creature.can_attack()):
for o in screen.others():
if o.alive() and screen.hero_panels.has(o):
rects.append(screen.hero_panels[o].get_rect())
"resonance":
for v in screen.hand_views:
var c: Cards.Card = v.card
if c.is_creature() and g.can_play(h, c)[0] and h.board.any(func(b): return b.card.elements.any(func(e): return e in c.elements)):
rects.append(Rect2(v.position, v.size))
"combo":
for v in screen.hand_views:
var c: Cards.Card = v.card
if c.is_creature() and g.can_play(h, c)[0] and h.board.any(func(b): return b.card.elements.any(func(e): return not (e in c.elements))):
rects.append(Rect2(v.position, v.size))
"spell":
for v in screen.hand_views:
if v.card.kind == "Zauber" and g.can_play(h, v.card)[0]:
rects.append(Rect2(v.position, v.size))
"trap_set":
for v in screen.hand_views:
if v.card.is_trap() and g.can_play(h, v.card)[0]:
rects.append(Rect2(v.position, v.size))
"combo_dampf":
var has_fire: bool = h.board.any(func(b): return "Feuer" in b.card.elements)
for v in screen.hand_views:
var c: Cards.Card = v.card
if not c.is_creature() or not g.can_play(h, c)[0]:
continue
if (has_fire and "Wasser" in c.elements) or (not has_fire and "Feuer" in c.elements):
rects.append(Rect2(v.position, v.size))
return rects