1122 lines
58 KiB
GDScript
1122 lines
58 KiB
GDScript
extends Node
|
||
## Oberflächentest mit echtem Fenster und Screenshots:
|
||
## DINGER_SHOTS=/pfad/zum/ordner godot --path . tests/ui_test.tscn
|
||
|
||
const SAVE := "user://test_ui_spielstand.json"
|
||
const DEVICE := 7 # Gerätenummer der Testereignisse; alles andere fängt der InputFilter ab
|
||
|
||
var main: Control
|
||
var shots := ""
|
||
var passed := 0
|
||
var failed := 0
|
||
|
||
|
||
func check(cond: bool, msg: String) -> void:
|
||
if cond:
|
||
passed += 1
|
||
else:
|
||
failed += 1
|
||
printerr("FEHLER: " + msg)
|
||
print("%d Prüfungen bestanden, Abbruch beim ersten Fehler" % passed)
|
||
get_tree().quit(1)
|
||
|
||
|
||
## Lässt nur Ereignisse des Tests durch; echte Maus und Tastatur des Nutzers werden abgefangen,
|
||
## bevor Knöpfe oder Bildschirme sie sehen. Sitzt als letzter Knoten im Baum und bekommt _input
|
||
## darum als Erster.
|
||
class InputFilter extends Node:
|
||
func _input(event: InputEvent) -> void:
|
||
if event.device != DEVICE:
|
||
get_viewport().set_input_as_handled()
|
||
|
||
|
||
## Testereignis an das Fenster, mit der eigenen Gerätenummer.
|
||
func push(ev: InputEvent) -> void:
|
||
ev.device = DEVICE
|
||
get_viewport().push_input(ev)
|
||
|
||
|
||
func frames(n: int = 2) -> void:
|
||
for i in n:
|
||
await get_tree().process_frame
|
||
|
||
|
||
func shot(name: String) -> void:
|
||
if shots == "":
|
||
return
|
||
await frames(10) # Einblenden und Austeilen abwarten
|
||
await RenderingServer.frame_post_draw
|
||
get_viewport().get_texture().get_image().save_png(shots.path_join(name + ".png"))
|
||
|
||
|
||
func click_at(pos: Vector2) -> void:
|
||
var down := InputEventMouseButton.new()
|
||
down.position = pos
|
||
down.global_position = pos
|
||
down.button_index = MOUSE_BUTTON_LEFT
|
||
down.pressed = true
|
||
push(down)
|
||
var up: InputEventMouseButton = down.duplicate()
|
||
up.pressed = false
|
||
push(up)
|
||
await frames(2)
|
||
|
||
|
||
func click(node: Control) -> void:
|
||
check(node != null, "Klickziel vorhanden")
|
||
if node != null:
|
||
await click_at(node.get_global_rect().get_center())
|
||
|
||
|
||
func right_click() -> void:
|
||
await right_click_at(Vector2(5, 5))
|
||
|
||
|
||
func double_click_at(pos: Vector2) -> void:
|
||
var down := InputEventMouseButton.new()
|
||
down.position = pos
|
||
down.global_position = pos
|
||
down.button_index = MOUSE_BUTTON_LEFT
|
||
down.pressed = true
|
||
down.double_click = true
|
||
push(down)
|
||
var up: InputEventMouseButton = down.duplicate()
|
||
up.pressed = false
|
||
up.double_click = false
|
||
push(up)
|
||
await frames(2)
|
||
|
||
|
||
func right_click_at(pos: Vector2, button: int = MOUSE_BUTTON_RIGHT) -> void:
|
||
var down := InputEventMouseButton.new()
|
||
down.position = pos
|
||
down.global_position = pos
|
||
down.button_index = button
|
||
down.pressed = true
|
||
push(down)
|
||
var up: InputEventMouseButton = down.duplicate()
|
||
up.pressed = false
|
||
push(up)
|
||
await frames(2)
|
||
|
||
|
||
func key(code: int) -> void:
|
||
var down := InputEventKey.new()
|
||
down.keycode = code
|
||
down.pressed = true
|
||
push(down)
|
||
var up := InputEventKey.new()
|
||
up.keycode = code
|
||
up.pressed = false
|
||
push(up)
|
||
await frames(2)
|
||
|
||
|
||
func pad(button: int) -> void:
|
||
var ev := InputEventJoypadButton.new()
|
||
ev.button_index = button
|
||
ev.pressed = true
|
||
push(ev)
|
||
await frames(2)
|
||
|
||
|
||
## Mulligan-Maske wegklicken (Hand behalten), falls sie offen ist.
|
||
func hover(node: Control) -> void:
|
||
var ev := InputEventMouseMotion.new()
|
||
ev.position = node.get_global_rect().get_center()
|
||
ev.global_position = ev.position
|
||
push(ev)
|
||
await frames(2)
|
||
|
||
|
||
func screen():
|
||
return main.screen
|
||
|
||
|
||
## Jeder Abschnitt startet im Menü, auch wenn ein vorheriger abgebrochen ist.
|
||
func ensure_menu() -> void:
|
||
if TranslationServer.get_locale().begins_with("en"):
|
||
main.set_language("de")
|
||
if not (screen() is MenuScreen):
|
||
main.show_screen(MenuScreen.new())
|
||
await frames(2)
|
||
|
||
|
||
func find_button(text: String, root: Node = null) -> Button:
|
||
if root == null:
|
||
root = screen()
|
||
for c in root.get_children():
|
||
if c is Button and (c.text == text or c.tr(c.text) == text) and c.is_visible_in_tree():
|
||
return c
|
||
var found := find_button(text, c)
|
||
if found != null:
|
||
return found
|
||
return null
|
||
|
||
|
||
## Wartet, bis keine Kachel mehr animiert wird (Angriffe verschieben Kacheln kurz).
|
||
func settle(scr) -> void:
|
||
for i in 40:
|
||
if not scr.tiles.values().any(func(t): return t.busy):
|
||
return
|
||
await frames(1)
|
||
|
||
|
||
func wait_for_human(scr) -> void:
|
||
var g: Game = scr.game
|
||
var wait := 0
|
||
while g.current() != g.human and g.winner == "" and wait < 600:
|
||
await frames(1)
|
||
wait += 1
|
||
await frames(1)
|
||
|
||
|
||
func _ready() -> void:
|
||
Game.test_seed = 4242 # wiederholbare Partien und Booster
|
||
shots = OS.get_environment("DINGER_SHOTS")
|
||
var abs := ProjectSettings.globalize_path(SAVE)
|
||
for suffix in ["", ".bak", ".corrupt"]:
|
||
if FileAccess.file_exists(SAVE + suffix):
|
||
DirAccess.remove_absolute(abs + suffix)
|
||
main = load("res://scenes/main.tscn").instantiate()
|
||
main.save_path = SAVE
|
||
add_child(main)
|
||
add_child(InputFilter.new()) # nach main, damit er im Baum zuletzt steht
|
||
main.sfx.muted = true # Tests bleiben still
|
||
await frames(3)
|
||
var suite := [test_start, test_menu, test_gamepad_menue, test_booster, test_gegnerauswahl, test_einstellungen,
|
||
test_kampagne, test_sammlung, test_skilltree, test_cheats, test_werkstatt, test_deck, test_sprache, test_kampf_steuerung, test_kampf_elemente_und_verschmelzen, test_kampf_grenzen]
|
||
suite += [test_kampf.bind(3), test_kampf_abbrechen]
|
||
suite.append(test_profil_zuruecksetzen)
|
||
var only := OS.get_environment("DINGER_ONLY") # z. B. DINGER_ONLY=test_sammlung,test_kampf
|
||
if only != "":
|
||
suite = suite.filter(func(t: Callable): return t.get_method() in only.split(","))
|
||
for t in suite:
|
||
await ensure_menu()
|
||
await t.call()
|
||
for suffix in ["", ".bak", ".corrupt"]:
|
||
if FileAccess.file_exists(SAVE + suffix):
|
||
DirAccess.remove_absolute(abs + suffix)
|
||
print("%d Prüfungen bestanden, %d fehlgeschlagen" % [passed, failed])
|
||
get_tree().quit(1 if failed > 0 else 0)
|
||
|
||
|
||
## Neues Profil: die neutralen Karten als Sammlung und Stapel, direkt ins Menü.
|
||
func test_start() -> void:
|
||
main.reset_profile()
|
||
await frames(2)
|
||
var p: Profile = main.profile
|
||
check(screen() is MenuScreen and p.collection.size() == 110 and p.deck.size() == Game.DECK_MIN and p.deck.all(func(id): return Cards.by_id(id).is_neutral()), "neues Profil: 110 Neutrale, 30 im Stapel")
|
||
await shot("00_start")
|
||
check(Profile.load_or_new(SAVE).collection.size() == 110, "gespeichert")
|
||
|
||
|
||
func test_menu() -> void:
|
||
check(screen() is MenuScreen, "Menü sichtbar")
|
||
check(find_button("Einzelspieler") != null and find_button("Beenden") != null, "Menüknöpfe vorhanden")
|
||
await shot("01_menu")
|
||
|
||
|
||
func test_booster() -> void:
|
||
await click(find_button("Booster öffnen"))
|
||
var scr = screen()
|
||
check(scr is BoosterScreen, "Boosterbildschirm")
|
||
var coins: int = main.profile.coins
|
||
await click(scr.b_buy)
|
||
check(main.profile.coins == coins - Profile.BOOSTER_COST, "Booster bezahlt")
|
||
check(scr.cards.size() == 5 and scr.revealed.is_empty(), "5 verdeckte Karten")
|
||
await shot("03_booster_zu")
|
||
var backs: Array = scr.slots.get_children().filter(func(c): return c is BoosterScreen.CardBack)
|
||
await click(backs[0])
|
||
check(scr.revealed.size() == 1, "eine Karte aufgedeckt")
|
||
await click(scr.b_all)
|
||
check(scr.revealed.size() == 5, "alle aufgedeckt")
|
||
await shot("04_booster_offen")
|
||
check(Profile.load_or_new(SAVE).coins == main.profile.coins, "Kauf gespeichert")
|
||
for res in scr.cards:
|
||
if res.new or res.upgraded:
|
||
check(main.profile.owns(res.card.id) and main.profile.level(res.card.id) == Cards.RARITIES.find(res.rarity), "neue oder aufgewertete Karte trägt ihre Stufe")
|
||
else:
|
||
check(res.dust > 0, "sonst gibt es Staub")
|
||
check(scr.b_buy.disabled == (main.profile.coins < Profile.BOOSTER_COST), "Kaufknopf folgt dem Münzstand")
|
||
check(not scr.pack_tiles["Feuer"].visible and scr.pack_tiles[Cards.NEUTRAL].visible and scr.pack_tiles[Cards.NEUTRAL].position.x == 530, "Feuer-Pack bis Kapitel 2 unsichtbar, Neutral mittig")
|
||
check(scr.summary_label.text.ends_with("Staub") and scr.pack_tiles[Cards.NEUTRAL].pulse, "Fazit steht, das gekaufte Pack pulsiert")
|
||
var saved_coins: int = main.profile.coins
|
||
main.profile.campaign_stage = Campaign.STAGES_PER_CHAPTER
|
||
main.profile.coins = Profile.ELEMENT_BOOSTER_COST
|
||
scr.refresh()
|
||
check(scr.pack_tiles["Feuer"].visible and not scr.pack_buttons["Feuer"].disabled and "200" in scr.pack_tiles["Feuer"].hint and not scr.pack_tiles["Wasser"].visible, "Kapitel 2 offen: Feuer kaufbar, Wasser unsichtbar")
|
||
await click(scr.pack_buttons["Feuer"])
|
||
check(main.profile.coins == 0 and scr.cards.size() == 5 and scr.cards.all(func(res): return res.card.elements[0] == "Feuer"), "Feuer-Booster: fünf Feuerkarten")
|
||
await click(scr.b_all)
|
||
await shot("04b_booster_feuer")
|
||
main.profile.coins = Profile.BOOSTER_COST * 5
|
||
scr.refresh()
|
||
check(not scr.pack_tiles[Cards.NEUTRAL].buy_buttons[1].disabled and scr.pack_tiles[Cards.NEUTRAL].buy_buttons[2].disabled, "×5 bezahlbar, ×10 nicht")
|
||
await click(scr.pack_tiles[Cards.NEUTRAL].buy_buttons[1])
|
||
check(scr.cards.size() == 25 and main.profile.coins == 0 and scr.revealed.is_empty() and scr.slots.get_children().filter(func(c): return c is BoosterScreen.CardBack).size() == 25, "fünf Packs auf einmal: 25 verdeckte Karten")
|
||
check(Cards.RARITIES.find(scr.cards[0].rarity) >= Cards.RARITIES.find(scr.cards[24].rarity), "beste Stufe zuerst")
|
||
await frames(30)
|
||
await shot("04c_booster_fuenf_zu")
|
||
await click(scr.b_all)
|
||
check(scr.revealed.size() == 25 and scr.summary_label.text.begins_with(str(scr.cards.filter(func(x): return x.new).size())) and "Staub" in scr.summary_label.text, "alle aufgedeckt, Fazit zählt neu und Staub")
|
||
await frames(40)
|
||
await shot("04d_booster_fuenf_offen")
|
||
main.profile.campaign_stage = 0
|
||
main.profile.coins = saved_coins
|
||
main.save()
|
||
await click(find_button("Zurück"))
|
||
check(screen() is MenuScreen, "zurück im Menü")
|
||
|
||
|
||
func target_view(scr, valid: Array) -> Control:
|
||
for v in scr.enemy_views:
|
||
if v.creature in valid:
|
||
return v
|
||
for v in scr.own_views:
|
||
if v.creature in valid:
|
||
return v
|
||
for p in scr.hero_panels:
|
||
if p in valid:
|
||
return scr.hero_panels[p]
|
||
return null
|
||
|
||
|
||
## Zieht eine Karte oder Kachel auf eine andere und lässt sie dort los; danach wartet das Ergebnis
|
||
## über dem Ziel auf accept_fuse oder „Abbrechen“.
|
||
func drag_fuse(scr, from: Control, to: Control) -> void:
|
||
await frames(20) # Kacheln sind noch unterwegs (Einfliegen, Nachrücken)
|
||
await settle(scr)
|
||
var a := from.get_global_rect().get_center()
|
||
var b := to.get_global_rect().get_center()
|
||
var fo = from.card if from is CardView else from.creature
|
||
var to_o = to.card if to is CardView else to.creature
|
||
var down := InputEventMouseButton.new()
|
||
down.position = a
|
||
down.global_position = a
|
||
down.button_index = MOUSE_BUTTON_LEFT
|
||
down.pressed = true
|
||
push(down)
|
||
await frames(1)
|
||
for i in range(1, 6):
|
||
var m := InputEventMouseMotion.new()
|
||
m.position = a.lerp(b, i / 5.0)
|
||
m.global_position = m.position
|
||
m.button_mask = MOUSE_BUTTON_MASK_LEFT
|
||
push(m)
|
||
await frames(1)
|
||
if scr.drag_ghost == null or scr.drag_target == null:
|
||
print("DEBUG drag: ghost=", scr.drag_ghost, " target=", scr.drag_target, " press=", scr.press_target, " dran=", scr.my_turn(),
|
||
" overlay=", scr.any_overlay(), " mana=", scr.game.human.energy, " kann=", scr.game.can_fuse(scr.game.human, fo, to_o), " from=", from.get_global_rect(), " to=", to.get_global_rect())
|
||
check(scr.drag_ghost != null and scr.drag_target == to_o and to.drop, "Karte schwebt über einem Verschmelzungsziel, das golden gerahmt ist")
|
||
var up: InputEventMouseButton = down.duplicate()
|
||
up.pressed = false
|
||
up.position = b
|
||
up.global_position = b
|
||
push(up)
|
||
await frames(2)
|
||
var want: Cards.Card = Game.fusion_result(scr.game.human, fo, to_o)
|
||
var shown: Array = scr.menu_layer.get_children().filter(func(c): return c is CardView)
|
||
check(scr.fuse_pending == [fo, to_o] and scr.menu_layer.visible and shown.size() == 1 and shown[0].card.name == want.name and shown[0].card.attack == want.attack and shown[0].card.health == want.health, "nach dem Loslassen schwebt %s (%d/%d) über dem Ziel und wartet auf Bestätigung" % [want.name, want.attack, want.health])
|
||
check(shown[0].position.y + shown[0].size.y < to.get_global_rect().position.y and not to.drop, "das Ergebnis steht über dem Ziel, der goldene Rahmen ist weg")
|
||
|
||
|
||
## Bestätigt die offene Verschmelzungsfrage; die neue Karte gleitet von oben in die Hand.
|
||
func accept_fuse(scr) -> void:
|
||
var pair: Array = scr.fuse_pending
|
||
check(pair != null, "eine Verschmelzung wartet")
|
||
var before: Array = scr.game.human.hand.duplicate()
|
||
var btn := find_button("Verschmelzen", scr.menu_layer)
|
||
check(btn != null, "Knopf Verschmelzen")
|
||
var at := btn.get_global_rect().get_center()
|
||
var down := InputEventMouseButton.new()
|
||
down.position = at
|
||
down.global_position = at
|
||
down.button_index = MOUSE_BUTTON_LEFT
|
||
down.pressed = true
|
||
push(down)
|
||
var up: InputEventMouseButton = down.duplicate()
|
||
up.pressed = false
|
||
push(up)
|
||
await frames(1)
|
||
var fresh: Array = scr.hand_views.filter(func(v): return not (v.card in before))
|
||
check(fresh.size() == 1 and (fresh[0].position.y < BattleLayout.HAND_Y - 40 or not scr.fx.enabled), "die neue Karte gleitet von oben in die Hand")
|
||
await frames(2)
|
||
|
||
|
||
## Legt, was bezahlbar ist.
|
||
func play_hand(scr) -> void:
|
||
var g: Game = scr.game
|
||
for i in 12:
|
||
var playable: Array = scr.hand_views.filter(func(v): return g.can_play(g.human, v.card)[0])
|
||
if playable.is_empty():
|
||
break
|
||
var card: Cards.Card = playable[0].card
|
||
var board_before: int = g.human.board.size()
|
||
await click(playable[0])
|
||
check(g.human.board.size() > board_before, "Karte gespielt: " + card.name)
|
||
|
||
|
||
func human_turn(scr) -> void:
|
||
var g: Game = scr.game
|
||
# erst legen, was bezahlbar ist, dann mit dem Rest ziehen (und wieder legen)
|
||
for round in 2:
|
||
await play_hand(scr)
|
||
for i in 6:
|
||
if g.winner != "" or not g.can_draw(g.human)[0]:
|
||
break
|
||
var mana: int = g.human.energy
|
||
var hand: int = g.human.hand.size()
|
||
var cost: int = g.draw_cost(g.human)
|
||
await click(scr.deck)
|
||
check(g.human.energy == mana - cost and g.human.hand.size() == hand + 1, "gezogen: eine Karte")
|
||
var tried := [] # Wesen ohne lohnendes Ziel (0 Angriff ohne Vorteil) nicht wieder versuchen
|
||
for i in 12:
|
||
var ready: Array = scr.own_views.filter(func(v): return v.creature.can_attack() and not (v.creature in tried))
|
||
if ready.is_empty() or g.winner != "":
|
||
break
|
||
var cr: Game.Creature = ready[0].creature
|
||
var any_target: bool = g.can_attack(cr, g.opponent)[0] or g.opponent.board.any(func(c): return g.can_attack(cr, c)[0])
|
||
if not any_target:
|
||
tried.append(cr)
|
||
continue
|
||
await settle(scr)
|
||
await hover(ready[0])
|
||
var under = get_viewport().gui_get_hovered_control()
|
||
await click(ready[0])
|
||
if scr.selected != cr:
|
||
print("DEBUG Auswahl fehlgeschlagen: rect=", ready[0].get_global_rect(), " scale=", ready[0].scale,
|
||
" busy=", ready[0].busy, " unter Maus=", under, " Meldung='", scr.msg_label.text,
|
||
"' Zug Mensch=", g.current().is_human, " kann=", cr.can_attack(), " selected=", scr.selected)
|
||
check(scr.selected == cr, "Wesen gewählt")
|
||
var targets := []
|
||
for o in [g.opponent]:
|
||
if o.alive() and g.can_attack(cr, o)[0]:
|
||
targets.append(o)
|
||
for c in o.board:
|
||
if g.can_attack(cr, c)[0]:
|
||
targets.append(c)
|
||
await settle(scr)
|
||
await click(target_view(scr, targets))
|
||
check(scr.selected == null and cr.attacks_done > 0, "Angriff ausgeführt")
|
||
if g.winner == "":
|
||
await key(KEY_SPACE)
|
||
|
||
|
||
func start_battle() -> void:
|
||
await click(find_button("Einzelspieler"))
|
||
check(screen() is OpponentSelectScreen, "Gegnerauswahl")
|
||
await click(find_button("Starten"))
|
||
|
||
|
||
func test_gegnerauswahl() -> void:
|
||
await click(find_button("Einzelspieler"))
|
||
var sel = screen()
|
||
check(sel is OpponentSelectScreen, "Gegnerauswahl geöffnet")
|
||
check(find_button("Neu würfeln") == null and find_button("Feuer") == null and find_button("Starten") != null, "nur Schwierigkeit und Starten")
|
||
await click(find_button("Schwer"))
|
||
check(main.profile.settings.difficulty == "schwer" and Profile.load_or_new(SAVE).settings.difficulty == "schwer", "Schwer gewählt und gespeichert")
|
||
check("Rare" in sel.info_label.text and "Merkmal" in sel.info_label.text and "×2" in sel.info_label.text, "Schwer: Gegnerkarten Rare, Merkmal und Münzfaktor stehen dabei: " + sel.info_label.text)
|
||
await shot("07_gegnerauswahl")
|
||
await click(find_button("Starten"))
|
||
var hard = screen()
|
||
check(hard is BattleScreen and hard.game.players.size() == 2, "Duell gestartet")
|
||
var opp: Game.Player = hard.game.opponent
|
||
check(opp.max_life == Game.START_LIFE and hard.smart_ai and opp.perks.size() == 1 and opp.looks.values().all(func(l): return l == 1) and opp.title == Cards.NEUTRAL, "Schwer: 30 Leben, Vorausschau-KI, ein Merkmal, Neutral in Rare")
|
||
check(hard.game.human.hand.size() == Game.START_HAND and hard.hand_views.size() == Game.START_HAND, "Start: fünf Karten auf der Hand")
|
||
var mine: Array = main.profile.deck_cards()
|
||
check(hard.game.human.hand.all(func(c): return c.id in mine), "die Starthand kommt ungefiltert vom eigenen Stapel")
|
||
await shot("05_starthand")
|
||
check(hard.hero_panels[opp].position == BattleLayout.OPP_BOX.position, "der Gegner steht links oben")
|
||
await shot("11_kampf")
|
||
await give_up()
|
||
await click(find_button("Einzelspieler"))
|
||
await click(find_button("Leicht"))
|
||
await click(find_button("Starten"))
|
||
var easy = screen()
|
||
opp = easy.game.opponent
|
||
check(opp.max_life == Game.START_LIFE and not easy.smart_ai and opp.perks.is_empty() and opp.looks.values().all(func(l): return l == 0), "Leicht: 30 Leben, einfache KI, kein Merkmal, Common")
|
||
check(easy.game.players[0].max_energy == Game.START_MANA and easy.game.human.life == Game.START_LIFE, "gleiche Regeln für beide")
|
||
await give_up()
|
||
await click(find_button("Einzelspieler"))
|
||
await click(find_button("Normal"))
|
||
await click(find_button("Zurück"))
|
||
check(screen() is MenuScreen, "zurück im Menü")
|
||
|
||
|
||
## Kampf verlassen: nach Spielende über den Ergebnisdialog, sonst per Aufgeben.
|
||
func leave_battle() -> void:
|
||
var scr = screen()
|
||
if scr is BattleScreen and scr.game != null and scr.game.winner != "":
|
||
await frames(2)
|
||
await click(find_button("Zurück zum Menü"))
|
||
check(screen() is MenuScreen, "Ergebnisdialog führt ins Menü")
|
||
else:
|
||
await give_up()
|
||
|
||
|
||
func give_up() -> void:
|
||
var scr = screen()
|
||
await key(KEY_ESCAPE)
|
||
check(scr.options.visible, "Esc öffnet die Optionen")
|
||
await click(find_button("Aufgeben", scr.options))
|
||
check(scr.confirm.visible and not scr.options.visible, "Aufgeben fragt nach")
|
||
await click(find_button("Weiterspielen"))
|
||
check(not scr.confirm.visible and screen() == scr, "Weiterspielen bleibt im Kampf")
|
||
await key(KEY_ESCAPE)
|
||
await click(find_button("Aufgeben", scr.options))
|
||
await click(find_button("Ja, aufgeben"))
|
||
check(screen() is MenuScreen, "Aufgeben führt ins Menü")
|
||
|
||
|
||
func test_einstellungen() -> void:
|
||
await click(find_button("Einstellungen"))
|
||
var scr = screen()
|
||
check(scr is SettingsScreen, "Einstellungen geöffnet")
|
||
scr.volume.value = 0.3
|
||
scr.speed.value = 2.0
|
||
scr.anim.button_pressed = false
|
||
await frames(1)
|
||
check(is_equal_approx(main.profile.settings.volume, 0.3) and is_equal_approx(main.profile.settings.ai_speed, 2.0), "Regler gespeichert")
|
||
check(main.profile.settings.animations == false, "Animationen abschaltbar")
|
||
check(is_equal_approx(Profile.load_or_new(SAVE).settings.volume, 0.3), "Einstellungen auf Platte")
|
||
scr.anim.button_pressed = true
|
||
scr.big_font.button_pressed = true
|
||
await frames(2)
|
||
check(main.profile.settings.big_font and Style.font_scale > 1.0 and screen() is SettingsScreen, "große Schrift aktiv")
|
||
screen().big_font.button_pressed = false
|
||
await frames(2)
|
||
check(is_equal_approx(Style.font_scale, 1.0), "normale Schrift")
|
||
var scr_b = screen()
|
||
await shot("13_einstellungen")
|
||
scr = scr_b
|
||
await click(find_button("Zurück"))
|
||
check(screen() is MenuScreen, "zurück im Menü")
|
||
|
||
|
||
## Skilltree: Level und Punkte aus der Erfahrung, Lernen verbraucht Punkte, Knöpfe folgen dem Stand.
|
||
func test_skilltree() -> void:
|
||
main.profile.xp = Skills.xp_for_level(3)
|
||
main.profile.skills = {}
|
||
main.save()
|
||
await click(find_button("Skilltree"))
|
||
var scr = screen()
|
||
check(scr is SkillScreen and scr.level_label.text == "Level 3" and scr.points_label.text == "Skillpunkte: 3" and scr.xp_label.text == "0 / 400 EP bis Level 4" and scr.xp_bar.value == 0 and scr.xp_bar.max_value == 400, "Skilltree: Level 3, drei Punkte, Balken leer")
|
||
check(scr.nodes.size() == Skills.SKILLS.size() and scr.learn_buttons["packglueck"].visible and not scr.learn_buttons["garantie"].visible and scr.state_labels["garantie"].text == "Erst Packglück lernen" and not scr.nodes["garantie"].open, "erste Knoten lernbar, Garantie gesperrt")
|
||
check(scr.now_labels["packglueck"].text == "Jetzt: Chancen 45/25/15/10/5 %" and scr.next_labels["packglueck"].text == "Nächste: Chancen 37/28/17/12/6 %", "Wirkung jetzt und nächste Stufe in Zahlen")
|
||
await click(scr.learn_buttons["packglueck"])
|
||
check(main.profile.skill("packglueck") == 1 and scr.points_label.text == "Skillpunkte: 2" and scr.nodes["packglueck"].rank == 1 and scr.learn_buttons["garantie"].visible and scr.nodes["garantie"].open, "Packglück gelernt, Garantie offen")
|
||
await click(scr.learn_buttons["gratisziehen"])
|
||
check(not scr.learn_buttons["gratisziehen"].visible and scr.state_labels["gratisziehen"].text == "Gelernt" and scr.next_labels["gratisziehen"].text == "", "Freies Ziehen voll: Gelernt statt Knopf")
|
||
await click(scr.learn_buttons["startmana"])
|
||
check(scr.points_label.text == "Skillpunkte: 0" and not scr.learn_buttons["garantie"].visible and scr.state_labels["garantie"].text == "Kein Skillpunkt frei", "ohne Punkte kein Knopf")
|
||
await shot("28_skilltree")
|
||
check(Profile.load_or_new(SAVE).skill("packglueck") == 1, "gespeichert")
|
||
await click(find_button("Zurück"))
|
||
check(screen() is MenuScreen and screen().coll_label.text.ends_with("Level 3"), "zurück im Menü, Level steht dabei")
|
||
main.profile.xp = 0
|
||
main.profile.skills = {}
|
||
main.save()
|
||
|
||
|
||
## Cheats: Knöpfe setzen Münzen, Level, Kapitel und Sammlung sofort und speichern.
|
||
func test_cheats() -> void:
|
||
await click(find_button("Cheats"))
|
||
var scr = screen()
|
||
check(scr is CheatScreen and scr.status_label.text.begins_with("Münzen %d" % main.profile.coins), "Cheats geöffnet, Stand steht oben")
|
||
var coins: int = main.profile.coins
|
||
await click(find_button("+1.000 Münzen"))
|
||
check(main.profile.coins == coins + 1000 and Profile.load_or_new(SAVE).coins == coins + 1000 and scr.msg_label.text == "Gespeichert", "+1.000 Münzen, gespeichert")
|
||
await click(find_button("+1 Level"))
|
||
check(main.profile.player_level() == 1 and main.profile.skill_points() == 1, "+1 Level: ein Skillpunkt")
|
||
await click(find_button("Kapitel 3 frei"))
|
||
check(main.profile.campaign_stage == 20 and main.profile.unlocked_elements().size() == 3, "Kapitel 3 frei: Neutral, Feuer, Wasser")
|
||
await click(find_button("Alle Karten Ultimate Rare"))
|
||
check(main.profile.collection.size() == Cards.all().size() and main.profile.level("f_5_5") == 4, "alle 550 Karten in Ultimate Rare")
|
||
await shot("29_cheats")
|
||
await click(find_button("Sammlung zurücksetzen"))
|
||
check(main.profile.collection.size() == 110 and main.profile.deck.size() == Game.DECK_MIN, "Sammlung wieder am Start")
|
||
await click(find_button("Kampagne zurücksetzen"))
|
||
await click(find_button("Erfahrung auf 0"))
|
||
check(main.profile.campaign_stage == 0 and main.profile.xp == 0, "Kampagne und Erfahrung zurück")
|
||
await click(find_button("Zurück"))
|
||
check(screen() is MenuScreen, "zurück im Menü")
|
||
|
||
|
||
## Werkstatt: nach Kapitel 5 im Menü; Elemente, Werte, Eigenschaften und Name ergeben Vorschau und Preis.
|
||
func test_werkstatt() -> void:
|
||
check(find_button("Werkstatt") == null, "vor Kapitel 5 keine Werkstatt im Menü")
|
||
main.profile.campaign_stage = 5 * Campaign.STAGES_PER_CHAPTER
|
||
main.profile.dust = 1000
|
||
main.save()
|
||
main.show_screen(MenuScreen.new())
|
||
await frames(2)
|
||
check(find_button("Werkstatt") != null, "nach Kapitel 5 steht die Werkstatt im Menü")
|
||
await click(find_button("Werkstatt"))
|
||
var scr = screen()
|
||
check(scr is WorkshopScreen and scr.dust_label.text == "Staub: 1000" and scr.name_box.text == "Eigenbau 1", "Werkstatt geöffnet, Vorschlagsname")
|
||
await click(scr.element_buttons["Feuer"])
|
||
await click(scr.element_buttons["Wasser"])
|
||
scr.attack_slider.value = 3
|
||
scr.health_slider.value = 2
|
||
await click(scr.keyword_boxes["Spott"])
|
||
scr.name_box.text = "Dampfwächter"
|
||
scr.name_box.text_changed.emit("Dampfwächter")
|
||
await frames(1)
|
||
check(scr.elements == ["Feuer", "Wasser"] and scr.keywords == ["Spott"] and scr.info_label.text == "Kosten 4 Mana · Wert 200 · Preis 500 Staub", "Vorschau: Kosten 4, Wert 200, Preis 500: " + scr.info_label.text)
|
||
check(scr.preview_holder.get_child(0).card.name == "Dampfwächter" and scr.preview_holder.get_child(0).card.cost == 4, "Vorschaukarte zeigt Name und Kosten")
|
||
await click(scr.element_buttons["Erde"])
|
||
await click(scr.keyword_boxes["Doppelschlag"])
|
||
await click(scr.keyword_boxes["Flink"])
|
||
check(scr.elements == ["Feuer", "Wasser", "Erde"] and scr.keywords == ["Spott", "Flink", "Doppelschlag"] and scr.info_label.text == "Kosten 9 Mana · Wert 400 · Preis 1000 Staub", "drei Elemente, drei Eigenschaften in fester Reihenfolge: " + scr.info_label.text)
|
||
await shot("30_werkstatt")
|
||
await click(scr.element_buttons["Erde"])
|
||
await click(scr.keyword_boxes["Doppelschlag"])
|
||
await click(scr.keyword_boxes["Flink"])
|
||
check(scr.elements == ["Feuer", "Wasser"] and scr.keywords == ["Spott"], "wieder abgewählt")
|
||
await click(find_button("Bauen"))
|
||
var p: Profile = main.profile
|
||
check(p.owns("c_1") and p.dust == 500 and "c_1" in p.deck and scr.msg_label.text == "Gebaut: Dampfwächter" and scr.name_box.text == "Eigenbau 2", "gebaut: 500 Staub weg, Karte in Sammlung und Stapel")
|
||
check(Profile.load_or_new(SAVE).owns("c_1") and Cards.by_id("c_1").keywords == ["Spott"] and Cards.by_id("c_1").elements == ["Feuer", "Wasser"], "gespeichert")
|
||
await click(find_button("Zurück"))
|
||
check(screen() is MenuScreen, "zurück im Menü")
|
||
main.profile.campaign_stage = 0
|
||
main.save()
|
||
|
||
|
||
## Deck: eigene Karten rein und raus, Auffüllen nach Filter, Speichern. Beginnt mit einem frischen Profil.
|
||
func test_deck() -> void:
|
||
main.reset_profile()
|
||
await frames(2)
|
||
await click(find_button("Deck"))
|
||
var scr = screen()
|
||
check(scr is DeckScreen, "Deck geöffnet")
|
||
var p: Profile = main.profile
|
||
check(scr.grid.views().size() == p.collection.size() and scr.count_label.text == "Stapel: %d Karten (%d bis %d)" % [p.deck.size(), Game.DECK_MIN, Game.DECK_MAX], "alle eigenen Karten, Zähler stimmt")
|
||
check(scr.strength_label.text == "Stärke 100", "Startsammlung aus Grundkarten: Stärke 100")
|
||
p.collection[p.deck[0]] = 4
|
||
scr.refresh()
|
||
await frames(2)
|
||
check(scr.strength_label.text == "Stärke %d" % roundi((29 * 100 + 500) / 30.0), "eine Ultimate Rare hebt die Stärke: " + scr.strength_label.text)
|
||
check(scr.grid.views()[0].card.id == p.deck[0] and scr.grid.views()[0].rarity == "Ultimate Rare", "höchste Stufe steht vorn")
|
||
p.collection[p.deck[0]] = 0
|
||
scr.refresh()
|
||
await frames(2)
|
||
var views: Array = scr.grid.views()
|
||
check(views.size() == 110 and views.slice(0, Game.DECK_MIN).all(func(v): return not v.dim and v.marked) and views[Game.DECK_MIN].dim and not views[Game.DECK_MIN].marked and p.deck.size() == Game.DECK_MIN, "Stapelkarten vorn mit Haken, der Rest dunkel")
|
||
check(scr.curve.counts.size() == 11 and scr.curve.counts.reduce(func(a, v): return a + v, 0) == Game.DECK_MIN and scr.curve.counts[0] == 2, "Manakurve: 30 Karten verteilt, zwei zu 0 Mana")
|
||
check(scr.share.counts == {Cards.NEUTRAL: Game.DECK_MIN} and scr.share.shown() == [Cards.NEUTRAL], "Elementanteile: nur Neutral")
|
||
check(views[0].card.cost <= views[1].card.cost and views[Game.DECK_MIN].card.cost <= views[Game.DECK_MIN + 1].card.cost, "innerhalb von Stapel und Rest nach Kosten")
|
||
await click(scr.cost_buttons[3])
|
||
views = scr.grid.views()
|
||
check(scr.cost_filter == 3 and views.size() == 15 and views.all(func(v): return v.card.cost == 3), "Manafilter 3: nur die 15 Karten zu 3 Mana")
|
||
await click(scr.cost_buttons[3])
|
||
views = scr.grid.views()
|
||
check(scr.cost_filter == -1 and views.size() == 110, "derselbe Knopf hebt den Manafilter auf")
|
||
check(scr.element_buttons.size() == 5 and scr.element_buttons["Feuer"].tooltip_text == "Feuer" and not scr.element_buttons["Feuer"].visible and scr.element_buttons[Cards.NEUTRAL].visible and scr.element_buttons[Cards.NEUTRAL].position.x == 108, "Elementfilter als Symbolknöpfe, nur offene Elemente")
|
||
await hover(views[2])
|
||
check(scr.preview_holder.get_child_count() == 1 and scr.preview_holder.get_child(0).card.id == views[2].card.id, "Zeigen stellt die Karte groß in die Vorschau")
|
||
var first_id: String = views[0].card.id
|
||
await click(views[0])
|
||
check(not (first_id in p.deck) and p.deck.size() == Game.DECK_MIN - 1 and scr.grid.views().any(func(v): return v.card.id == first_id and v.dim and not v.marked), "Klick nimmt die Karte aus dem Stapel, sie wird dunkel und verliert den Haken")
|
||
check(scr.curve.counts.reduce(func(a, v): return a + v, 0) == Game.DECK_MIN - 1 and scr.msg_label.text == "Klick auf eine Karte: rein oder raus.", "Manakurve folgt, Hinweis steht")
|
||
var back_view = scr.grid.views().filter(func(v): return v.card.id == first_id)[0]
|
||
scr.grid.ensure_control_visible(back_view) # sie liegt jetzt hinter den Stapelkarten, außerhalb des Sichtbereichs
|
||
await frames(2)
|
||
await click(back_view)
|
||
check(first_id in p.deck and p.deck.size() == Game.DECK_MIN, "Klick nimmt sie wieder hinein")
|
||
await shot("19_deck")
|
||
await click(find_button("Leeren"))
|
||
check(p.deck.is_empty(), "geleert")
|
||
for id in ["w_0_1", "w_0_2", "w_0_3", "w_0_4", "w_0_5"]:
|
||
p.collection[id] = 0
|
||
main.profile.campaign_stage = 2 * Campaign.STAGES_PER_CHAPTER # Wasser offen
|
||
scr.refresh()
|
||
await frames(1)
|
||
check(scr.element_buttons["Wasser"].visible and scr.element_buttons["Wasser"].position.x == 108 + 2 * 40, "Kapitel 3 offen: Wasserfilter an dritter Stelle")
|
||
await click(scr.element_buttons["Wasser"])
|
||
await click(find_button("Auffüllen"))
|
||
check(p.deck.size() == 5 and p.deck.all(func(id): return Cards.by_id(id).elements[0] == "Wasser"), "Auffüllen hält sich an den Filter: nur die fünf Wasserkarten")
|
||
check(scr.share.shown() == ["Wasser"] and scr.share.counts["Wasser"] == 5, "Elementanteile: fünf Wasser")
|
||
await click(find_button("Alle"))
|
||
await click(find_button("Auffüllen"))
|
||
check(p.deck.size() == Game.DECK_MAX and Profile.load_or_new(SAVE).deck == p.deck, "ohne Filter bis 60, gespeichert")
|
||
main.profile.campaign_stage = 0
|
||
main.save()
|
||
await click(find_button("Zurück"))
|
||
check(screen() is MenuScreen, "zurück im Menü")
|
||
|
||
|
||
func test_sammlung() -> void:
|
||
await click(find_button("Sammlung"))
|
||
var scr = screen()
|
||
check(scr is CollectionScreen, "Sammlung geöffnet")
|
||
var p: Profile = main.profile
|
||
check(scr.grid.views().size() == 110 and scr.grid.views().all(func(v): return v.card.is_neutral()), "nur die Karten offener Elemente: zu Beginn die 110 Neutralen")
|
||
check(find_button("Feuer") == null and find_button("Neutral") != null, "Filter nur für offene Elemente")
|
||
check(scr.grid.views().all(func(v): return not v.dim), "zu Beginn gehören dir alle gezeigten Karten")
|
||
p.dust = 5000
|
||
scr.search_box.text = "Tapferer Knappe"
|
||
scr.search_box.text_changed.emit("Tapferer Knappe")
|
||
await frames(1)
|
||
var funke = scr.grid.views().filter(func(v): return v.card.id == "n_2_1")[0]
|
||
await click(funke)
|
||
check(scr.selected.id == "n_2_1" and p.rarity("n_2_1") == "Common" and scr.b_upgrade.visible, "eigene Karte gewählt, noch Common")
|
||
await click(scr.b_upgrade)
|
||
check(p.level("n_2_1") == 1 and p.dust == 5000 - Profile.DUST_UPGRADE["Rare"], "auf Rare aufgewertet")
|
||
scr.search_box.text = ""
|
||
scr.search_box.text_changed.emit("")
|
||
await frames(1)
|
||
var front = scr.grid.views().filter(func(v): return v.card.id == "n_2_1")[0]
|
||
check(front.rarity == "Rare" and front.card.attack == 3 and front.card.health == 2 and scr.grid.views()[0].rarity != "Common", "die Rare zeigt +1/+1, aufgewertete stehen vorn: %s %d/%d" % [front.rarity, front.card.attack, front.card.health])
|
||
check(Profile.load_or_new(SAVE).level("n_2_1") == 1, "Sammlung gespeichert")
|
||
scr.search_box.text = "Tapferer Knappe"
|
||
scr.search_box.text_changed.emit("Tapferer Knappe")
|
||
await frames(1)
|
||
var dust: int = p.dust
|
||
await click(scr.b_dismantle)
|
||
check(p.level("n_2_1") == 0 and p.dust == dust + Profile.DUST_DISMANTLE["Rare"], "wieder zerlegt")
|
||
# Herstellen einer fehlenden Karte (Feuer: erst ab Kapitel 2 zu sehen)
|
||
scr.search_box.text = ""
|
||
scr.search_box.text_changed.emit("")
|
||
main.profile.campaign_stage = Campaign.STAGES_PER_CHAPTER
|
||
scr.refresh()
|
||
await frames(1)
|
||
var owned_shown: int = scr.grid.views().filter(func(v): return not v.dim).size()
|
||
check(scr.grid.views().size() == 220 and owned_shown < 220 and scr.grid.views().slice(0, owned_shown).all(func(v): return not v.dim) and scr.grid.views().slice(owned_shown).all(func(v): return v.dim), "Kapitel 2 offen: auch Feuer zu sehen, eigene vorn, fehlende abgedunkelt")
|
||
scr.search_box.text = "Infernalischer Drache"
|
||
scr.search_box.text_changed.emit("Infernalischer Drache")
|
||
await frames(1)
|
||
await click(scr.grid.views().filter(func(v): return v.card.id == "f_5_5")[0])
|
||
check(scr.b_craft.visible and not scr.b_upgrade.visible, "fehlende Karte: nur Herstellen")
|
||
dust = p.dust
|
||
await click(scr.b_craft)
|
||
check(p.owns("f_5_5") and p.dust == dust - Profile.CRAFT_COST, "hergestellt")
|
||
await shot("20_sammlung")
|
||
main.profile.campaign_stage = 0
|
||
main.save()
|
||
await click(find_button("Zurück"))
|
||
check(screen() is MenuScreen, "zurück im Menü")
|
||
|
||
|
||
func test_gamepad_menue() -> void:
|
||
check(get_viewport().gui_get_focus_owner() == null, "ohne Gamepad kein Fokus")
|
||
await pad(JOY_BUTTON_DPAD_DOWN)
|
||
var f := get_viewport().gui_get_focus_owner()
|
||
check(f is Button and main.pad_mode, "Gamepad setzt den Fokus auf einen Knopf")
|
||
var down := InputEventJoypadButton.new()
|
||
down.button_index = JOY_BUTTON_DPAD_DOWN
|
||
down.pressed = true
|
||
down.device = DEVICE
|
||
Input.parse_input_event(down)
|
||
await frames(2)
|
||
var f2 := get_viewport().gui_get_focus_owner()
|
||
check(f2 is Button and f2 != f, "Steuerkreuz wechselt den Knopf")
|
||
var target_text: String = f2.text
|
||
check(InputMap.action_get_events("ui_accept").any(func(e): return e is InputEventJoypadButton), "A ist auf ui_accept gemappt")
|
||
var accept := InputEventJoypadButton.new()
|
||
accept.button_index = JOY_BUTTON_A
|
||
accept.pressed = true
|
||
push(accept)
|
||
await frames(1)
|
||
var release := InputEventJoypadButton.new()
|
||
release.button_index = JOY_BUTTON_A
|
||
release.pressed = false
|
||
push(release)
|
||
await frames(3)
|
||
check(not (screen() is MenuScreen) or target_text == "Beenden", "A öffnet den fokussierten Bildschirm (%s)" % target_text)
|
||
await hover(main)
|
||
check(not main.pad_mode and get_viewport().gui_get_focus_owner() == null, "Maus nimmt den Fokus wieder")
|
||
|
||
|
||
func test_profil_zuruecksetzen() -> void:
|
||
await click(find_button("Einstellungen"))
|
||
await click(find_button("Profil zurücksetzen"))
|
||
var scr = screen()
|
||
check(scr.confirm.visible, "Rückfrage")
|
||
await click(find_button("Abbrechen"))
|
||
check(not scr.confirm.visible, "abgebrochen")
|
||
await click(find_button("Profil zurücksetzen"))
|
||
await click(find_button("Ja, zurücksetzen"))
|
||
check(screen() is MenuScreen and main.profile.coins == Profile.START_COINS and main.profile.collection.size() == 110 and main.profile.campaign_stage == 0, "Profil zurückgesetzt: neutrale Startsammlung, zurück im Menü")
|
||
check(Profile.load_or_new(SAVE).coins == Profile.START_COINS, "zurückgesetzt gespeichert")
|
||
|
||
|
||
func test_sprache() -> void:
|
||
await click(find_button("Einstellungen"))
|
||
var scr = screen()
|
||
scr.language.select(1)
|
||
scr.language.item_selected.emit(1)
|
||
await frames(2)
|
||
check(TranslationServer.get_locale().begins_with("en") and main.profile.settings.language == "en", "Sprache auf Englisch")
|
||
check(TranslationServer.translate("Zurück") == "Back" and TranslationServer.translate("Einzelspieler") == "Single player", "Übersetzung geladen")
|
||
check(TranslationServer.translate("Abwerfen +%d ◆") == "Discard +%d ◆" and TranslationServer.translate("Nicht genug Mana") == "Not enough mana", "dynamische Texte übersetzt")
|
||
check(find_button("Back") != null, "Knöpfe übersetzt")
|
||
await shot("26_englisch")
|
||
await click(find_button("Back"))
|
||
check(find_button("Single player") != null and find_button("Settings") != null, "Menü auf Englisch")
|
||
check(screen().coins_label.text.begins_with("Coins:"), "zusammengesetzte Texte übersetzt")
|
||
await click(find_button("Settings"))
|
||
var scr2 = screen()
|
||
scr2.language.select(0)
|
||
scr2.language.item_selected.emit(0)
|
||
await frames(2)
|
||
check(find_button("Zurück") != null, "zurück auf Deutsch")
|
||
await click(find_button("Zurück"))
|
||
check(screen() is MenuScreen, "zurück im Menü")
|
||
|
||
|
||
## Steuerung im Kampf: Tastatur, Rückgängig, Gamepad, Überspringen, Esc.
|
||
func test_kampf_steuerung() -> void:
|
||
await start_battle()
|
||
var scr = screen()
|
||
check(scr is BattleScreen, "Kampf gestartet")
|
||
scr.ai_delay = 0.0
|
||
await wait_for_human(scr)
|
||
var g: Game = scr.game
|
||
g.human.energy = 20 # die Starthand ist zufällig, hier soll nur die Steuerung geprüft werden
|
||
g.human.hand = [Cards.by_id("f_3_2").copy()]
|
||
scr.refresh()
|
||
check(scr.hand_views.size() == 1 and scr.deck.enabled, "eine Handkarte, Stapel bereit")
|
||
# Ziehen: eine Karte vom eigenen Stapel, das erste Ziehen ist frei
|
||
var mana: int = g.human.energy
|
||
var deck_before: int = g.human.deck.size()
|
||
await click(scr.deck)
|
||
check(g.human.energy == mana and g.human.hand.size() == 2 and scr.hand_views.size() == 2 and g.human.deck.size() == deck_before - 1, "gezogen: eine Karte, frei")
|
||
check(g.draw_cost(g.human) == 1 and scr.deck.cost == 1, "das nächste Ziehen kostet 1")
|
||
# Ausspielen kostet die Kartenkosten; das Wesen greift noch nicht an
|
||
var board_before: int = g.human.board.size()
|
||
mana = g.human.energy
|
||
await key(KEY_1)
|
||
check(g.human.board.size() == board_before + 1 and g.human.energy == mana - Cards.by_id("f_3_2").cost, "Wesen per Taste gespielt, Kosten bezahlt")
|
||
check(not g.human.board.back().can_attack(), "frisch gelegt greift es noch nicht an")
|
||
# Rückgängig: Wesen zurück auf die Hand, Mana zurück
|
||
check(scr.b_undo.visible, "Rückgängig angeboten")
|
||
await click(scr.b_undo)
|
||
g = scr.game
|
||
check(g.human.board.size() == board_before and g.human.hand.any(func(c): return c.id == "f_3_2") and g.human.energy == mana, "Rückgängig stellt Hand und Mana her")
|
||
check(not scr.b_undo.visible and g.log.back() == "Rückgängig.", "Rückgängig verbraucht")
|
||
await key(KEY_1)
|
||
check(g.human.board.size() == board_before + 1, "erneut gespielt")
|
||
# Ziehen per Taste
|
||
mana = g.human.energy
|
||
var hand_before: int = g.human.hand.size()
|
||
await key(KEY_Z)
|
||
check(g.human.energy == mana - 1 and g.human.hand.size() == hand_before + 1, "Z zieht, das zweite Ziehen für 1")
|
||
await shot("22_kampf_steuerung")
|
||
# Gamepad: A auf dem Stapel zieht
|
||
await pad(JOY_BUTTON_DPAD_LEFT)
|
||
check(scr.gp_zone == "deck" and scr.cursor_layer.get_child_count() == 1, "Gamepad-Zeiger auf dem Stapel")
|
||
hand_before = g.human.hand.size()
|
||
await pad(JOY_BUTTON_A)
|
||
check(g.human.hand.size() == hand_before + 1, "Gamepad A zieht")
|
||
scr.ai_delay = 0.75
|
||
await key(KEY_SPACE)
|
||
check(g.current() != g.human or g.turn >= 2, "Zug per Leertaste beendet")
|
||
await frames(2)
|
||
check(scr.b_skip.visible, "Überspringen-Knopf während des Gegnerzugs")
|
||
await click(scr.b_skip)
|
||
var w := 0
|
||
while g.current() != g.human and g.winner == "" and w < 120:
|
||
await frames(1)
|
||
w += 1
|
||
check(g.current() == g.human and w < 60, "Gegnerzug übersprungen (%d Bilder)" % w)
|
||
scr.ai_delay = 0.0
|
||
await key(KEY_ESCAPE)
|
||
check(scr.options.visible and find_button("Hauptmenü", scr.options) != null and find_button("Beenden", scr.options) != null, "Esc öffnet die Optionen")
|
||
await key(KEY_ESCAPE)
|
||
check(not scr.options.visible, "Esc schließt die Optionen")
|
||
# Einstellungen aus dem Kampf und zurück
|
||
await key(KEY_ESCAPE)
|
||
await click(find_button("Einstellungen", scr.options))
|
||
check(screen() is SettingsScreen and is_instance_valid(scr), "Einstellungen geöffnet, der Kampf bleibt erhalten")
|
||
await click(find_button("Zurück"))
|
||
check(screen() == scr and scr.game == g and not scr.options.visible, "zurück im selben Kampf")
|
||
await give_up()
|
||
|
||
|
||
## Grenzen im Kampf: volle Hand, volles Feld, kein Mana.
|
||
func test_kampf_grenzen() -> void:
|
||
await start_battle()
|
||
var scr = screen()
|
||
scr.ai_delay = 0.0
|
||
await wait_for_human(scr)
|
||
var g: Game = scr.game
|
||
g.human.energy = 100
|
||
scr.refresh()
|
||
await frames(1)
|
||
# Keine Grenze für Handkarten: acht weitere ziehen
|
||
var hand_before: int = g.human.hand.size()
|
||
for i in 8:
|
||
await click(scr.deck)
|
||
check(g.human.hand.size() == hand_before + 8 and scr.deck.enabled and scr.hand_views.size() == hand_before + 8, "acht Karten nachgezogen, Ziehen bleibt möglich")
|
||
# Feld voll spielen
|
||
var guard := 0
|
||
while g.human.board.size() < Game.MAX_BOARD and guard < 30:
|
||
guard += 1
|
||
if g.human.hand.is_empty():
|
||
await click(scr.deck)
|
||
await key(KEY_1)
|
||
check(g.human.board.size() == Game.MAX_BOARD, "Feld mit %d Wesen voll" % Game.MAX_BOARD)
|
||
if g.human.hand.is_empty():
|
||
await click(scr.deck)
|
||
await key(KEY_1)
|
||
check(scr.msg_label.text == tr("Dein Feld ist voll"), "Meldung bei vollem Feld: " + scr.msg_label.text)
|
||
await shot("25_kampf_voll")
|
||
# Verschmelzen per Ziehen macht wieder Platz
|
||
var before: int = g.human.board.size()
|
||
var hand_n: int = g.human.hand.size()
|
||
await drag_fuse(scr, scr.own_views[0], scr.own_views[1])
|
||
await accept_fuse(scr)
|
||
check(g.human.board.size() == before - 2 and g.human.hand.size() == hand_n + 1, "Verschmelzen macht zwei Plätze frei, das Ergebnis liegt auf der Hand")
|
||
# Ohne Mana kein Ziehen
|
||
g.human.energy = 0
|
||
g.human.draws = 1
|
||
scr.refresh()
|
||
await frames(1)
|
||
check(not scr.deck.enabled, "ohne Mana kein zweites Ziehen")
|
||
await give_up()
|
||
|
||
|
||
## Elementboni, verdeckt legen, Kartenmenü und Verschmelzen (per Ziehen und per Menü) im Kampf.
|
||
func test_kampf_elemente_und_verschmelzen() -> void:
|
||
await start_battle()
|
||
var scr = screen()
|
||
check(scr is BattleScreen, "Kampf gestartet")
|
||
scr.ai_delay = 0.0
|
||
await wait_for_human(scr)
|
||
var g: Game = scr.game
|
||
g.human.board.clear()
|
||
scr.opponent().board.clear() # was die KI im ersten Zug gelegt hat, stört die Paare
|
||
var put := func(p: Game.Player, id: String, hidden: bool = false) -> Game.Creature:
|
||
var cr := Game.Creature.new(Cards.by_id(id).copy(), p, g.next_uid)
|
||
g.next_uid += 1
|
||
cr.ready = true
|
||
cr.hidden = hidden
|
||
p.board.append(cr)
|
||
return cr
|
||
var wolf_cr: Game.Creature = put.call(g.human, "f_3_2")
|
||
wolf_cr.health = 4 # übersteht die Konter von Falke und Funke, damit der Test ihn weiter nutzen kann
|
||
wolf_cr.max_health = 4
|
||
put.call(g.human, "f_4_3")
|
||
put.call(g.human, "w_3_3")
|
||
var falke: Game.Creature = put.call(scr.opponent(), "l_3_1")
|
||
var secret: Game.Creature = put.call(scr.opponent(), "f_2_1", true)
|
||
g.human.hand = [Cards.by_id("f_2_1").copy(), Cards.by_id("l_1_1").copy()]
|
||
g.human.energy = 20
|
||
g.human.looks = {"w_3_3": 3}
|
||
scr.refresh()
|
||
await frames(2)
|
||
check(scr.element_view.text() == "Feuer 2 (Doppelschlag) · Wasser 1", "Liste: nur Elemente mit offenen Wesen, Feuerpaar: " + scr.element_view.text())
|
||
check(scr.opp_element_view.text() == "Luft 1", "die Liste des Gegners zählt nur seine offenen Wesen: " + scr.opp_element_view.text())
|
||
check(g.human.board[0].keywords() == ["Doppelschlag"] and scr.tiles[secret.uid].secret, "Feuerpaar gibt Doppelschlag; fremdes Verdecktes zeigt den Rücken")
|
||
# Rechtsklick auf die Handkarte: Menü mit Verdeckt legen, ohne Verschmelzen
|
||
await hover(scr.hand_views[0])
|
||
await right_click_at(scr.hand_views[0].get_global_rect().get_center())
|
||
check(scr.menu_layer.visible and find_button("Verdeckt legen ◆ %d" % g.human.hand[0].cost, scr.menu_layer) != null and find_button("Abwerfen +1 ◆", scr.menu_layer) != null, "Rechtsklick öffnet das Kartenmenü mit Verdeckt legen und Abwerfen")
|
||
check(find_button("Verschmelzen mit …", scr.menu_layer) == null, "kein Verschmelzen im Rechtsklick-Menü")
|
||
await shot("23_kartenmenue")
|
||
await right_click()
|
||
check(not scr.menu_layer.visible, "Rechtsklick schließt das Menü")
|
||
await right_click_at(Vector2(1150, 300)) # leere Fläche
|
||
check(scr.menu_layer.visible and find_button("Zug beenden", scr.menu_layer) != null, "Rechtsklick ins Leere bietet Zug beenden an")
|
||
await right_click()
|
||
# Mittlere Maustaste legt die Handkarte verdeckt
|
||
var mana: int = g.human.energy
|
||
await right_click_at(scr.hand_views[0].get_global_rect().get_center(), MOUSE_BUTTON_MIDDLE)
|
||
var mine_hidden: Game.Creature = g.human.board[3]
|
||
check(mine_hidden.hidden and g.human.energy == mana - 1 and scr.element_view.text().begins_with("Feuer 2 (Doppelschlag)"), "mittlere Maustaste legt verdeckt: zählt nicht als offen")
|
||
# Rechtsklick auf ein eigenes offenes Wesen: nichts zu wählen, also kein Menü
|
||
await right_click_at(scr.own_views[0].get_global_rect().get_center())
|
||
check(not scr.menu_layer.visible, "kein Menü ohne Einträge")
|
||
var wolf: Game.Creature = g.human.board[0]
|
||
while scr.own_views[3].busy:
|
||
await frames(1)
|
||
await click(scr.own_views[3])
|
||
check(mine_hidden.hidden and scr.msg_label.text == "Lodernder Funke kann erst im nächsten Zug aufgedeckt werden", "im Legezug nicht aufzudecken: " + scr.msg_label.text)
|
||
mine_hidden.ready = true
|
||
await click(scr.own_views[3])
|
||
check(not mine_hidden.hidden and scr.element_view.text().begins_with("Feuer 3 (Doppelschlag)"), "aufgedeckt: drei offene Feuer: " + scr.element_view.text())
|
||
check(wolf.power() == 3 and falke.power() == 3 and mine_hidden.can_attack(), "Werte bleiben die der Karten, aufgedeckt greift sofort an")
|
||
await shot("23b_drei_feuer")
|
||
# Angriffsziel: Feuer gegen Luft schlägt doppelt, der Falke kontert halb; verdeckte ohne Anzeige, Held gesperrt
|
||
await click(scr.own_views[0])
|
||
check(scr.selected == wolf and scr.tiles[falke.uid].matchup == -1 and scr.tiles[secret.uid].matchup == 0, "Angreifer gewählt: der Falke kontert halbiert, nichts gegen Verdeckte")
|
||
check(scr.tiles[falke.uid].shown_stats() == [2, falke.health], "der Falke zeigt seinen Konter gegen den Wolf: 3 halbiert, aufgerundet 2")
|
||
await hover(scr.tiles[falke.uid])
|
||
check(scr.tiles[falke.uid].doomed and not scr.own_views[0].doomed and scr.own_views[0].shown_stats()[0] == wolf.power() * 2, "über dem Falken: er stirbt, der Wolf nicht, der Wolf schlägt doppelt zu")
|
||
await shot("23c_angreifer_gewaehlt")
|
||
check(not g.can_attack(wolf, scr.opponent())[0], "Held nicht angreifbar, solange Wesen stehen")
|
||
await click(scr.tiles[secret.uid])
|
||
check(not secret.hidden and not (secret in scr.opponent().board), "Angriff deckt auf und tötet den Kobold")
|
||
check(wolf.attacks_left() == 1, "Doppelschlag: ein Angriff bleibt")
|
||
await double_click_at(scr.own_views[0].get_global_rect().get_center())
|
||
check(scr.msg_label.text == "Erst die Wesen" and scr.opponent().life == 30, "Doppelklick zielt auf das Leben, hier stehen noch Wesen: " + scr.msg_label.text)
|
||
# Verschmelzen per Ziehen: den Aschekrieger auf den Wellenreiter ziehen, erst abbrechen, dann bestätigen
|
||
mana = g.human.energy
|
||
var before: int = g.human.board.size()
|
||
await drag_fuse(scr, scr.own_views[1], scr.own_views[2])
|
||
await shot("24a_verschmelzen_frage")
|
||
await click(find_button("Abbrechen", scr.menu_layer))
|
||
check(scr.fuse_pending == null and not scr.menu_layer.visible and g.human.board.size() == before and g.human.energy == mana, "Abbrechen lässt beide Wesen und das Mana, wie sie waren")
|
||
await drag_fuse(scr, scr.own_views[1], scr.own_views[2])
|
||
await accept_fuse(scr)
|
||
var dampf_cards: Array = g.human.hand.filter(func(c): return c.name == "Dampf")
|
||
check(g.human.board.size() == before - 2 and dampf_cards.size() == 1 and g.human.energy == mana, "beide Wesen verlassen das Brett, Dampf liegt auf der Hand, kostenlos")
|
||
var dampf: Cards.Card = dampf_cards[0]
|
||
var dampf_views: Array = scr.hand_views.filter(func(v): return v.card == dampf)
|
||
check(dampf.look == 3 and dampf.attack == 8 and dampf.health == 7 and dampf.cost == 4 and dampf_views.size() == 1 and dampf_views[0].rarity == "Ultra Rare", "Dampf 5/4 für 4 Mana aus den Grundwerten, Stufe 3 des Reiters macht 8/7")
|
||
await shot("24_verschmelzen")
|
||
# Verschmelzen auf der Hand per Ziehen
|
||
g.human.hand = [Cards.by_id("f_2_1").copy(), Cards.by_id("f_2_1").copy(), Cards.by_id("l_1_1").copy()]
|
||
scr.refresh()
|
||
await frames(1)
|
||
await drag_fuse(scr, scr.hand_views[0], scr.hand_views[1])
|
||
await accept_fuse(scr)
|
||
check(g.human.hand.size() == 2 and g.human.hand[0].name == "Inferno" and g.human.hand[0].attack == 3 and g.human.hand[0].health == 2, "zwei Funken auf der Hand zu Inferno 3/2")
|
||
# Gamepad-Menü (Y) bietet Verdeckt legen und Verschmelzen mit …
|
||
scr.open_menu(g.human.hand[0], Rect2(scr.hand_views[0].position, scr.hand_views[0].size), true)
|
||
check(find_button("Verdeckt legen ◆ %d" % g.human.hand[0].cost, scr.menu_layer) != null and g.human.hand[0].cost == 2, "Gamepad-Menü: Verdeckt legen, Inferno kostet 2")
|
||
await click(find_button("Verschmelzen mit …", scr.menu_layer))
|
||
check(scr.fuse_first == g.human.hand[0], "Partner wählen")
|
||
await click(scr.hand_views[1])
|
||
check(g.human.hand.size() == 1 and g.human.hand[0].name == "Blitz", "Inferno und Hauch zu Blitz")
|
||
# Handkarte mit Wesen: Blitz auf den Kobold ziehen
|
||
var board_n: int = g.human.board.size()
|
||
await drag_fuse(scr, scr.hand_views[0], scr.own_views[0])
|
||
await accept_fuse(scr)
|
||
check(g.human.board.size() == board_n - 1 and g.human.hand.size() == 1 and g.human.hand[0].name == "Blitz" and g.human.hand[0].base_attack() == 4, "Handkarte und Wesen verschmelzen: der Kobold verlässt das Brett, Blitz 4/4 bleibt auf der Hand")
|
||
await give_up()
|
||
|
||
|
||
func test_kampagne() -> void:
|
||
await click(find_button("Kampagne"))
|
||
var scr = screen()
|
||
check(scr is CampaignScreen, "Kampagne geöffnet")
|
||
await shot("15_kampagne")
|
||
check(scr.node_buttons.filter(func(b): return b.disabled).size() == Campaign.STAGES_PER_CHAPTER - 1 and find_button("Kämpfen") != null, "neun Stationen gesperrt, die erste offen")
|
||
check(find_button("Kapitel 5") != null and find_button("Kapitel 5").disabled, "fünf Kapitel, das letzte gesperrt")
|
||
await click(scr.node_buttons[3])
|
||
check(scr.selected == 0, "gesperrte Station lässt sich nicht wählen")
|
||
check(find_button("Kapitel 2").disabled and scr.perk_box == null, "Kapitel 2 und Merkmal noch gesperrt")
|
||
await click(find_button("Kämpfen"))
|
||
var battle = screen()
|
||
check(battle is BattleScreen and battle.stage == 0, "Kampagnenkampf gestartet")
|
||
check(battle.game.opponent.name == "Fenn", "Fenn ist der Gegner")
|
||
check(battle.game.opponent.perks == ["feuer_angriff"] and battle.game.log.any(func(l): return l.begins_with("Fenn: Feuerwesen")), "Boss-Merkmal aktiv")
|
||
await shot("16_kampagne_kampf")
|
||
await give_up()
|
||
# Kapitel 1 als abgeschlossen setzen: Kapitel 2 und Merkmalwahl
|
||
main.profile.campaign_stage = Campaign.STAGES_PER_CHAPTER
|
||
await click(find_button("Kampagne"))
|
||
var scr2 = screen()
|
||
check(scr2.chapter == 2 and scr2.perk_box != null, "Kapitel 2 offen, Merkmal wählbar")
|
||
scr2.perk_box.select(1)
|
||
scr2.perk_box.item_selected.emit(1)
|
||
check(main.profile.perk == Game.PERKS.keys()[0], "Merkmal gewählt")
|
||
await shot("27_kampagne_kapitel2")
|
||
await click(find_button("Kämpfen"))
|
||
var b2 = screen()
|
||
check(b2 is BattleScreen and b2.stage == 10 and b2.game.opponent.perks.size() == 1 and b2.game.opponent.life == 22, "Kapitel-2-Gegner mit einem Merkmal und 22 Leben")
|
||
check(b2.game.opponent.deck.all(func(id): return Cards.by_id(id).elements[0] == "Feuer") and b2.game.opponent.looks.values().all(func(l): return l == 0), "Kapitel 2: nur Feuer in Common")
|
||
check(b2.game.human.perks == [main.profile.perk], "eigenes Merkmal im Kampf")
|
||
await give_up()
|
||
main.profile.campaign_stage = 0
|
||
main.profile.perk = ""
|
||
main.save()
|
||
|
||
|
||
func test_kampf(shot_turn: int) -> void:
|
||
await start_battle()
|
||
var scr = screen()
|
||
check(scr is BattleScreen, "Kampfbildschirm")
|
||
scr.ai_delay = 0.0
|
||
var g: Game = scr.game
|
||
check(g.players.size() == 2 and g.log[0].begins_with("Zugreihenfolge"), "Spiel gestartet, Reihenfolge ausgelost")
|
||
await wait_for_human(scr)
|
||
check(g.current() == g.human, "Spieler ist dran")
|
||
check(scr.hand_views.size() == g.human.hand.size(), "Handkarten sichtbar")
|
||
await shot("07_kampf_start")
|
||
# Kurze Partie: einfache KI, Gegner fast tot, drei bereite Drachen räumen auf
|
||
scr.smart_ai = false
|
||
g.opponent.life = 1
|
||
for i in 3:
|
||
var drache := Game.Creature.new(Cards.by_id("f_5_5").copy(), g.human, 9100 + i)
|
||
drache.ready = true
|
||
g.human.board.append(drache)
|
||
scr.refresh()
|
||
var coins: int = main.profile.coins
|
||
var guard := 0
|
||
while g.winner == "" and guard < 60:
|
||
guard += 1
|
||
var turn_before := g.turn
|
||
await human_turn(scr)
|
||
if g.winner != "":
|
||
break
|
||
check(g.current() != g.human or g.turn > turn_before, "Zug beendet")
|
||
var wait := 0
|
||
while g.current() != g.human and g.winner == "" and wait < 600:
|
||
await frames(1)
|
||
wait += 1
|
||
if guard == shot_turn:
|
||
await shot("08_kampf_zug%d" % (shot_turn + 1))
|
||
check(g.winner != "", "Spiel beendet")
|
||
await frames(3)
|
||
await shot("09_kampf_ende")
|
||
check(scr.reward > 0 and main.profile.coins >= coins + scr.reward, "Belohnung gutgeschrieben")
|
||
check(scr.fast_turns > 0 and scr.reward == Profile.battle_coins(g.winner == "human", g.human.turns, g.human.cards_played, scr.fast_turns, main.profile.difficulty().reward), "Münzen aus Zügen, Karten und schnellen Zügen")
|
||
check(scr.detail_label.visible and tr("%d Züge") % g.human.turns in scr.detail_label.text and "= " in scr.detail_label.text, "Aufschlüsselung im Ergebnisbild")
|
||
check(Profile.load_or_new(SAVE).coins == main.profile.coins, "Belohnung gespeichert")
|
||
await click(find_button("Zurück zum Menü"))
|
||
check(screen() is MenuScreen, "zurück im Menü")
|
||
|
||
|
||
func test_kampf_abbrechen() -> void:
|
||
await start_battle()
|
||
var scr = screen()
|
||
scr.ai_delay = 0.0
|
||
var g: Game = scr.game
|
||
await wait_for_human(scr)
|
||
check(g.opponent != null and g.human.max_energy == Game.START_MANA, "Duell läuft")
|
||
# Kartenvorschau: aufgewertete Karten zeigen ihre Seltenheit
|
||
g.human.energy = 10
|
||
var wolf := Game.Creature.new(Cards.by_id("f_3_2"), g.human, 9000)
|
||
wolf.ready = true
|
||
g.human.board.append(wolf)
|
||
g.human.hand.append(Cards.by_id("w_3_3"))
|
||
g.human.looks["w_3_3"] = Cards.RARITIES.find("Super Rare")
|
||
scr.refresh()
|
||
var reiter_view = scr.hand_views.filter(func(v): return v.card.id == "w_3_3")[0]
|
||
check(reiter_view.rarity == "Super Rare", "Handkarte trägt die eigene Stufe")
|
||
await hover(scr.element_view) # erst weg von der Hand, damit das Betreten sicher ausgelöst wird
|
||
await hover(reiter_view)
|
||
var previews = scr.preview_holder.get_children().filter(func(c): return c is CardView)
|
||
check(previews.size() == 1 and previews[0].rarity == "Super Rare", "Vorschau ebenfalls")
|
||
await shot("21_kampf_vorschau")
|
||
if not scr.own_views.is_empty():
|
||
await click(scr.own_views[0])
|
||
await right_click()
|
||
check(scr.selected == null, "Rechtsklick bricht ab")
|
||
await hover(scr.hand_views[0])
|
||
if scr.preview_holder.get_child_count() == 0:
|
||
print("DEBUG Hover: overlay=", scr.any_overlay(), " hand=", scr.hand_views.size(),
|
||
" dran=", g.current().name, " Sieger=", g.winner, " hovered=", get_viewport().gui_get_hovered_control())
|
||
check(scr.preview_holder.get_child_count() > 0, "Vorschau bei Hover")
|
||
await key(KEY_ESCAPE)
|
||
await click(find_button("Verlauf", scr.options))
|
||
check(scr.log_overlay.visible and scr.any_overlay(), "Verlauf öffnet als Überlagerung")
|
||
await key(KEY_ESCAPE)
|
||
check(not scr.log_overlay.visible, "Esc schließt den Verlauf")
|
||
# Runden laufen lassen, damit die Gegner ihre Züge machen
|
||
for round in 4:
|
||
await key(KEY_SPACE)
|
||
var wait := 0
|
||
while g.current() != g.human and g.winner == "" and wait < 600:
|
||
await frames(1)
|
||
wait += 1
|
||
var ai_attacked: bool = g.log.any(func(l): return l.begins_with("Gegner") and " spielt " in l) \
|
||
and g.log.any(func(l): return " greift " in l)
|
||
check(ai_attacked, "Gegner spielen Karten und greifen an")
|
||
check(scr.tiles.size() == g.players.reduce(func(acc, p): return acc + p.board.size(), 0), "eine Kachel je Wesen")
|
||
await shot("10_kampf_runden")
|