class_name BoosterScreen extends Control ## Booster kaufen und öffnen: unten die Pack-Kacheln der offenen Kapitel (Neutral und die Elemente in ## Kapitelreihenfolge, gesperrte sind unsichtbar) mit ×1, ×5, ×10; die Karten fliegen aus dem Pack in ein Raster ## (ein Pack: fünf große, mehrere: zehn je Reihe, beste Stufe zuerst) und liegen verdeckt. Klick ## oder „Alle aufdecken“ dreht sie gestaffelt um, seltene blitzen auf; danach steht das Fazit. const CARD_W := 200 const CARD_H := 280 const SMALL_W := 108 const SMALL_H := 151 const AREA := Rect2(40, 100, 1200, 460) const QUANTITIES := [1, 5, 10] var app var cards: Array = [] # {"card", "rarity", "new", "upgraded", "dust"} je Karte var revealed := {} var coins_label: Label var summary_label: Label var b_buy: Button # Neutral ×1 (Tests) var b_all: Button var scroll: ScrollContainer var slots: Control var pack_tiles := {} # Element -> PackTile (Kapitelreihenfolge) var pack_buttons := {} # Element -> der ×1-Knopf var fresh := {} # gerade aufgedeckt: Index -> Verzögerung fürs Umdrehen var fly_from := Vector2.ZERO # woher die Karten einfliegen (Mitte der gekauften Kachel) var fly := false # beim nächsten refresh einfliegen var last_pack := "" # zuletzt gekauftes Pack (pulsiert) class CardBack extends Control: signal clicked func _init(sz: Vector2) -> void: size = sz custom_minimum_size = sz mouse_filter = Control.MOUSE_FILTER_STOP focus_mode = Control.FOCUS_ALL focus_entered.connect(queue_redraw) focus_exited.connect(queue_redraw) func _gui_input(event: InputEvent) -> void: if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: accept_event() clicked.emit() elif has_focus() and event.is_action_pressed("ui_accept"): accept_event() clicked.emit() func _draw() -> void: var rect := Rect2(Vector2.ZERO, size) draw_style_box(Style.box(Color("3c285a"), 8, Style.GOLD, 5 if has_focus() else 3), rect) Style.border(self, rect.grow(-15), Color("5a3c82"), 2, 6) Style.text(self, "?", 48 if size.x >= 180 else 30, Style.GOLD, size / 2, "center", true) ## Pack-Kachel eines Elements: Symbol, Name, Preis; die Kaufknöpfe hängt der Bildschirm an. class PackTile extends Control: var element: String var locked := false var hint := "" var pulse := false var buy_buttons: Array = [] func _init(e: String, rect: Rect2) -> void: element = e position = rect.position size = rect.size pivot_offset = rect.size / 2 func _draw() -> void: var col: Color = Cards.ELEMENT_COLORS[element] draw_style_box(Style.box(Style.darker(col, 0.5), 10, Style.GOLD if pulse else Color(col, 0.7), 2), Rect2(Vector2.ZERO, size)) Style.element_icon(self, element, Vector2(32, 40), 34, Style.lighter(col, 0.4)) Style.text(self, tr(element), 16, Style.TEXT, Vector2(60, 30), "midleft", true) Style.text(self, hint, 13, Style.MUTED, Vector2(60, 52), "midleft") func _ready() -> void: Style.header(self, "Booster", func(): app.show_screen(MenuScreen.new())) coins_label = Style.chip(self, "", Rect2(860, 26, 240, 38)) scroll = ScrollContainer.new() scroll.position = AREA.position scroll.size = AREA.size scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED add_child(scroll) slots = Control.new() slots.mouse_filter = Control.MOUSE_FILTER_PASS slots.custom_minimum_size = AREA.size scroll.add_child(slots) summary_label = Style.label(self, "", 16, Style.GOLD, Rect2(0, 566, 1280, 24), "center", true) b_all = Style.button(self, "Alle aufdecken", Rect2(540, 594, 200, 40), reveal_all) for i in Campaign.CHAPTER_ELEMENTS.size(): var e: String = Campaign.CHAPTER_ELEMENTS[i] var tile := PackTile.new(e, Rect2(70 + i * 230, 644, 220, 136)) add_child(tile) for k in QUANTITIES.size(): var b := Style.button(tile, "×%d" % QUANTITIES[k], Rect2(10 + k * 68, 88, 64, 36), buy.bind(e, QUANTITIES[k])) b.add_theme_font_size_override("font_size", 14) tile.buy_buttons.append(b) pack_tiles[e] = tile pack_buttons[e] = tile.buy_buttons[0] b_buy = pack_buttons[Cards.NEUTRAL] refresh() func _input(event: InputEvent) -> void: if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE: app.show_screen(MenuScreen.new()) ## Kauft n Packs eines Elements; die Karten liegen verdeckt, mehrere Packs nach Stufe sortiert. func buy(element: String, n: int) -> void: var p: Profile = app.profile var before: int = p.coins var got: Array = p.open_boosters(element, n) if got.is_empty(): return app.save() app.sfx.play("play") if n > 1: got.sort_custom(func(a, b): return Cards.RARITIES.find(a.rarity) > Cards.RARITIES.find(b.rarity)) cards = got revealed = {} fresh = {} fly = true var tile: PackTile = pack_tiles[element] fly_from = tile.position + tile.size / 2 last_pack = element scroll.scroll_vertical = 0 refresh() _count_coins(before, p.coins) ## Die Münzen zählen sichtbar herunter. func _count_coins(from: int, to: int) -> void: if not Style.animations or from == to: return create_tween().tween_method(func(v: float): coins_label.text = "Münzen: %d" % int(round(v)), float(from), float(to), 0.5) func reveal(i: int) -> void: revealed[i] = true fresh = {i: 0.0} app.sfx.play("combo" if cards[i].rarity != "Common" else "click") refresh() ## Deckt alles auf, von vorn nach hinten gestaffelt; ab Ultra Rare bleibt die Karte kurz allein. func reveal_all() -> void: fresh = {} var delay := 0.0 var step := 0.12 if cards.size() <= Profile.BOOSTER_SIZE else 0.06 for i in cards.size(): if not revealed.has(i): fresh[i] = delay delay += step if Cards.RARITIES.find(cards[i].rarity) >= 3: delay += 0.35 revealed[i] = true app.sfx.play("play") refresh() ## Umdrehen: die Karte wächst aus der Mitte; seltene Stufen blitzen in ihrer Farbe auf. func flip(v: CardView, delay: float, rarity: String) -> void: if not Style.animations: return v.scale.x = 0.0 var tw := create_tween() tw.tween_interval(delay) tw.tween_property(v, "scale:x", 1.0, 0.18).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) if rarity == "Common": return var burst := Style.panel(slots, Rect2(v.position, v.size).grow(6), Color(Cards.RARITY_COLORS[rarity], 0.6), 12) burst.pivot_offset = burst.size / 2 burst.modulate.a = 0.0 var bt := create_tween() bt.tween_interval(delay + 0.1) bt.tween_property(burst, "modulate:a", 1.0, 0.05) bt.parallel().tween_property(burst, "scale", Vector2(1.25, 1.25), 0.45).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) bt.tween_property(burst, "modulate:a", 0.0, 0.3) bt.tween_callback(burst.queue_free) ## Einflug einer verdeckten Karte aus dem Pack auf ihren Platz, reihenweise gestaffelt. func fly_in(back: Control, target: Vector2, i: int, per_row: int) -> void: if not Style.animations: return back.position = fly_from - AREA.position - back.size / 2 back.scale = Vector2(0.4, 0.4) var tw := create_tween().set_parallel() var delay := (i / per_row) * 0.1 + (i % per_row) * 0.03 tw.tween_property(back, "position", target, 0.3).set_delay(delay).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) tw.tween_property(back, "scale", Vector2.ONE, 0.3).set_delay(delay).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) ## Platz der i-ten Karte: ein Pack fünf große in einer Reihe, sonst zehn kleine je Reihe. func slot_rect(i: int) -> Rect2: var single := cards.size() <= Profile.BOOSTER_SIZE var w := CARD_W if single else SMALL_W var h := CARD_H if single else SMALL_H var per_row := 5 if single else 10 var gap := 20 if single else 12 var x0 := (AREA.size.x - per_row * w - (per_row - 1) * gap) / 2 var row_h := h + (60 if single else 44) return Rect2(x0 + (i % per_row) * (w + gap), 10 + (i / per_row) * row_h, w, h) ## Fazit nach dem Aufdecken: neue Karten, Aufwertungen, Staub. func summary() -> String: if cards.is_empty() or revealed.size() < cards.size(): return "" var fresh_n := 0 var up := 0 var dust := 0 for res in cards: if res.get("new", false): fresh_n += 1 elif res.upgraded: up += 1 dust += int(res.dust) return tr("%d neu · %d aufgewertet · +%d Staub") % [fresh_n, up, dust] func refresh() -> void: var p: Profile = app.profile coins_label.text = "Münzen: %d" % p.coins var open: Array = p.unlocked_elements() var x0 := (1280 - open.size() * 220 - (open.size() - 1) * 10) / 2 for e in pack_tiles: var tile: PackTile = pack_tiles[e] tile.locked = not p.booster_unlocked(e) tile.visible = not tile.locked if not tile.locked: tile.position = Vector2(x0 + open.find(e) * 230, 644) tile.hint = tr("%d Münzen") % p.pack_price(e) for k in QUANTITIES.size(): tile.buy_buttons[k].disabled = tile.locked or p.coins < p.pack_price(e) * QUANTITIES[k] tile.pulse = e == last_pack and not cards.is_empty() and revealed.size() >= cards.size() tile.queue_redraw() b_all.disabled = cards.is_empty() or revealed.size() >= cards.size() summary_label.text = summary() Style.clear(slots) var single := cards.size() <= Profile.BOOSTER_SIZE var per_row := 5 if single else 10 var n := maxi(cards.size(), Profile.BOOSTER_SIZE) var rows := (n + per_row - 1) / per_row slots.custom_minimum_size = Vector2(AREA.size.x, maxf(AREA.size.y, slot_rect(n - 1).end.y + 60)) for i in n: var r := slot_rect(i) if cards.is_empty(): Style.panel(slots, r, Style.PANEL, 8, Style.PANEL_LIGHT, 2) elif revealed.has(i): var res: Dictionary = cards[i] var c: Cards.Card = res.card var v := CardView.new(c.leveled(Cards.RARITIES.find(res.rarity)), r.size, false, res.rarity) v.position = r.position slots.add_child(v) if fresh.has(i): flip(v, fresh[i], res.rarity) var fs := 16 if single else 12 Style.label(slots, tr(res.rarity), fs, Cards.RARITY_COLORS[res.rarity], Rect2(r.position.x - 10, r.end.y + 6, r.size.x + 20, 20), "center", true) var y2 := r.end.y + (30 if single else 24) if res.get("new", false): Style.label(slots, "Neu!", fs, Style.GOLD, Rect2(r.position.x, y2, r.size.x, 20), "center", true) elif res.upgraded: Style.label(slots, "Aufgewertet", fs, Style.GREEN, Rect2(r.position.x - 10, y2, r.size.x + 20, 20), "center", true) else: Style.label(slots, "+%d Staub" % res.dust, fs - 2, Style.MUTED, Rect2(r.position.x, y2, r.size.x, 20), "center") else: var back := CardBack.new(r.size) back.position = r.position back.pivot_offset = r.size / 2 back.clicked.connect(reveal.bind(i)) slots.add_child(back) if fly: fly_in(back, r.position, i, per_row) fly = false fresh = {} if rows > 0 and last_pack != "" and pack_tiles[last_pack].pulse and Style.animations: var tile: PackTile = pack_tiles[last_pack] var tw := tile.create_tween().set_loops(3) tw.tween_property(tile, "scale", Vector2(1.04, 1.04), 0.35).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tw.tween_property(tile, "scale", Vector2.ONE, 0.35).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)