update
Some checks failed
Tests / headless (push) Has been cancelled
Tests / fenster-schnell (push) Has been cancelled
Tests / fenster-voll (push) Has been cancelled
Tests / balance (push) Has been cancelled

This commit is contained in:
Team3
2026-09-28 19:44:30 +02:00
parent dfcd3cb713
commit f0a8f18de8
37 changed files with 1588 additions and 880 deletions

View File

@@ -26,6 +26,7 @@ func frames(n: int = 2) -> void:
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"))
@@ -142,18 +143,15 @@ func _ready() -> void:
for suffix in ["", ".bak", ".corrupt"]:
if FileAccess.file_exists(SAVE + suffix):
DirAccess.remove_absolute(abs + suffix)
if FileAccess.file_exists(SAVE.get_basename() + "_kampf.json"):
DirAccess.remove_absolute(abs.get_basename() + "_kampf.json")
main = load("res://scenes/main.tscn").instantiate()
main.save_path = SAVE
add_child(main)
await frames(3)
var fast := OS.get_environment("DINGER_FAST") != ""
var suite := [test_menu, test_gamepad_menue, test_booster, test_gegnerauswahl, test_einstellungen,
test_kampagne, test_sammlung, test_sprache, test_kampf_steuerung, test_kampf_feld_und_verschmelzen, test_kampf_grenzen,
test_kampf_fortsetzen]
test_kampagne, test_sammlung, test_sprache, test_kampf_steuerung, test_kampf_feld_und_verschmelzen, test_kampf_grenzen]
if not fast:
suite += [test_kampf.bind(2, 3), test_kampf_abbrechen]
suite += [test_kampf.bind(3), test_kampf_abbrechen]
# Die Netzwerktests starten Bot-Prozesse und warten auf Verbindungen; das dauert Minuten.
# Sie laufen nur auf Wunsch: DINGER_NETZ=1 (oder gezielt über DINGER_ONLY).
if OS.get_environment("DINGER_NETZ") != "":
@@ -254,7 +252,7 @@ func human_turn(scr) -> void:
"' Zug Mensch=", g.current().is_human, " kann=", cr.can_attack(), " pending=", scr.pending, " selected=", scr.selected)
check(scr.selected == cr, "Wesen gewählt")
var targets := []
for o in g.opponents:
for o in [g.opponent]:
if o.alive() and g.can_attack(cr, o)[0]:
targets.append(o)
for c in o.board:
@@ -267,11 +265,9 @@ func human_turn(scr) -> void:
await click(scr.b_end)
func start_battle(n_opp: int) -> void:
func start_battle() -> void:
await click(find_button("Einzelspieler"))
check(screen() is OpponentSelectScreen, "Gegnerauswahl")
screen().count_slider.value = n_opp
await frames(1)
await click(find_button("Starten"))
@@ -279,51 +275,33 @@ 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("1 Gegner") == null and find_button("Starten") != null, "kein Würfeln und keine Gegnerknöpfe, nur Starten")
check(find_button("Feuer") == null and find_button("Luft") == null, "keine Elementwahl mehr")
check(sel.count_slider.min_value == 1 and sel.count_slider.max_value == Game.MAX_OPPONENTS and sel.count_slider.step == 1, "Gegnerzahl als Schieberegler von 1 bis %d" % Game.MAX_OPPONENTS)
check(sel.opponents() == 1 and sel.count_label.text == "1 Gegner" and "Duell" in sel.info_label.text, "Regler beginnt beim Duell")
var rect: Rect2 = sel.count_slider.get_global_rect()
await click_at(Vector2(rect.end.x - 4, rect.get_center().y)) # ganz rechts anklicken
check(sel.opponents() == Game.MAX_OPPONENTS and sel.count_label.text == "%d Gegner" % Game.MAX_OPPONENTS, "Mausklick am rechten Rand stellt %d Gegner ein" % Game.MAX_OPPONENTS)
sel.count_slider.grab_focus()
await key(KEY_LEFT)
check(sel.opponents() == Game.MAX_OPPONENTS - 1, "Pfeiltaste links nimmt einen Gegner weg")
sel.count_slider.value = 4
await frames(1)
check(sel.opponents() == 4 and sel.count_label.text == "4 Gegner" and "5 am Tisch" in sel.info_label.text
and str(Profile.REWARD_WIN[4]) in sel.info_label.text, "Regler auf 4: fünf am Tisch mit Belohnung")
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(str(Profile.DIFFICULTIES.schwer.life) in sel.info_label.text and str(int(Profile.REWARD_WIN * 1.5)) in sel.info_label.text, "Leben des Gegners und Belohnung stehen dabei")
await shot("07_gegnerauswahl")
sel.count_slider.value = 1
await frames(1)
await click(find_button("Starten"))
var hard = screen()
check(hard is BattleScreen and hard.game.opponents.size() == 1, "Kampf mit einem Gegner gestartet")
var opp: Game.Player = hard.game.opponents[0]
check(hard is BattleScreen and hard.game.players.size() == 2, "Duell gestartet")
var opp: Game.Player = hard.game.opponent
check(opp.max_life == Profile.DIFFICULTIES.schwer.life and hard.smart_ai, "Schwer: Gegner mit %d Leben, Vorausschau-KI" % Profile.DIFFICULTIES.schwer.life)
check(hard.game.human.shop.size() == Game.SHOP_SIZE and hard.game.human.hand.is_empty(),
"Start mit vollem Laden und leerer Hand")
check(hard.hero_panels[opp].position == BattleLayout.OPP_BOX.position, "der Gegner steht oben in der Mitte")
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.opponents[0]
opp = easy.game.opponent
check(opp.max_life == Profile.DIFFICULTIES.leicht.life and not easy.smart_ai, "Leicht: Gegner mit %d Leben, einfache KI" % Profile.DIFFICULTIES.leicht.life)
check(easy.game.players[0].max_energy == 1 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ü")
for n in [1, 5]:
await start_battle(n)
var scr = screen()
check(scr is BattleScreen and scr.game.opponents.size() == n, "Kampf gegen %d gestartet" % n)
check(scr.game.players[0].max_energy == 1 and scr.game.opponents[0].life == Game.START_LIFE, "gleiche Regeln gegen %d" % n)
await shot("11_kampf_%d" % n)
await give_up()
## Kampf verlassen: nach Spielende über den Ergebnisdialog, sonst per Aufgeben.
@@ -348,32 +326,6 @@ func give_up() -> void:
check(screen() is MenuScreen, "Aufgeben führt ins Menü")
## Kampf speichern, im Menü fortsetzen, Aufgeben löscht die Datei.
func test_kampf_fortsetzen() -> void:
await start_battle(1)
var scr = screen()
scr.ai_delay = 0.0
await wait_for_human(scr)
var kampf: String = main.battle_save_path()
check(FileAccess.file_exists(kampf), "Kampf wird laufend gespeichert")
await click(scr.b_end)
await wait_for_human(scr)
var turn: int = scr.game.turn
var life: int = scr.game.human.life
var hand: int = scr.game.human.hand.size()
await click(find_button("Aufgeben"))
await click(find_button("Speichern und zurück"))
check(screen() is MenuScreen and find_button("Kampf fortsetzen") != null, "Speichern führt ins Menü mit Fortsetzen-Knopf")
await click(find_button("Kampf fortsetzen"))
var scr2 = screen()
check(scr2 is BattleScreen and scr2.game.turn == turn and scr2.game.human.life == life and scr2.game.human.hand.size() == hand
and scr2.game.current() == scr2.game.human, "Kampf fortgesetzt mit gleichem Zug, Leben und Hand")
scr2.ai_delay = 0.0
await shot("30_kampf_fortgesetzt")
await give_up()
check(not FileAccess.file_exists(kampf) and find_button("Kampf fortsetzen") == null, "Aufgeben löscht den gespeicherten Kampf")
func test_einstellungen() -> void:
await click(find_button("Einstellungen"))
var scr = screen()
@@ -414,6 +366,14 @@ func test_sammlung() -> void:
check(scr.selected.id == "ff_phoenix" and p.rarity("ff_phoenix") == "gewöhnlich", "Karte gewählt, noch gewöhnlich")
await click(scr.b_upgrade)
check(p.level("ff_phoenix") == 1 and p.dust == 5000 - Profile.DUST_UPGRADE["selten"], "auf selten aufgewertet")
scr.search_box.text = ""
scr.search_box.text_changed.emit("")
await frames(1)
var first: CardView = scr.grid.views()[0]
check(first.card.id == "ff_phoenix" and first.rarity == "selten", "die aufgewertete Karte steht vorn in der Sammlung")
scr.search_box.text = "Phönix"
scr.search_box.text_changed.emit("Phönix")
await frames(1)
check(Profile.load_or_new(SAVE).level("ff_phoenix") == 1, "Sammlung gespeichert")
var dust: int = p.dust
await click(scr.b_dismantle)
@@ -491,7 +451,7 @@ func test_sprache() -> void:
## Steuerung im Kampf: Tastatur, Rückgängig, Gamepad, Überspringen, Esc.
func test_kampf_steuerung() -> void:
await start_battle(1)
await start_battle()
var scr = screen()
check(scr is BattleScreen, "Kampf gestartet")
scr.ai_delay = 0.0
@@ -551,7 +511,7 @@ func test_kampf_steuerung() -> void:
## Grenzen im Kampf: volle Hand, volles Feld, kein Mana.
func test_kampf_grenzen() -> void:
await start_battle(1)
await start_battle()
var scr = screen()
scr.ai_delay = 0.0
await wait_for_human(scr)
@@ -603,26 +563,45 @@ func test_kampf_grenzen() -> void:
## Feldanzeige und Verschmelzen im Kampf.
func test_kampf_feld_und_verschmelzen() -> void:
await start_battle(1)
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()
for id in ["f_wolf", "w_reiter"]:
var cr := Game.Creature.new(Cards.by_id(id), g.human, g.next_uid)
var put := func(p: Game.Player, id: String) -> Game.Creature:
var cr := Game.Creature.new(Cards.by_id(id), p, g.next_uid)
g.next_uid += 1
g.human.board.append(cr)
cr.ready = true
cr.attacks_left = 1
p.board.append(cr)
return cr
put.call(g.human, "f_wolf")
put.call(g.human, "w_reiter")
var falke: Game.Creature = put.call(scr.others()[0], "l_falke")
g.human.hand = [Cards.by_id("f_kobold")]
g.human.energy = 10
g.field_charge["Wasser"] = 2
scr.refresh()
await frames(2)
check(scr.field_view.text() == "Feuer 0/4 · Wasser 2/4 · Erde 0/4 · Luft 0/4", "neutrales Feld zeigt die Ladung: " + scr.field_view.text())
for i in Cards.FIELD_CHARGE:
g.charge_field(Cards.by_id("f_kobold"))
scr.refresh()
await frames(2)
check(scr.field_label.text.begins_with("Vulkan"), "Feldanzeige nennt das Feld: " + scr.field_label.text)
check(scr.field_view.text().begins_with("Vulkan: +1 Angriff"), "Feldanzeige nennt das Feld: " + scr.field_view.text())
check(g.human.board[0].field_bonus == 1 and g.human.board[0].power() == 4, "das Feuerwesen bekommt +1 Angriff")
check(scr.hand_views[0].bonus == 2 and scr.hand_views[0].tag == "Dampf", "die Handkarte zeigt +2 (Resonanz und Feld) und die Kombo Dampf")
check(scr.b_fuse.visible and scr.b_fuse.text == "Verschmelzen", "Verschmelzen wird angeboten")
await shot("23_feld")
await hover(scr.hand_views[0])
check(scr.tiles[g.human.board[1].uid].mark == "Dampf" and "Dampf" in scr.info_label.text, "beim Zeigen auf die Handkarte ist der Kombo-Partner markiert")
await click(scr.own_views[0])
check(scr.selected == g.human.board[0] and scr.tiles[falke.uid].matchup == 1, "Angreifer gewählt: das Luftwesen zeigt +1 für Feuer")
await shot("23b_angriffsziel")
await right_click() # Auswahl wieder aufheben
check(scr.selected == null and scr.tiles[falke.uid].matchup == 0, "Auswahl aufgehoben, Vorteil verschwindet")
await click(scr.b_fuse)
check(scr.fusing and scr.b_fuse.text == "Abbrechen", "Verschmelzen ausgewählt")
await click(scr.own_views[0])
@@ -834,13 +813,15 @@ func test_kampagne() -> void:
var scr = screen()
check(scr is CampaignScreen, "Kampagne geöffnet")
await shot("15_kampagne")
check(find_button("Gesperrt") != null and find_button("Kämpfen") != null, "Stufen gesperrt und offen")
check(scr.node_buttons.filter(func(b): return b.disabled).size() == Campaign.STAGES.size() - 1 and find_button("Kämpfen") != null, "neun Stationen gesperrt, die erste offen")
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.opponents.size() == 1 and battle.game.opponents[0].name == "Fenn", "Fenn ist der Gegner")
check(battle.game.opponents[0].perks == ["feuer_angriff"] and battle.game.log.any(func(l): return l.begins_with("Fenn: Feuerwesen")), "Boss-Merkmal aktiv")
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
@@ -854,7 +835,7 @@ func test_kampagne() -> void:
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.opponents[0].perks.size() == 2, "Kapitel-2-Boss mit zwei Merkmalen")
check(b2 is BattleScreen and b2.stage == 10 and b2.game.opponent.perks.size() == 2 and b2.game.opponent.life == 30, "Kapitel-2-Boss mit zwei Merkmalen und 30 Leben")
check(b2.game.human.perks == [main.profile.perk], "eigenes Merkmal im Kampf")
await give_up()
main.profile.campaign_stage = 0
@@ -862,13 +843,13 @@ func test_kampagne() -> void:
main.save()
func test_kampf(n_opp: int, shot_turn: int) -> void:
await start_battle(n_opp)
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.opponents.size() == n_opp and g.log[0].begins_with("Zugreihenfolge"), "Spiel gestartet, Reihenfolge ausgelost")
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")
@@ -898,12 +879,12 @@ func test_kampf(n_opp: int, shot_turn: int) -> void:
func test_kampf_abbrechen() -> void:
await start_battle(5)
await start_battle()
var scr = screen()
scr.ai_delay = 0.0
var g: Game = scr.game
await wait_for_human(scr)
check(g.opponents.size() == 5 and g.human.max_energy == 1, "5 Gegner")
check(g.opponent != null and g.human.max_energy == 1, "Duell läuft")
# Kartenvorschau: aufgewertete Karten zeigen ihre Seltenheit
g.human.energy = 10
var wolf := Game.Creature.new(Cards.by_id("f_wolf"), g.human, 9000)
@@ -913,7 +894,7 @@ func test_kampf_abbrechen() -> void:
scr.refresh()
var reiter_view = scr.hand_views.filter(func(v): return v.card.id == "w_reiter")[0]
check(reiter_view.rarity == "episch", "Handkarte trägt die eigene Stufe")
await hover(scr.log_box) # erst weg von der Hand, damit das Betreten sicher ausgelöst wird
await hover(scr.field_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 == "episch", "Vorschau ebenfalls")
@@ -930,11 +911,10 @@ func test_kampf_abbrechen() -> void:
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 click(find_button("Lupe"))
check(scr.zoom_player == scr.others()[0], "Lupe öffnet die erste Spalte")
await shot("12_lupe")
await click(find_button("Schließen"))
check(scr.zoom_player == null, "Lupe geschlossen")
await click(scr.b_log)
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 click(scr.b_end)
@@ -946,4 +926,4 @@ func test_kampf_abbrechen() -> void:
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_fuenf")
await shot("10_kampf_runden")