class_name DeckScreen extends Control ## Deckbau mit fünf Slots, Suche, Sortierung und fehlenden Karten. const SORTS := ["Kosten", "Name", "Element", "Seltenheit"] var app var slot := 0 var deck: Array = [] var filter := "" var search := "" var sort_mode := 0 var show_missing := false var msg_label: Label var grid: CardGrid var deck_list: Control var deck_label: Label var elems_label: Label var valid_label: Label var active_label: Label var curve: Control var preview: CardView var search_box: LineEdit var sort_box: OptionButton var missing_box: CheckButton var slot_buttons: Array = [] var b_activate: Button var b_autofill: Button var name_box: LineEdit var confirm: Control class DeckRow extends Control: signal clicked(card) signal hover(card, entered: bool) var card: Cards.Card var count: int var lit := false func _init(c: Cards.Card, n: int, sz: Vector2) -> void: card = c count = n size = sz custom_minimum_size = sz mouse_filter = Control.MOUSE_FILTER_STOP mouse_entered.connect(func(): lit = true queue_redraw() hover.emit(card, true)) mouse_exited.connect(func(): lit = false queue_redraw() hover.emit(card, false)) func _gui_input(event: InputEvent) -> void: if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: accept_event() clicked.emit(card) func _draw() -> void: if lit: draw_style_box(Style.box(Style.PANEL_LIGHT, 4), Rect2(Vector2.ZERO, size)) for k in card.elements.size(): draw_circle(Vector2(10 + k * 12, size.y / 2), 7, Cards.ELEMENT_COLORS[card.elements[k]]) Style.text(self, "%dx %s" % [count, card.name], 15, Style.TEXT, Vector2(40, size.y / 2), "midleft") Style.text(self, str(card.cost), 15, Style.GOLD, Vector2(size.x - 10, size.y / 2), "midright", true) class EnergyCurve extends Control: var cards: Array = [] func _draw() -> void: Style.text(self, "Energiekurve", 12, Style.MUTED, Vector2(0, 0)) var counts := {} for c in cards: var k: int = mini(c.cost, 8) counts[k] = counts.get(k, 0) + 1 for i in range(1, 9): var n: int = counts.get(i, 0) var x := (i - 1) * 48 draw_rect(Rect2(x, 104 - n * 5, 36, n * 5), Style.BLUE) if n > 0: Style.text(self, str(n), 11, Style.TEXT, Vector2(x + 18, 102 - n * 5), "midbottom") Style.text(self, str(i) + ("+" if i == 8 else ""), 12, Style.MUTED, Vector2(x + 18, 108), "midtop") func _ready() -> void: slot = app.profile.active_deck deck = app.profile.decks[slot].cards.duplicate() Style.label(self, "Deck bauen", 30, Style.GOLD, Rect2(20, 8, 400, 40), "left", true) Style.label(self, "Klick: Karte ins Deck. Klick in der Liste: Karte entfernen. Mausrad: scrollen.", 14, Style.MUTED, Rect2(300, 18, 700, 24)) Style.button(self, "Alle", Rect2(20, 55, 60, 34), set_filter.bind("")) for i in Cards.ELEMENTS.size(): var e: String = Cards.ELEMENTS[i] Style.button(self, e, Rect2(88 + i * 76, 55, 70, 34), set_filter.bind(e), Style.darker(Cards.ELEMENT_COLORS[e], 0.8)) search_box = LineEdit.new() search_box.position = Vector2(400, 55) search_box.size = Vector2(160, 34) search_box.placeholder_text = "Suche" search_box.text_changed.connect(func(t: String): search = t refresh()) add_child(search_box) sort_box = OptionButton.new() sort_box.position = Vector2(570, 55) sort_box.size = Vector2(130, 34) for s in SORTS: sort_box.add_item(s) sort_box.item_selected.connect(func(i: int): sort_mode = i refresh()) add_child(sort_box) missing_box = CheckButton.new() missing_box.position = Vector2(710, 58) missing_box.text = "Fehlende" missing_box.toggled.connect(func(on: bool): show_missing = on refresh()) add_child(missing_box) grid = CardGrid.new(Rect2(20, 100, CardGrid.COLS * (CardGrid.CW + CardGrid.GAP) + 10, 690)) grid.card_clicked.connect(add) grid.card_hover.connect(show_preview) add_child(grid) # Rechte Seite: Slots, Deckliste, Kurve Style.panel(self, Rect2(845, 50, 420, 680)) for i in Profile.DECK_SLOTS: slot_buttons.append(Style.button(self, str(i + 1), Rect2(860 + i * 46, 58, 40, 32), select_slot.bind(i))) b_activate = Style.button(self, "Aktiv setzen", Rect2(1100, 58, 150, 32), activate) name_box = LineEdit.new() name_box.position = Vector2(860, 98) name_box.size = Vector2(200, 30) name_box.max_length = 20 name_box.text_submitted.connect(func(t: String): rename(t)) name_box.focus_exited.connect(func(): rename(name_box.text)) add_child(name_box) deck_label = Style.label(self, "", 22, Style.TEXT, Rect2(1070, 98, 120, 30), "left", true) active_label = Style.label(self, "", 14, Style.GREEN, Rect2(1180, 104, 75, 24), "right") b_autofill = Style.button(self, "Auto-Ausfüllen", Rect2(1100, 154, 150, 26), autofill) b_autofill.add_theme_font_size_override("font_size", 13) elems_label = Style.label(self, "", 16, Style.MUTED, Rect2(860, 130, 390, 24)) valid_label = Style.label(self, "", 14, Style.GREEN, Rect2(860, 156, 235, 22)) deck_list = Control.new() deck_list.position = Vector2(860, 182) deck_list.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(deck_list) curve = EnergyCurve.new() curve.position = Vector2(860, 604) curve.size = Vector2(400, 125) curve.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(curve) msg_label = Style.label(self, "", 16, Style.GREEN, Rect2(860, 736, 400, 24)) Style.button(self, "Speichern", Rect2(860, 740, 180, 44), save_deck, Color("285a3c")) Style.button(self, "Leeren", Rect2(1050, 740, 90, 44), clear_deck) Style.button(self, "Zurück", Rect2(1150, 740, 110, 44), leave) confirm = Control.new() confirm.set_anchors_preset(Control.PRESET_FULL_RECT) confirm.visible = false var dark := ColorRect.new() dark.color = Color(0, 0, 0, 0.67) dark.set_anchors_preset(Control.PRESET_FULL_RECT) dark.mouse_filter = Control.MOUSE_FILTER_STOP confirm.add_child(dark) Style.panel(confirm, Rect2(340, 290, 600, 200), Style.PANEL, 10, Style.GOLD, 2) Style.label(confirm, "Ungespeicherte Änderungen", 26, Style.TEXT, Rect2(340, 312, 600, 40), "center", true) Style.label(confirm, "Das Deck wurde geändert, aber nicht gespeichert.", 16, Style.MUTED, Rect2(340, 360, 600, 24), "center") Style.button(confirm, "Verwerfen", Rect2(380, 420, 240, 46), func(): app.show_screen(MenuScreen.new()), Color("5a2828")) Style.button(confirm, "Weiter bearbeiten", Rect2(660, 420, 240, 46), func(): confirm.visible = false) add_child(confirm) preview = CardView.new(Cards.all()[0], Vector2(200, 280)) preview.mouse_filter = Control.MOUSE_FILTER_IGNORE preview.visible = false add_child(preview) refresh() func dirty() -> bool: return deck != app.profile.decks[slot].cards func leave() -> void: if dirty(): confirm.visible = true else: app.show_screen(MenuScreen.new()) func _input(event: InputEvent) -> void: if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE and not search_box.has_focus() and not name_box.has_focus(): if confirm.visible: confirm.visible = false else: leave() func _process(_delta: float) -> void: if preview.visible: var pos := get_local_mouse_position() + Vector2(16, 16) pos.x = clampf(pos.x, 0, 1280 - preview.size.x) pos.y = clampf(pos.y, 0, 800 - preview.size.y) preview.position = pos func set_filter(e: String) -> void: filter = e refresh() func select_slot(i: int) -> void: slot = i deck = app.profile.decks[slot].cards.duplicate() say("", true) refresh() func rename(t: String) -> void: if t.strip_edges() == app.profile.deck_name(slot): return var err: String = app.profile.rename_deck(slot, t) if err == "": app.save() say("Umbenannt in %s" % app.profile.deck_name(slot), true) else: say(err, false) refresh() func autofill() -> void: app.profile.decks[slot].cards = deck.duplicate() var err: String = app.profile.autofill_deck(slot) deck = app.profile.decks[slot].cards.duplicate() if err == "": app.save() say("Aufgefüllt und gespeichert", true) else: say(err, false) refresh() func activate() -> void: var ok: Array = Game.validate_deck(deck_cards(), app.profile.collection) if not ok[0]: say("Erst ein spielbares Deck speichern", false) return app.profile.decks[slot].cards = deck.duplicate() app.profile.active_deck = slot app.save() say("%s ist jetzt das aktive Deck" % app.profile.deck_name(slot), true) refresh() ## Karten der Sammlung (oder alle, wenn Fehlende gezeigt werden), gefiltert und sortiert. func cards() -> Array: var out: Array = Cards.all().duplicate() if show_missing else app.profile.owned_cards() if filter != "": out = out.filter(func(c): return filter in c.elements) if search != "": var q := search.to_lower() out = out.filter(func(c): return q in c.name.to_lower() or q in c.text().to_lower() or q in c.combo().to_lower()) match sort_mode: 0: out.sort_custom(func(a, b): return a.cost < b.cost or (a.cost == b.cost and a.name < b.name)) 1: out.sort_custom(func(a, b): return a.name < b.name) 3: out.sort_custom(func(a, b): var ra := Cards.RARITIES.find(a.rarity) var rb := Cards.RARITIES.find(b.rarity) return ra > rb or (ra == rb and a.cost < b.cost)) return out func say(m: String, ok: bool) -> void: msg_label.text = m msg_label.add_theme_color_override("font_color", Style.GREEN if ok else Style.RED) func add(card: Cards.Card) -> void: var owned: int = app.profile.collection.get(card.id, 0) var have := deck.count(card.id) if owned == 0: say("%s fehlt in deiner Sammlung (Werkstatt)" % card.name, false) elif deck.size() >= Game.DECK_SIZE: say("Das Deck ist voll (%d)" % Game.DECK_SIZE, false) elif have >= mini(owned, Game.MAX_COPIES): say("Nicht mehr Kopien möglich (max. %d)" % mini(owned, Game.MAX_COPIES), false) else: var elems := Game.deck_elements(deck_cards()) for e in card.elements: if not (e in elems): elems.append(e) if elems.size() > Game.MAX_DECK_ELEMENTS: say("Höchstens %d Elemente im Deck" % Game.MAX_DECK_ELEMENTS, false) else: deck.append(card.id) app.sfx.play("click") say("", true) refresh() func remove(card: Cards.Card) -> void: deck.erase(card.id) say("", true) refresh() func clear_deck() -> void: deck = [] refresh() func save_deck() -> void: var ok: Array = Game.validate_deck(deck_cards(), app.profile.collection) if ok[0]: app.profile.decks[slot].cards = deck.duplicate() app.save() say("%s gespeichert" % app.profile.deck_name(slot), true) else: say(ok[1], false) refresh() func deck_cards() -> Array: var out := [] for cid in deck: out.append(Cards.by_id(cid)) return out func show_preview(card: Cards.Card, entered: bool) -> void: if entered: preview.card = card preview.visible = true preview.queue_redraw() elif preview.card == card: preview.visible = false func refresh() -> void: var p: Profile = app.profile grid.fill(cards(), func(c): var owned: int = p.collection.get(c.id, 0) return "fehlt" if owned == 0 else "%d/%d im Deck" % [deck.count(c.id), owned], func(c): var owned: int = p.collection.get(c.id, 0) return owned == 0 or deck.count(c.id) >= mini(owned, Game.MAX_COPIES)) for i in slot_buttons.size(): var b: Button = slot_buttons[i] var col: Color = Style.GOLD if i == slot else (Style.GREEN if p.deck_valid(i)[0] else Style.MUTED) b.add_theme_stylebox_override("normal", Style.box(Style.PANEL_LIGHT if i != slot else Style.darker(Style.GOLD, 0.45), 6, col, 2)) b.add_theme_stylebox_override("hover", Style.box(Style.lighter(Style.PANEL_LIGHT), 6, col, 2)) active_label.text = "aktiv" if slot == p.active_deck else "" # Deckliste Style.clear(deck_list) var counts := {} for cid in deck: counts[cid] = counts.get(cid, 0) + 1 var listed: Array = counts.keys().map(func(cid): return Cards.by_id(cid)) listed.sort_custom(func(a, b): return a.cost < b.cost or (a.cost == b.cost and a.name < b.name)) for i in listed.size(): var c: Cards.Card = listed[i] var row := DeckRow.new(c, counts[c.id], Vector2(400, 20)) row.position = Vector2(0, i * 21) row.clicked.connect(remove) row.hover.connect(show_preview) deck_list.add_child(row) var dc := deck_cards() if not name_box.has_focus(): name_box.text = p.deck_name(slot) deck_label.text = "%d/%d" % [deck.size(), Game.DECK_SIZE] deck_label.tooltip_text = tr("Deckstärke: Durchschnitt aus Seltenheit, Schlüsselwörtern und Effekten") deck_label.add_theme_color_override("font_color", Style.GREEN if deck.size() == Game.DECK_SIZE else Style.TEXT) var elems := Game.deck_elements(dc) elems.sort_custom(func(a, b): return Cards.ELEMENTS.find(a) < Cards.ELEMENTS.find(b)) elems_label.text = "Elemente: " + (", ".join(elems) if not elems.is_empty() else "–") if not dc.is_empty(): elems_label.text += " · " + tr("Stärke %.1f") % Game.deck_rating(dc) var ok: Array = Game.validate_deck(dc, p.collection) valid_label.text = "spielbar" if ok[0] else ok[1] valid_label.add_theme_color_override("font_color", Style.GREEN if ok[0] else Style.ORANGE) curve.cards = dc curve.queue_redraw()