- 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
150 lines
6.6 KiB
GDScript
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
|