class_name CardView extends Control ## Zeichnet eine Karte (Hand, Sammlung, Booster, Vorschau). ## Aufbau wie bei Hearthstone: Kostenkristall oben links, Bild, Namensband, Eigenschaften als ## Symbole, Angriff und Leben als Kreise unten. Das erste Element färbt den Hintergrund, weitere ## Elemente zeigt ein Medaillon als Tortenstücke hinter dem Bild; die Seltenheit steckt in Name, ## Rahmen, Glanz und Edelstein (siehe Style). Sehr flache Karten zeigen nur das Nötigste. signal clicked(view: CardView) signal pressed(view: CardView, pos: Vector2) # nur mit drag_enabled: der Kampf macht daraus Klick oder Ziehen signal hover(card: Cards.Card, entered: bool) var card: Cards.Card var border = null # Color oder null (dann Rahmen der Seltenheitsstufe) var rarity := "Common" # nur Optik var dim := false var bonus := 0 # Vorschau im Kampf: Elementbonus, den die Karte offen gelegt bekäme var lift := false # hebt sich beim Zeigen an (Hand) var drag_enabled := false # Hand im Kampf: Drücken meldet pressed statt clicked var hold := 0.0 # Fortschritt des Verschmelzens, wenn eine Karte darüber gehalten wird (0..1) ## focusable: per Gamepad oder Tab anwählbar (Sammlung, Booster); A bzw. Enter wirkt wie ein Klick. func _init(c: Cards.Card, sz: Vector2, focusable: bool = false, rar: String = "Common") -> void: card = c rarity = rar set_process(Style.rarity_animated(rarity)) # bewegter Glanz custom_minimum_size = sz size = sz pivot_offset = sz / 2 mouse_filter = Control.MOUSE_FILTER_STOP mouse_entered.connect(func(): hover.emit(card, true) _lift(true)) mouse_exited.connect(func(): hover.emit(card, false) _lift(false)) if focusable: focus_mode = Control.FOCUS_ALL focus_entered.connect(func(): hover.emit(card, true) queue_redraw()) focus_exited.connect(func(): hover.emit(card, false) queue_redraw()) func _lift(up: bool) -> void: if not lift or not Style.animations: return z_index = 1 if up else 0 create_tween().tween_property(self, "scale", Vector2(1.07, 1.07) if up else Vector2.ONE, 0.1).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) func _process(_delta: float) -> void: queue_redraw() func _gui_input(event: InputEvent) -> void: if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: accept_event() if drag_enabled: pressed.emit(self, event.global_position) else: clicked.emit(self) elif has_focus() and event.is_action_pressed("ui_accept"): accept_event() clicked.emit(self) func _draw() -> void: var rect := Rect2(Vector2.ZERO, size) var seed := hash(card.id) var frame: Color = border if border != null else Style.rarity_frame(rarity, seed) if rarity != "Common": Style.glow(self, rect, Style.rarity_color(rarity, seed), 8, 8 if size.x >= 180 else 6) Style.fill_elements(self, rect, card.elements, 8) if size.y < 90: _draw_compact(rect, frame, seed) else: _draw_full(rect, frame, seed) if dim: draw_rect(rect, Color(0, 0, 0, 0.47)) if has_focus(): Style.border(self, rect, Style.GOLD, 4, 8) if hold > 0.0: var c := size / 2 var r := minf(size.x, size.y) * 0.3 draw_circle(c, r + 6, Color(0, 0, 0, 0.55)) draw_arc(c, r, -PI / 2, -PI / 2 + TAU * hold, 40, Style.GOLD, 5.0, true) ## Volle Karte: Sammlung, Booster, Vorschau. func _draw_full(rect: Rect2, frame: Color, seed: int) -> void: var big := size.x >= 180 var mid := size.x >= 140 var f_name := 17 if big else (14 if mid else 11) var f_text := 13 if big else (11 if mid else 10) var f_stat := 20 if big else (17 if mid else 14) var r_cost := 17.0 if big else (14.0 if mid else 12.0) var r_stat := 18.0 if big else (15.0 if mid else 12.0) var icon := 11.0 if big else (9.0 if mid else 7.0) # Bild: das Elementsymbol füllt den oberen Teil var band_top: float = size.y * 0.36 var gc := Vector2(size.x / 2, band_top * 0.56) var gs := minf(size.x * 0.62, band_top * 0.95) if card.is_combo(): Style.element_medallion(self, gc, gs * 0.6, card.elements) Style.glyph(self, card, gc, gs, 0.4, Style.rarity_relief(rarity)) Style.rarity_sheen(self, rect, rarity, seed, Rect2(0, 0, size.x, band_top)) # Namensband über die volle Breite var band_h: float = Style.fs(f_name) + 10 var band := Rect2(0, band_top, size.x, band_h) draw_rect(band, Color(0.05, 0.05, 0.08, 0.82)) var nf := Style.fit_size(card.title(), true, f_name, size.x - 12) Style.text(self, Style.fit(card.title(), true, Style.fs(nf), size.x - 12), nf, Style.rarity_name_color(rarity, seed), Vector2(size.x / 2, band.get_center().y), "center", true) var y := band.end.y + 4 # Kombo eines verschmolzenen Wesens if card.is_combo(): Style.text(self, tr(card.combo()), f_text, Color("e6e6e6"), Vector2(size.x / 2, y), "midtop") y += Style.fs(f_text) + 4 # Schlüsselwörter als Symbole, auf großen Karten mit Wort var kws: Array[String] = [] for k in Cards.KEYWORDS: if k in card.keywords: kws.append(k) if not kws.is_empty(): var with_words := big var widths: Array[float] = [] var total := 0.0 for k in kws: var w: float = icon * 2 + 6 + (Style.FONT.get_string_size(tr(k), HORIZONTAL_ALIGNMENT_LEFT, -1, Style.fs(f_text)).x + 8 if with_words else 0) widths.append(w) total += w + 6 if with_words and total > size.x - 12: with_words = false total = kws.size() * (icon * 2 + 12) var x := (size.x - total + 6) / 2 for i in kws.size(): var k := kws[i] Style.keyword_icon(self, k, Vector2(x + icon, y + icon + 2), icon) if with_words: Style.text(self, tr(k), f_text, Style.TEXT, Vector2(x + icon * 2 + 6, y + icon + 2), "midleft") x += widths[i] + 6 else: x += icon * 2 + 12 y += icon * 2 + 8 # Effekttext: so hoch wie nötig, endet über den Wertkreisen var box := Rect2() var fx := card.text() var box_bottom: float = size.y - r_stat * 2 - 4 if fx != "" and box_bottom - y > Style.fs(f_text) + 6: var tsize := Style.fs(f_text) var needed: float = Style.FONT.get_multiline_string_size(fx, HORIZONTAL_ALIGNMENT_LEFT, size.x - 20, tsize).y + 8 var box_top: float = maxf(y, y + (box_bottom - y - needed) / 2) box = Rect2(6, box_top, size.x - 12, box_bottom - box_top) draw_rect(box, Color(0.06, 0.06, 0.09, 0.72)) var ty := box.position.y + 4 var max_lines := maxi(1, int((box.end.y - ty - 2) / Style.FONT.get_height(tsize))) draw_multiline_string(Style.FONT, Vector2(box.position.x + 4, ty + Style.FONT.get_ascent(tsize)), fx, HORIZONTAL_ALIGNMENT_LEFT, box.size.x - 8, tsize, max_lines, Style.TEXT) # Werte springen aus den unteren Ecken, dazwischen der Edelstein Style.stat_circle(self, Vector2(r_stat - 2, size.y - r_stat + 2), r_stat, str(card.attack), Style.AMBER, f_stat) Style.stat_circle(self, Vector2(size.x - r_stat + 2, size.y - r_stat + 2), r_stat, str(card.health), Style.LIFE, f_stat) if bonus > 0: Style.text_outlined(self, "+%d" % bonus, f_text, Style.GREEN, Vector2(r_stat * 2 + 4, size.y - r_stat + 2), "midleft", 3) Style.rarity_gem(self, rarity, Vector2(size.x / 2, size.y - r_stat + 1), 8 if big else 6, seed) Style.border(self, rect, frame, 3 if big else 2, 8) Style.cost_crystal(self, Vector2(r_cost + 2, r_cost + 2), r_cost, card.cost, f_stat) Style.rarity_sparkle(self, rect, rarity, seed, box) ## Flache Handkachel: Kosten, Name, Symbole, Werte. func _draw_compact(rect: Rect2, frame: Color, seed: int) -> void: Style.rarity_sheen(self, rect, rarity, seed) Style.border(self, rect, frame, 2, 8) Style.cost_crystal(self, Vector2(13, 13), 11, card.cost, 13) var name_w: float = size.x - 30 - (12 if rarity != "Common" else 0) var nf := Style.fit_size(card.title(), true, 12, name_w) Style.text(self, Style.fit(card.title(), true, Style.fs(nf), name_w), nf, Style.TEXT, Vector2(28, 13), "midleft", true) Style.rarity_gem(self, rarity, Vector2(size.x - 9, 13), 5, seed) var kws: Array[String] = [] for k in Cards.KEYWORDS: if k in card.keywords: kws.append(k) var x := size.x / 2 - (kws.size() * 18 - 4) / 2.0 for k in kws: Style.keyword_icon(self, k, Vector2(x + 6, size.y - 13), 6) x += 18 Style.stat_circle(self, Vector2(11, size.y - 11), 10, str(card.attack), Style.AMBER, 12) Style.stat_circle(self, Vector2(size.x - 11, size.y - 11), 10, str(card.health), Style.LIFE, 12) if bonus > 0: Style.text_outlined(self, "+%d" % bonus, 10, Style.GREEN, Vector2(4, size.y - 29), "midleft", 3) Style.rarity_sparkle(self, rect, rarity, seed)