This commit is contained in:
team3
2026-09-16 12:09:33 +02:00
parent 9410d18ea5
commit 8a188c5d5c
29 changed files with 2016 additions and 564 deletions

View File

@@ -83,7 +83,7 @@ func _hex(c: int, r: int) -> Vector2:
func _shop_button(i: int) -> Button:
return main.shop_row.get_child(i) as Button
return main.shop.row.get_child(i) as Button
func _center(c: Control) -> Vector2:
@@ -109,7 +109,7 @@ func _free_shop_slot(p: PlayerState) -> int:
func _buy(p: PlayerState, element: int) -> void:
var slot := _free_shop_slot(p)
if slot < 0:
await _click(_center(main.btn_reroll))
await _click(_center(main.shop.btn_reroll))
slot = _free_shop_slot(p)
await _click(_center(_shop_button(slot)))
if main.picker.visible:
@@ -118,7 +118,16 @@ func _buy(p: PlayerState, element: int) -> void:
func _run() -> void:
await _wait(0.6)
print("\n[S] Startbildschirm")
check(main.overlay != null and not main.started, "Startbildschirm liegt über dem Spiel")
var start_btn := _find_button(main.overlay, "Spiel starten")
check(start_btn != null, "Knopf 'Spiel starten' ist da")
await _click(_center(start_btn))
check(main.started and main.overlay == null, "Klick startet das Spiel und schließt den Bildschirm")
main.game.timer_enabled = false
var p: PlayerState = main.game.human
check(main.top.lbl_next.text.begins_with("Nächster Gegner:"), "Kopfzeile nennt den nächsten Gegner: '%s'" % main.top.lbl_next.text)
check(main.game.human_opponent() != null, "Paarung steht schon in der Planung fest")
print("\n[A] Spezialisierung wird beim Kauf gewählt")
var gold0 := p.gold
@@ -137,6 +146,25 @@ func _run() -> void:
% [pool0, main.game.pool.element_left[Data.Element.FEUER]])
await _shot("bank_3d")
print("\n[A2] Rechtsklick kauft wie zuletzt")
check(p.last_element == Data.Element.FEUER, "letzte Wahl ist gemerkt")
var slot2 := _free_shop_slot(p)
if slot2 >= 0 and p.shop[slot2].element < 0:
var filled_before := 0
for x in p.bench:
if x != null:
filled_before += 1
await _click(_center(_shop_button(slot2)), MOUSE_BUTTON_RIGHT)
check(not main.picker.visible, "kein Popup beim Rechtsklick")
var filled_after := 0
var last_is_fire := false
for x in p.bench:
if x != null:
filled_after += 1
last_is_fire = x.elements[0] == Data.Element.FEUER
check(filled_after == filled_before + 1 and last_is_fire, "Avatar kam direkt als Feuer auf die Bank")
await _click(_bench(filled_after - 1), MOUSE_BUTTON_RIGHT)
print("\n[B] Drag & Drop Bank -> Board")
var a0: Avatar = p.bench[0]
await _drag(_bench(0), _hex(3, 1))
@@ -165,7 +193,7 @@ func _run() -> void:
check(main.board.selected_slot.is_empty()
or not BoardView3D._same_slot(main.board.selected_slot, main.board.hover_slot)
or true, "Auswahl stört die Zielmarkierung nicht")
check(_collect_text(main.info_box).contains("Feuer"),
check(_collect_text(main.info.info_box).contains("Feuer"),
"Info-Panel zeigt während des Ziehens den Avatar in der Hand")
await _shot("beim_ziehen")
_btn(_hex(3, 1), false, MOUSE_BUTTON_LEFT)
@@ -199,11 +227,11 @@ func _run() -> void:
print("\n[F] Klick ohne Ziehen wählt aus")
await _click(_hex(2, 0))
check(main.sel_slot.get("kind", "") == "board", "Board-Feld ist ausgewählt")
var info := _collect_text(main.info_box)
var info := _collect_text(main.info.info_box)
check(info.contains("Feuer"), "Info-Panel zeigt die Einheit")
await _click(_bench(7))
check(main.sel_slot.get("kind", "") == "bench", "Bankplatz ist ausgewählt")
check(_collect_text(main.info_box).contains("Wind"), "Info-Panel zeigt den Bank-Avatar")
check(_collect_text(main.info.info_box).contains("Wind"), "Info-Panel zeigt den Bank-Avatar")
await _shot("auswahl")
print("\n[G] Rechtsklick: Board zurück auf die Bank, Bank verkauft")
@@ -221,7 +249,7 @@ func _run() -> void:
print("\n[H] Shop zeigt nur eigene Elemente und Neutral")
var seen := {}
for i in 40:
await _click(_center(main.btn_reroll))
await _click(_center(main.shop.btn_reroll))
for e in p.shop:
seen[e.element] = true
var only_mine := true
@@ -232,19 +260,69 @@ func _run() -> void:
check(seen.has(-1), "Neutral bleibt immer im Angebot")
check(seen.has(Data.Element.FEUER), "das eigene Feuer taucht auf")
print("\n[H2] Shop einfrieren")
await _click(_center(main.shop.btn_lock))
check(p.shop_locked, "Einfrieren ist aktiv")
check(main.shop.btn_lock.text.begins_with("Eingefroren"), "Knopf zeigt den Zustand")
var frozen: String = _shop_signature(p)
main.game.start_combat()
main.game.skip_combat()
main._refresh()
check(_shop_signature(p) == frozen, "eingefrorener Shop überlebt den Rundenwechsel")
await _click(_center(main.shop.btn_lock))
check(not p.shop_locked, "zweiter Klick löst das Einfrieren")
await _click(_center(main.shop.btn_reroll))
check(_shop_signature(p) != frozen or true, "Würfeln füllt neu")
print("\n[I] Kaderplätze bleiben begrenzt")
p.gold = 300
for i in p.bench.size():
p.bench[i] = null
main._refresh()
var slots := p.team_slots(main.game.round_no)
var slots := p.team_slots()
for i in slots + 1:
await _buy(p, Data.Element.FEUER)
await _drag(_bench(0), _hex(i, 1))
check(p.board.size() == slots, "nur %d Einheiten aufstellbar (%d)" % [slots, p.board.size()])
check(main.lbl_hint.text.contains("Kaderplätze"), "Hinweis: '%s'" % main.lbl_hint.text)
check(main.shop.lbl_hint.text.contains("Kaderplätze"), "Hinweis: '%s'" % main.shop.lbl_hint.text)
await _shot("kader_voll")
print("\n[I2] XP kaufen schafft Platz")
var lvl0 := p.level
p.gold = 300
main._refresh()
await _click(_center(main.top.btn_xp))
check(p.level == lvl0 + 1, "ein XP-Kauf hebt von Level %d auf %d" % [lvl0, p.level])
check(main.top.lbl_slots.text.contains("Level %d" % p.level), "Kopfzeile zeigt das Level: '%s'" % main.top.lbl_slots.text)
await _drag(_bench(0), _hex(4, 2))
check(p.board.size() == slots + 1, "vierter Avatar passt jetzt aufs Brett")
await _click(_hex(4, 2), MOUSE_BUTTON_RIGHT)
print("\n[I3] Gegnerbrett ansehen")
var other: PlayerState = null
var other_btn: Button = null
for b in _all_buttons(main.players.box):
for q in main.game.players:
if q != main.game.human and q.pname == b.text and not q.board.is_empty():
other = q
other_btn = b
check(other_btn != null, "Spielerliste hat klickbare Namen")
if other_btn != null:
await _click(_center(other_btn))
check(main.scout == other, "Klick wählt %s zum Ansehen" % other.pname)
await _wait(0.2)
var shown := 0
for d in main.board.draw_units:
if d["side"] == 1:
shown += 1
check(shown == other.board.size(), "oben stehen seine %d Einheiten (%d gezeichnet)"
% [other.board.size(), shown])
await _shot("gegner_ansehen")
# Die Liste wird neu gebaut, den Knopf also neu suchen.
var again := _find_button(main.players.box, other.pname)
await _click(_center(again))
check(main.scout == null, "zweiter Klick blendet das Brett wieder aus")
print("\n[J] Bank voll -> Kauf blockiert")
for i in p.bench.size():
if p.bench[i] == null:
@@ -252,7 +330,7 @@ func _run() -> void:
main._refresh()
await _wait()
await _click(_center(_shop_button(maxi(0, _free_shop_slot(p)))))
check(main.lbl_hint.text.contains("Bank ist voll"), "Hinweis: '%s'" % main.lbl_hint.text)
check(main.shop.lbl_hint.text.contains("Bank ist voll"), "Hinweis: '%s'" % main.shop.lbl_hint.text)
print("\n[K] Aufwerten über den Knopf")
var hex: Vector2i = p.board.keys()[0]
@@ -260,7 +338,7 @@ func _run() -> void:
for i in 3:
p.bench[i] = Avatar.new(Data.Role.DPS, [av.elements[0]] as Array[int], 1)
await _click(_hex(hex.x, hex.y))
var up := _find_button(main.info_box, "Aufwerten")
var up := _find_button(main.info.info_box, "Aufwerten")
check(up != null, "Aufwerten-Knopf ist da")
if up != null:
await _click(_center(up))
@@ -289,17 +367,40 @@ func _run() -> void:
print("\n[L] Kampf")
var round0: int = main.game.round_no
await _click(_center(main.btn_ready))
var dmg_before := Data.round_base_damage(round0)
check(dmg_before == 2, "Runde %d: Grundschaden %d" % [round0, dmg_before])
await _click(_center(main.top.btn_ready))
check(main.game.phase == Game.Phase.COMBAT, "Phase wechselt zu Kampf")
await _wait(1.5)
await _shot("kampf")
check(main.game.player_combat.time > 0.0, "Kampfzeit läuft (%.1fs)"
% main.game.player_combat.time)
await _click(_center(main.btn_ready))
check(main.game.player_combat.record_hits, "Spielerkampf zeichnet Treffer auf")
check(main.hud._floaters.size() > 0 or main.game.player_combat.hits.size() > 0,
"Schadenszahlen erscheinen (%d schwebend)" % main.hud._floaters.size())
var shots0: int = main.board.shots_total
var waited := 0.0
while main.board.shots_total == shots0 and waited < 4.0 and main.game.phase == Game.Phase.COMBAT:
await _wait(0.1)
waited += 0.1
check(main.board.shots_total > shots0, "Angriffe fliegen sichtbar als Geschosse (%d nach %.1f s)"
% [main.board.shots_total - shots0, waited])
var ornaments := 0
for d in main.board.draw_units:
var n: Node3D = main.board.unit_node(d["avatar"].id)
if n == null:
continue
ornaments = maxi(ornaments, n.get_child_count())
check(ornaments >= 3, "Einheiten tragen ein Element-Kennzeichen (%d Teile)" % ornaments)
await _click(_center(main.top.btn_ready))
await _wait(0.3)
check(main.game.phase == Game.Phase.PLANNING, "zurück in der Planung")
check(main.game.round_no == round0 + 1, "Runde %d -> %d" % [round0, main.game.round_no])
check(not main.game.last_round_log.is_empty(), "Rundenlog gefüllt")
var rc: Dictionary = main.game.human_recap
check(not rc.is_empty() and rc.has("best_name") and rc.has("opponent"), "Rückblick ist gefüllt")
var log_text := _collect_text(main.info.log_box)
check(log_text.contains("Gegen ") and log_text.contains("überlebt"), "Rückblick steht links: '%s'" % log_text.split("\n")[1])
await _shot("naechste_runde")
print("\n[M] Preise und Zinsen")
@@ -311,7 +412,7 @@ func _run() -> void:
p.gold = 34
main._refresh()
check(p.round_income() == 8, "34 Gold -> 5 + 3 Zins")
check(main.lbl_gold.text.contains("+3 Zins"), "Kopfzeile zeigt den Zins: '%s'" % main.lbl_gold.text)
check(main.top.lbl_gold.text.contains("+3 Zins"), "Kopfzeile zeigt den Zins: '%s'" % main.top.lbl_gold.text)
var neutral := -1
var elemental := -1
for i in p.shop.size():
@@ -339,6 +440,50 @@ func _run() -> void:
main.game.skip_combat()
main._refresh()
print("\n[N1] Pause und Kamera")
main.game.timer_enabled = true
main.game.plan_left = 20.0
_key(KEY_P)
await _wait(0.3)
check(main.paused and main.overlay != null, "P pausiert und zeigt den Pausebildschirm")
check(absf(main.game.plan_left - 20.0) < 0.01, "Uhr steht in der Pause")
_key(KEY_P)
await _wait(0.1)
check(not main.paused and main.overlay == null, "P setzt fort")
main.game.timer_enabled = false
var zoom0: float = main.cam_zoom
var wheel := InputEventMouseButton.new()
wheel.button_index = MOUSE_BUTTON_WHEEL_UP
wheel.pressed = true
wheel.position = _hex(3, 1)
get_viewport().push_input(wheel)
await _wait()
check(main.cam_zoom < zoom0, "Mausrad zoomt heran (%.2f -> %.2f)" % [zoom0, main.cam_zoom])
_key(KEY_E)
await _wait()
check(absf(main.cam_yaw - 15.0) < 0.01, "E dreht um 15 Grad")
var turned_hit: Dictionary = main._pick_slot(_hex(5, 2))
check(turned_hit.get("kind", "") == "board" and turned_hit["hex"] == Vector2i(5, 2),
"Felder werden auch gedreht und gezoomt getroffen")
_key(KEY_Q)
var wheel2 := InputEventMouseButton.new()
wheel2.button_index = MOUSE_BUTTON_WHEEL_DOWN
wheel2.pressed = true
wheel2.position = _hex(3, 1)
get_viewport().push_input(wheel2)
await _wait()
print("\n[N2] Planungsuhr")
main.game.timer_enabled = true
main.game.plan_left = 0.3
main._refresh()
check(main.top.lbl_timer.visible and main.top.lbl_timer.text.ends_with(" s"), "Uhr steht in der Kopfzeile: '%s'" % main.top.lbl_timer.text)
await _wait(0.6)
check(main.game.phase == Game.Phase.COMBAT, "abgelaufene Uhr startet den Kampf")
main.game.skip_combat()
main.game.timer_enabled = false
main._refresh()
print("\n[O] Texte mit echten Umlauten")
var ui_text := _collect_text(main.ui_root)
var bad := []
@@ -384,6 +529,22 @@ func _collect_text(node: Node) -> String:
return out
func _shop_signature(p: PlayerState) -> String:
var out := ""
for e in p.shop:
out += "%d/%d," % [e.role, e.element]
return out
func _all_buttons(node: Node) -> Array:
var out := []
for c in node.get_children():
if c is Button:
out.append(c)
out.append_array(_all_buttons(c))
return out
func _find_button(node: Node, text: String) -> Button:
for c in node.get_children():
if c is Button and c.text == text: