update
This commit is contained in:
@@ -139,14 +139,31 @@ func pop_in(tile: CreatureView) -> void:
|
||||
|
||||
|
||||
func fade_out(tile: CreatureView) -> void:
|
||||
tile.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
fade_view(tile)
|
||||
|
||||
|
||||
## Blendet eine Ansicht aus und entfernt sie (Wesen, die sterben; Handkarten, die verschmelzen).
|
||||
func fade_view(view: Control) -> void:
|
||||
view.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
if not enabled:
|
||||
tile.queue_free()
|
||||
view.queue_free()
|
||||
return
|
||||
var tw := tile.create_tween().set_parallel(true)
|
||||
tw.tween_property(tile, "modulate:a", 0.0, 0.3)
|
||||
tw.tween_property(tile, "scale", Vector2(0.6, 0.6), 0.3)
|
||||
tw.chain().tween_callback(tile.queue_free)
|
||||
var tw := view.create_tween().set_parallel(true)
|
||||
tw.tween_property(view, "modulate:a", 0.0, 0.3)
|
||||
tw.tween_property(view, "scale", Vector2(0.6, 0.6), 0.3)
|
||||
tw.chain().tween_callback(view.queue_free)
|
||||
|
||||
|
||||
## Eine Ansicht gleitet von from an ihren Platz (die neue Handkarte nach dem Verschmelzen).
|
||||
func glide(view: Control, from: Rect2) -> void:
|
||||
if not enabled:
|
||||
return
|
||||
var to := view.position
|
||||
view.position = from.position
|
||||
view.scale = from.size / view.size
|
||||
var tw := view.create_tween().set_parallel(true)
|
||||
tw.tween_property(view, "position", to, 0.25).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||
tw.tween_property(view, "scale", Vector2.ONE, 0.25).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||
|
||||
|
||||
func move_to(tile: CreatureView, pos: Vector2) -> void:
|
||||
|
||||
@@ -8,7 +8,7 @@ const B_UNDO := Rect2(20, 10, 210, 32) # links oben: Rückgängig, d
|
||||
const B_SKIP := Rect2(20, 48, 210, 32)
|
||||
const OPP_DECK := Rect2(1090, 10, 130, 182) # Stapel des Gegners rechts oben
|
||||
const OPP_BOX := Rect2(1050, 200, 210, 70) # Kasten des Gegners darunter
|
||||
const OPP_HAND_Y := 11
|
||||
const OPP_HAND_Y := 22 # Bogen hebt die äußeren Rückseiten um bis zu 12 Pixel
|
||||
const OPP_ELEMENTS := Rect2(20, 220, 210, 128) # Elementzähler des Gegners, links von seinem Brett
|
||||
const OWN_ELEMENTS := Rect2(20, 440, 210, 128) # eigene Elementzähler, links vom eigenen Brett
|
||||
const INFO := Rect2(20, 580, 210, 60) # Zusatzzeile zur Vorschau
|
||||
@@ -16,11 +16,11 @@ const MSG := Rect2(20, 650, 210, 80) # Meldungen
|
||||
const STATUS := Rect2(1050, 526, 210, 70) # eigener Kasten rechts über dem Stapel
|
||||
const DECK := Rect2(1090, 606, 130, 182) # eigener Stapel rechts unten
|
||||
const OPTIONS := Rect2(440, 180, 400, 420) # Optionsmenü (Esc)
|
||||
const OWN_X := 345
|
||||
const HAND_W := 110 # Handkarten so groß wie die Wesen auf dem Feld
|
||||
const HAND_H := 140
|
||||
const HAND_GAP := 10
|
||||
const HAND_Y := 610
|
||||
const OWN_X := 295
|
||||
const HAND_W := 130 # Handkarten so groß wie die Wesen auf dem Feld
|
||||
const HAND_H := 165
|
||||
const HAND_GAP := 6
|
||||
const HAND_Y := 605
|
||||
const HAND_ARC := 3.0 # Grad Neigung und Pixel Absenkung je Platz von der Mitte (leichter Bogen)
|
||||
const LOG_PANEL := Rect2(240, 60, 800, 560)
|
||||
const PREVIEW_SIZE := Vector2(170, 238)
|
||||
@@ -29,19 +29,23 @@ const CENTER_W := 800
|
||||
|
||||
|
||||
static func own_slot(j: int) -> Rect2:
|
||||
return Rect2(OWN_X + j * 120, 440, 110, 140)
|
||||
return Rect2(OWN_X + j * 140, 410, HAND_W, HAND_H)
|
||||
|
||||
|
||||
## Brett des Gegners über seiner Elementleiste.
|
||||
static func opp_slot(j: int) -> Rect2:
|
||||
return Rect2(OWN_X + j * 120, 220, 110, 140)
|
||||
return Rect2(OWN_X + j * 140, 205, HAND_W, HAND_H)
|
||||
|
||||
|
||||
## Hand: bis zu fünf Karten unten, mittig; den Bogen legt hand_arc dazu.
|
||||
## Hand: alle Karten unten, mittig; passen sie nicht nebeneinander, überlappen sie sich
|
||||
## gleichmäßig. Den Bogen legt hand_arc dazu.
|
||||
static func hand_rect(count: int, i: int) -> Rect2:
|
||||
var total := count * HAND_W + maxi(0, count - 1) * HAND_GAP
|
||||
var step: float = HAND_W + HAND_GAP
|
||||
if count > 1:
|
||||
step = minf(step, float(CENTER_W - HAND_W) / (count - 1))
|
||||
var total: float = HAND_W + (count - 1) * step
|
||||
var x := CENTER_X + (CENTER_W - total) / 2
|
||||
return Rect2(x + i * (HAND_W + HAND_GAP), HAND_Y, HAND_W, HAND_H)
|
||||
return Rect2(x + i * step, HAND_Y, HAND_W, HAND_H)
|
||||
|
||||
|
||||
## Hand des Gegners oben, gleich groß, nur die Rückseiten.
|
||||
|
||||
@@ -5,8 +5,9 @@ extends Control
|
||||
## die Leertaste oder der Rechtsklick ins Leere.
|
||||
## Mittlere Maustaste auf eine Handkarte legt sie verdeckt. Rechtsklick auf eine Handkarte oder
|
||||
## ein eigenes Wesen öffnet das Kartenmenü: verdeckt legen, aufdecken, Eigenschaften kaufen.
|
||||
## Verschmelzen: eine Karte auf eine andere ziehen und dort eine Sekunde stillhalten. Mit
|
||||
## Gamepad öffnet Y das Menü, dort zusätzlich mit „Verschmelzen mit …“.
|
||||
## Verschmelzen: eine Handkarte oder ein eigenes Wesen auf ein anderes ziehen und loslassen; das
|
||||
## Ergebnis (eine neue Handkarte) erscheint über dem Ziel, darunter Bestätigen oder Abbrechen.
|
||||
## Mit Gamepad öffnet Y das Menü, dort zusätzlich mit „Verschmelzen mit …“.
|
||||
## Modi: Einzelspieler und Kampagne.
|
||||
|
||||
var app
|
||||
@@ -17,15 +18,13 @@ var fx: BattleFx
|
||||
var selected = null # Game.Creature
|
||||
var hover_target = null # fremdes Wesen unter der Maus: Vorschau des Angriffs mit dem gewählten Wesen
|
||||
var fuse_first = null # Karte oder Wesen, für das im Menü „Verschmelzen mit …“ gewählt wurde
|
||||
var hold_time := 1.0 # so lange still über dem Ziel halten, dann verschmelzen die beiden
|
||||
var fuse_pending = null # [a, b] nach dem Loslassen über einem Ziel, wartet auf Bestätigung
|
||||
var press_target = null # Karte oder Wesen unter der gedrückten linken Maustaste
|
||||
var press_view: Control = null
|
||||
var press_pos := Vector2.ZERO
|
||||
var drag_ghost: Control = null # schwebende Kopie der gezogenen Karte
|
||||
var drag_target = null # mögliches Verschmelzungsziel unter dem Zeiger
|
||||
var drag_target_view: Control = null
|
||||
var hold_t := 0.0
|
||||
var hold_pos := Vector2.ZERO
|
||||
var msg_t := 0.0
|
||||
var ai_delay := 0.75
|
||||
var ai_timer := 0.0
|
||||
@@ -77,6 +76,7 @@ var info_label: Label # Vorschau: Elementbonus, Merkmale
|
||||
var menu_layer: Control # Kartenmenü (Rechtsklick)
|
||||
var menu_target = null # Karte oder Wesen, für das das Menü offen ist
|
||||
var arrivals := {} # uid -> Rect2: Wesen, das gerade aus der Hand aufs Brett fliegt
|
||||
var hand_arrivals := {} # Karte -> Rect2: neue Handkarte, die aus der Verschmelzung in die Hand gleitet
|
||||
var banner_turn := -1 # Zug, für den die Zugansage schon lief
|
||||
var overlay: Control
|
||||
var result_label: Label
|
||||
@@ -408,15 +408,6 @@ func _process(delta: float) -> void:
|
||||
undo_t -= delta
|
||||
if undo_t <= 0.0:
|
||||
clear_undo()
|
||||
if drag_ghost != null and drag_target != null:
|
||||
_set_hold(hold_t + delta)
|
||||
if hold_t >= hold_time:
|
||||
var a = press_target
|
||||
var b = drag_target
|
||||
press_target = null
|
||||
_end_drag()
|
||||
do_fuse(a, b)
|
||||
after_action()
|
||||
if game.winner != "":
|
||||
if not ended:
|
||||
ended = true
|
||||
@@ -487,7 +478,7 @@ func cancel() -> void:
|
||||
_end_drag()
|
||||
|
||||
|
||||
# --- Ziehen mit der Maus: Klick beim Loslassen, Verschmelzen nach einer Sekunde über dem Ziel ---
|
||||
# --- Ziehen mit der Maus: Klick beim Loslassen; losgelassen über einem Ziel fragt nach dem Verschmelzen ---
|
||||
|
||||
func _press(obj, view: Control, pos: Vector2) -> void:
|
||||
_end_drag()
|
||||
@@ -505,23 +496,28 @@ func _drag_motion(pos: Vector2) -> void:
|
||||
_start_drag(pos)
|
||||
drag_ghost.position = pos - drag_ghost.size / 2
|
||||
var t = _fusion_target_at(pos)
|
||||
if t != drag_target or pos.distance_to(hold_pos) > 6.0:
|
||||
_set_hold(0.0)
|
||||
if t != drag_target:
|
||||
_mark_target(false)
|
||||
drag_target = t
|
||||
drag_target_view = _view_of(t)
|
||||
hold_pos = pos
|
||||
_mark_target(true)
|
||||
|
||||
|
||||
## Loslassen ohne Ziehen ist der Klick.
|
||||
## Loslassen ohne Ziehen ist der Klick; über einem Ziel öffnet es die Verschmelzungsfrage.
|
||||
func _release() -> void:
|
||||
var obj = press_target
|
||||
var target = drag_target
|
||||
var target_rect := _rect_of(drag_target_view) if drag_target_view != null else Rect2()
|
||||
var dragged := drag_ghost != null
|
||||
press_target = null
|
||||
_end_drag()
|
||||
if obj == null:
|
||||
return
|
||||
if dragged:
|
||||
refresh()
|
||||
if target != null:
|
||||
open_fuse_confirm(obj, target, target_rect)
|
||||
else:
|
||||
refresh()
|
||||
elif obj is Cards.Card:
|
||||
on_click("hand", obj)
|
||||
else:
|
||||
@@ -549,24 +545,27 @@ func _end_drag() -> void:
|
||||
drag_ghost = null
|
||||
if is_instance_valid(press_view):
|
||||
press_view.modulate.a = 1.0
|
||||
_set_hold(0.0)
|
||||
_mark_target(false)
|
||||
drag_target = null
|
||||
drag_target_view = null
|
||||
|
||||
|
||||
func _set_hold(v: float) -> void:
|
||||
hold_t = v
|
||||
func _mark_target(on: bool) -> void:
|
||||
if is_instance_valid(drag_target_view):
|
||||
drag_target_view.hold = clampf(v / hold_time, 0.0, 1.0) if v > 0.0 else 0.0
|
||||
drag_target_view.drop = on
|
||||
drag_target_view.queue_redraw()
|
||||
|
||||
|
||||
## Karte oder Wesen unter pos, mit dem sich die gezogene Karte verschmelzen lässt.
|
||||
## Platz einer Ansicht: Kacheln zählen an ihrem Platz, auch während sie dorthin gleiten.
|
||||
func _rect_of(v: Control) -> Rect2:
|
||||
return Rect2(v.slot, v.size) if v is CreatureView else v.get_global_rect()
|
||||
|
||||
|
||||
## Handkarte oder eigenes Wesen unter pos, mit dem sich die gezogene Karte verschmelzen lässt.
|
||||
func _fusion_target_at(pos: Vector2):
|
||||
for v in (hand_views if press_target is Cards.Card else own_views):
|
||||
for v in hand_views + own_views:
|
||||
var obj = v.card if v is CardView else v.creature
|
||||
var r: Rect2 = Rect2(v.slot, v.size) if v is CreatureView else v.get_global_rect() # Kacheln zählen an ihrem Platz, auch während sie dorthin gleiten
|
||||
if obj != press_target and r.has_point(pos) and game.can_fuse(perspective, press_target, obj)[0]:
|
||||
if obj != press_target and _rect_of(v).has_point(pos) and game.can_fuse(perspective, press_target, obj)[0]:
|
||||
return obj
|
||||
return null
|
||||
|
||||
@@ -581,9 +580,9 @@ func _view_of(obj) -> Control:
|
||||
return null
|
||||
|
||||
|
||||
## Mögliche Partner für „Verschmelzen mit …“: andere Handkarten bzw. andere eigene Wesen.
|
||||
## Mögliche Partner für „Verschmelzen mit …“: alle anderen Handkarten und eigenen Wesen.
|
||||
func fusion_partners(target) -> Array:
|
||||
var pool: Array = perspective.hand if target is Cards.Card else perspective.board
|
||||
var pool: Array = perspective.hand + perspective.board
|
||||
return pool.filter(func(o): return o != target and game.can_fuse(perspective, target, o)[0])
|
||||
|
||||
|
||||
@@ -675,6 +674,37 @@ func close_menu() -> void:
|
||||
menu_layer.visible = false
|
||||
Style.clear(menu_layer)
|
||||
menu_target = null
|
||||
fuse_pending = null
|
||||
|
||||
|
||||
## Nach dem Loslassen über einem Ziel: die neue Handkarte schwebt über dem Ziel, darunter
|
||||
## Bestätigen und Abbrechen; beide Ausgangskarten bleiben sichtbar. Klick daneben oder Esc bricht ab.
|
||||
func open_fuse_confirm(a, b, target: Rect2) -> void:
|
||||
cancel()
|
||||
Style.clear(menu_layer)
|
||||
fuse_pending = [a, b]
|
||||
var card := Game.fusion_result(perspective, a, b)
|
||||
var size := Vector2(BattleLayout.HAND_W, BattleLayout.HAND_H)
|
||||
var bw := 150.0
|
||||
var x := clampf(target.get_center().x - size.x / 2, 10 + bw - size.x / 2, 1270 - bw - size.x / 2)
|
||||
var y := maxf(10.0, target.position.y - size.y - 48)
|
||||
var v := CardView.new(card, size, false, Cards.RARITIES[card.look])
|
||||
v.position = Vector2(x, y)
|
||||
v.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
menu_layer.add_child(v)
|
||||
var by := y + size.y + 6
|
||||
var bx := x + size.x / 2 - bw - 3
|
||||
var yes := Style.button(menu_layer, tr("Verschmelzen"), Rect2(bx, by, bw, 36), func():
|
||||
var pair: Array = fuse_pending
|
||||
close_menu()
|
||||
do_fuse(pair[0], pair[1], Rect2(v.position, v.size))
|
||||
after_action(), Color("285a3c"))
|
||||
yes.add_theme_font_size_override("font_size", 15)
|
||||
var no := Style.button(menu_layer, tr("Abbrechen"), Rect2(bx + bw + 6, by, bw, 36), func():
|
||||
close_menu()
|
||||
refresh())
|
||||
no.add_theme_font_size_override("font_size", 15)
|
||||
menu_layer.visible = true
|
||||
|
||||
|
||||
## Abwerfen: eine Handkarte gegen Mana loswerden.
|
||||
@@ -696,13 +726,26 @@ func do_draw() -> void:
|
||||
fx.sfx("buy")
|
||||
|
||||
|
||||
## Verschmilzt zwei Handkarten oder zwei eigene Wesen (siehe Game.fuse).
|
||||
func do_fuse(a, b) -> void:
|
||||
## Verschmilzt zwei eigene Karten zur neuen Handkarte (siehe Game.fuse). from: von dort gleitet
|
||||
## die neue Karte in die Hand, Eltern auf der Hand blenden aus (Wesen tut das der Abgleich).
|
||||
func do_fuse(a, b, from: Rect2 = Rect2()) -> void:
|
||||
cancel()
|
||||
clear_undo()
|
||||
var before: Array = perspective.hand.duplicate()
|
||||
var err := game.fuse(perspective, a, b)
|
||||
if err != "":
|
||||
say(tr(err))
|
||||
return
|
||||
if from.size.x <= 0:
|
||||
return
|
||||
for x in [a, b]:
|
||||
var pv := _view_of(x)
|
||||
if pv is CardView:
|
||||
pv.reparent(fx_layer)
|
||||
fx.fade_view(pv)
|
||||
for c in perspective.hand:
|
||||
if not (c in before):
|
||||
hand_arrivals[c] = from
|
||||
|
||||
|
||||
func clear_undo() -> void:
|
||||
@@ -747,7 +790,8 @@ func on_click(kind: String, obj) -> void:
|
||||
var first = fuse_first
|
||||
fuse_first = null
|
||||
if obj != null and obj != first and g.can_fuse(perspective, first, obj)[0]:
|
||||
do_fuse(first, obj)
|
||||
var tv := _view_of(obj)
|
||||
do_fuse(first, obj, _rect_of(tv) if tv != null else Rect2())
|
||||
else:
|
||||
say(tr("Abgebrochen"))
|
||||
after_action()
|
||||
@@ -846,6 +890,8 @@ func creature_info(cr: Game.Creature) -> String:
|
||||
var perk := cr.power() - cr.attack - cr.aura
|
||||
if perk > 0:
|
||||
parts.append(tr("Merkmal +%d") % perk)
|
||||
if cr.card.is_neutral():
|
||||
parts.append(tr(Cards.NEUTRAL_TEXT))
|
||||
for k in cr.keywords():
|
||||
if not (k in cr.card.keywords):
|
||||
parts.append(tr("%s durch das Element") % tr(k))
|
||||
@@ -1089,6 +1135,9 @@ func refresh() -> void:
|
||||
var arc: Array = BattleLayout.hand_arc(h.hand.size(), i)
|
||||
v.rotation_degrees = arc[0]
|
||||
v.position.y += arc[1]
|
||||
if hand_arrivals.has(card):
|
||||
fx.glide(v, hand_arrivals[card])
|
||||
hand_arrivals.erase(card)
|
||||
v.lift = mine
|
||||
v.bonus = g.forecast(h, card)
|
||||
if fuse_first != null and mine and g.can_fuse(h, fuse_first, card)[0]:
|
||||
|
||||
@@ -5,9 +5,9 @@ extends ScrollContainer
|
||||
signal card_clicked(card: Cards.Card)
|
||||
|
||||
const COLS := 6
|
||||
const CW := 112
|
||||
const CH := 157
|
||||
const GAP := 14
|
||||
const CW := 118
|
||||
const CH := 165
|
||||
const GAP := 10
|
||||
|
||||
var grid: GridContainer
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
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.
|
||||
## Oben Kosten (links) und Name, in der Mitte das Bild im Kreis, unten Angriff (links) und
|
||||
## Leben (rechts). Die Elemente färben den Hintergrund (verschmolzen: ein Streifen je Element),
|
||||
## das Bild trägt bei Verschmelzungen der stärkere Elternteil; die Seltenheit steckt in Name,
|
||||
## Bild, Werten und Rahmen (siehe Style).
|
||||
|
||||
signal clicked(view: CardView)
|
||||
signal pressed(view: CardView, pos: Vector2) # nur mit drag_enabled: der Kampf macht daraus Klick oder Ziehen
|
||||
@@ -17,7 +17,7 @@ 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)
|
||||
var drop := false # Ziel der gerade gezogenen Karte
|
||||
|
||||
|
||||
## focusable: per Gamepad oder Tab anwählbar (Sammlung, Booster); A bzw. Enter wirkt wie ein Klick.
|
||||
@@ -75,120 +75,35 @@ func _draw() -> void:
|
||||
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)
|
||||
var big := size.x >= 180
|
||||
var mid := size.x >= 140
|
||||
var f_name := 17 if big else (14 if mid else 11)
|
||||
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 pad := 8.0 if mid else 6.0
|
||||
var shiny := Style.rarity_stats_shiny(rarity)
|
||||
# Oben: Kosten links, daneben der Name; das Namensfeld ist genau so hoch wie der Kristall
|
||||
var top_h := r_cost * 2 + 4
|
||||
draw_rect(Rect2(2, 2, size.x - 4, top_h), Color(0.05, 0.05, 0.08, 0.6))
|
||||
Style.card_name(self, card.title(), f_name, rarity, Rect2(r_cost * 2 + 10, 2, size.x - r_cost * 2 - pad - 10, top_h), seed)
|
||||
# Mitte: das Bild im Kreis, so groß wie der Platz bis zu den Werten erlaubt
|
||||
var pic_top := top_h + 4
|
||||
var pic_bottom := size.y - r_stat - 4
|
||||
var r_pic := minf(size.x * 0.42, (pic_bottom - pic_top) / 2)
|
||||
Style.picture(self, card, Vector2(size.x / 2, (pic_top + pic_bottom) / 2), r_pic, rarity, seed)
|
||||
# Unten: Angriff links, Leben rechts
|
||||
Style.stat_circle(self, Vector2(r_stat - 2, size.y - r_stat + 2), r_stat, str(card.attack), Style.AMBER, f_stat, Color.WHITE, shiny)
|
||||
Style.stat_circle(self, Vector2(size.x - r_stat + 2, size.y - r_stat + 2), r_stat, str(card.health), Style.LIFE, f_stat, Color.WHITE, shiny)
|
||||
if bonus > 0:
|
||||
Style.text_outlined(self, "+%d" % bonus, 13 if big else 11, Style.GREEN, Vector2(r_stat * 2 + 4, size.y - r_stat + 2), "midleft", 3)
|
||||
Style.card_sheen(self, rect, rarity, seed)
|
||||
Style.border(self, rect, frame, 3 if big else 2, 8)
|
||||
Style.cost_crystal(self, Vector2(r_cost + 4, r_cost + 4), r_cost, card.cost, f_stat, shiny)
|
||||
Style.rarity_sparkle(self, rect, rarity, 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)
|
||||
if drop:
|
||||
Style.border(self, rect, Style.GOLD, 5, 8)
|
||||
|
||||
@@ -21,13 +21,14 @@ var b_craft: Button
|
||||
func _ready() -> void:
|
||||
Style.header(self, "Sammlung", func(): app.show_screen(MenuScreen.new()))
|
||||
Style.button(self, "Alle", Rect2(40, 86, 60, 34), set_filter.bind(""))
|
||||
for i in Cards.ELEMENTS.size():
|
||||
var e: String = Cards.ELEMENTS[i]
|
||||
var filters: Array = Cards.ELEMENTS + [Cards.NEUTRAL]
|
||||
for i in filters.size():
|
||||
var e: String = filters[i]
|
||||
var fb := Style.button(self, e, Rect2(108 + i * 82, 86, 78, 34), set_filter.bind(e), Style.darker(Cards.ELEMENT_COLORS[e], 0.8))
|
||||
fb.add_theme_font_size_override("font_size", 14)
|
||||
search_box = LineEdit.new()
|
||||
search_box.position = Vector2(444, 86)
|
||||
search_box.size = Vector2(220, 34)
|
||||
search_box.position = Vector2(526, 86)
|
||||
search_box.size = Vector2(190, 34)
|
||||
search_box.placeholder_text = "Suche"
|
||||
search_box.text_changed.connect(func(t: String):
|
||||
search = t
|
||||
|
||||
@@ -12,11 +12,11 @@ var width := 2
|
||||
var flash := false # kurz rot: wurde gerade angegriffen
|
||||
var slot: Vector2 # Zielposition im Raster
|
||||
var busy := false # wird gerade animiert (kein Neupositionieren)
|
||||
var matchup := 0 # Elementvorteil des gewählten Angreifers gegen dieses fremde Wesen (+1/-1); die Werte zeigen dann, was ihn erwartet
|
||||
var matchup := 0 # Elementvorteil des gewählten Angreifers gegen dieses fremde Wesen (Game.matchup_bonus); die Werte zeigen dann, was ihn erwartet
|
||||
var attack_shift := 0 # eigener Angreifer: Elementvorteil gegen das Ziel unter der Maus
|
||||
var doomed := false # stirbt bei dem Angriff, der gerade unter der Maus liegt
|
||||
var secret := false # verdecktes fremdes Wesen: nur der Rücken ist zu sehen
|
||||
var hold := 0.0 # Fortschritt des Verschmelzens, wenn eine Karte darüber gehalten wird (0..1)
|
||||
var drop := false # Ziel der gerade gezogenen Karte
|
||||
|
||||
|
||||
func _init(cr: Game.Creature, sz: Vector2, b = null, w: int = 2) -> void:
|
||||
@@ -54,7 +54,7 @@ func _draw() -> void:
|
||||
var rect := Rect2(Vector2.ZERO, size)
|
||||
if secret:
|
||||
_draw_back(rect)
|
||||
_draw_hold()
|
||||
_draw_drop()
|
||||
return
|
||||
var small := size.x < 100
|
||||
var rar := rarity()
|
||||
@@ -62,44 +62,41 @@ func _draw() -> void:
|
||||
if rar != "Common":
|
||||
Style.glow(self, rect, Style.rarity_color(rar, seed), 6, 6)
|
||||
Style.fill_elements(self, rect, creature.card.elements, 6)
|
||||
var gc := Vector2(size.x / 2, size.y * 0.5)
|
||||
if creature.card.is_combo():
|
||||
Style.element_medallion(self, gc, size.x * 0.42, creature.card.elements)
|
||||
Style.glyph(self, creature.card, gc, size.x * 0.75, 0.3, Style.rarity_relief(rar))
|
||||
Style.rarity_sheen(self, rect, rar, seed, rect)
|
||||
var bcol: Color = border if border != null else Style.rarity_frame(rar, seed)
|
||||
if flash:
|
||||
bcol = Style.RED
|
||||
Style.border(self, rect, bcol, 4 if flash else width, 6)
|
||||
var fs := 11 if small else 13
|
||||
var name_w: float = size.x - 6
|
||||
var nf := Style.fit_size(creature.card.title(), true, fs, name_w)
|
||||
draw_rect(Rect2(2, 2, size.x - 4, Style.fs(fs) + 8), Color(0.05, 0.05, 0.08, 0.7))
|
||||
Style.text(self, Style.fit(creature.card.title(), true, Style.fs(nf), name_w), nf, Style.rarity_name_color(rar, seed), Vector2(3 + name_w / 2, 6), "midtop", true)
|
||||
# Elementbonus als ein Abzeichen rechts unter dem Namen; die Symbole rücken dafür nach links
|
||||
var badge := ""
|
||||
var bw := 0.0
|
||||
if creature.aura != 0:
|
||||
badge = "+%d" % creature.aura if creature.aura > 0 else "−%d" % -creature.aura
|
||||
bw = Style.FONT_BOLD.get_string_size(badge, HORIZONTAL_ALIGNMENT_LEFT, -1, Style.fs(10)).x + 8
|
||||
var by := Style.fs(fs) + 13
|
||||
draw_rect(Rect2(size.x - bw - 4, by, bw, 16), Color(0.05, 0.05, 0.08, 0.75))
|
||||
Style.text(self, badge, 10, Style.GREEN if creature.aura > 0 else Style.RED, Vector2(size.x - 4 - bw / 2, by + 8), "center", true)
|
||||
# Eigenschaften als Symbole in der Farbe des Elements, das sie gibt
|
||||
var icon := 7.0 if small else 9.0
|
||||
var kws := creature.keywords()
|
||||
var avail := size.x - (bw + 8 if badge != "" else 0.0)
|
||||
var x := avail / 2 - (kws.size() * (icon * 2 + 6) - 6) / 2.0
|
||||
var y := Style.fs(fs) + 14 + icon
|
||||
for k in kws:
|
||||
var shiny := Style.rarity_stats_shiny(rar)
|
||||
# Oben: Kosten links, daneben der Name; das Namensfeld ist genau so hoch wie der Kristall
|
||||
var fs := 12 if small else 13
|
||||
var r_cost := 10.0 if small else 11.0
|
||||
var top_h := r_cost * 2 + 4
|
||||
draw_rect(Rect2(2, 2, size.x - 4, top_h), Color(0.05, 0.05, 0.08, 0.6))
|
||||
Style.card_name(self, creature.card.title(), fs, rar, Rect2(r_cost * 2 + 10, 2, size.x - r_cost * 2 - 16, top_h), seed)
|
||||
# Mitte: das Bild im Kreis; unten Angriff links, Leben rechts
|
||||
var r_stat := 12.0 if small else 14.0
|
||||
var pic_top := top_h + 4
|
||||
var pic_bottom := size.y - r_stat - 4
|
||||
var r_pic := minf(size.x * 0.42, (pic_bottom - pic_top) / 2)
|
||||
Style.picture(self, creature.card, Vector2(size.x / 2, (pic_top + pic_bottom) / 2), r_pic, rar, seed)
|
||||
# Unter dem Namen links die Eigenschaften (in der Farbe des Elements, das sie gibt), rechts der Elementbonus
|
||||
var icon := 6.0 if small else 7.0
|
||||
var y := top_h + 6 + icon
|
||||
for k in creature.keywords():
|
||||
var col := Color("f2f2f6")
|
||||
for e in creature.card.elements:
|
||||
if Cards.ELEMENT_KEYWORD.get(e, "") == k:
|
||||
col = Style.lighter(Cards.ELEMENT_COLORS[e], 0.45)
|
||||
Style.keyword_icon(self, k, Vector2(x + icon, y), icon, col)
|
||||
x += icon * 2 + 6
|
||||
_draw_stats(15 if small else 17, 12.0 if small else 14.0)
|
||||
Style.rarity_gem(self, rar, Vector2(size.x / 2, size.y - 9), 5, seed)
|
||||
Style.keyword_icon(self, k, Vector2(6 + icon, y), icon, col)
|
||||
y += icon * 2 + 6
|
||||
if creature.aura != 0:
|
||||
var badge := "+%d" % creature.aura if creature.aura > 0 else "−%d" % -creature.aura
|
||||
var bw := Style.FONT_BOLD.get_string_size(badge, HORIZONTAL_ALIGNMENT_LEFT, -1, Style.fs(10)).x + 8
|
||||
draw_rect(Rect2(size.x - bw - 4, top_h + 5, bw, 16), Color(0.05, 0.05, 0.08, 0.75))
|
||||
Style.text(self, badge, 10, Style.GREEN if creature.aura > 0 else Style.RED, Vector2(size.x - 4 - bw / 2, top_h + 13), "center", true)
|
||||
_draw_stats(15 if small else 17, r_stat, shiny)
|
||||
Style.card_sheen(self, rect, rar, seed)
|
||||
Style.border(self, rect, bcol, 4 if flash else width, 6)
|
||||
Style.cost_crystal(self, Vector2(r_cost + 4, r_cost + 4), r_cost, creature.card.cost, 13, shiny)
|
||||
if creature.hidden:
|
||||
draw_rect(rect, Color(0.05, 0.05, 0.1, 0.55))
|
||||
Style.text_outlined(self, tr("verdeckt"), 12, Style.TEXT, Vector2(size.x / 2, size.y / 2), "center", 3)
|
||||
@@ -107,17 +104,13 @@ func _draw() -> void:
|
||||
draw_rect(Rect2(0, size.y * 0.5 - 12, size.x, 24), Color(0.05, 0.02, 0.02, 0.8))
|
||||
Style.text_outlined(self, tr("stirbt"), 14, Style.RED, Vector2(size.x / 2, size.y * 0.5), "center", 3)
|
||||
Style.rarity_sparkle(self, rect, rar, seed)
|
||||
_draw_hold()
|
||||
_draw_drop()
|
||||
|
||||
|
||||
## Ring, der sich füllt, solange eine Karte zum Verschmelzen darüber gehalten wird.
|
||||
func _draw_hold() -> void:
|
||||
if hold <= 0.0:
|
||||
return
|
||||
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)
|
||||
## Goldener Rahmen, solange eine Karte zum Verschmelzen darüber schwebt.
|
||||
func _draw_drop() -> void:
|
||||
if drop:
|
||||
Style.border(self, Rect2(Vector2.ZERO, size), Style.GOLD, 5, 6)
|
||||
|
||||
|
||||
## Verdecktes fremdes Wesen: ein Kartenrücken.
|
||||
@@ -143,11 +136,11 @@ func shown_stats() -> Array:
|
||||
|
||||
|
||||
## Angriff gelb, Leben blau; die Zahl wird grün, wenn sie für den aktuellen Angriff steigt, rot, wenn sie sinkt.
|
||||
func _draw_stats(f: int, r: float) -> void:
|
||||
func _draw_stats(f: int, r: float, shiny: bool) -> void:
|
||||
var shown := shown_stats()
|
||||
var atk: int = shown[0]
|
||||
var hp: int = shown[1]
|
||||
var atk_text := Style.GREEN if atk > creature.power() else (Style.RED if atk < creature.power() else Color.WHITE)
|
||||
var hp_text := Style.GREEN if hp > creature.health else (Style.RED if hp < creature.health else Color.WHITE)
|
||||
Style.stat_circle(self, Vector2(r - 1, size.y - r + 1), r, str(atk), Style.AMBER, f, atk_text)
|
||||
Style.stat_circle(self, Vector2(size.x - r + 1, size.y - r + 1), r, str(hp), Style.LIFE, f, hp_text)
|
||||
Style.stat_circle(self, Vector2(r - 1, size.y - r + 1), r, str(atk), Style.AMBER, f, atk_text, shiny)
|
||||
Style.stat_circle(self, Vector2(size.x - r + 1, size.y - r + 1), r, str(hp), Style.LIFE, f, hp_text, shiny)
|
||||
|
||||
@@ -2,20 +2,23 @@ class_name DeckScreen
|
||||
extends Control
|
||||
## Deck zusammenstellen: alle eigenen Karten im Raster, ein Klick nimmt eine Karte in den
|
||||
## Stapel oder wieder heraus. Karten außerhalb des Stapels sind abgedunkelt. Der Stapel hält
|
||||
## 40 bis 60 Karten; „Auffüllen“ nimmt die teuersten eigenen Karten des gewählten Filters.
|
||||
## 40 bis 60 Karten; „Auffüllen“ nimmt die wertvollsten eigenen Karten des gewählten Filters.
|
||||
## „Stärke“ ist der mittlere Kartenwert (100 Grundkarten … 500 Ultimate Rare).
|
||||
|
||||
var app
|
||||
var filter := ""
|
||||
var grid: CardGrid
|
||||
var count_label: Label
|
||||
var strength_label: Label
|
||||
var msg_label: Label
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Style.header(self, "Deck", func(): app.show_screen(MenuScreen.new()))
|
||||
Style.button(self, "Alle", Rect2(40, 86, 60, 34), set_filter.bind(""))
|
||||
for i in Cards.ELEMENTS.size():
|
||||
var e: String = Cards.ELEMENTS[i]
|
||||
var filters: Array = Cards.ELEMENTS + [Cards.NEUTRAL]
|
||||
for i in filters.size():
|
||||
var e: String = filters[i]
|
||||
var fb := Style.button(self, e, Rect2(108 + i * 82, 86, 78, 34), set_filter.bind(e), Style.darker(Cards.ELEMENT_COLORS[e], 0.8))
|
||||
fb.add_theme_font_size_override("font_size", 14)
|
||||
grid = CardGrid.new(Rect2(40, 132, CardGrid.COLS * (CardGrid.CW + CardGrid.GAP) + 10, 650))
|
||||
@@ -23,11 +26,13 @@ func _ready() -> void:
|
||||
add_child(grid)
|
||||
Style.panel(self, Rect2(820, 86, 420, 696))
|
||||
count_label = Style.chip(self, "", Rect2(860, 100, 340, 36), Style.BLUE)
|
||||
var hint := Style.label(self, "Klick nimmt eine Karte in den Stapel oder heraus. Auffüllen nimmt die teuersten Karten des Filters.", 15, Style.MUTED, Rect2(840, 160, 380, 60), "center")
|
||||
hint.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
Style.button(self, "Auffüllen", Rect2(840, 240, 380, 46), fill_up)
|
||||
Style.button(self, "Leeren", Rect2(840, 296, 380, 46), clear_deck, Color("5a3c28"))
|
||||
msg_label = Style.label(self, "", 15, Style.RED, Rect2(840, 360, 380, 48), "center")
|
||||
strength_label = Style.chip(self, "", Rect2(860, 144, 340, 36), Style.GOLD)
|
||||
var hint := Style.label(self, "", 15, Style.MUTED, Rect2(840, 188, 380, 66), "center")
|
||||
hint.autowrap_mode = TextServer.AUTOWRAP_WORD # vor dem Text, sonst wächst die Breite auf eine Zeile
|
||||
hint.text = "Klick nimmt eine Karte in den Stapel oder heraus. Auffüllen nimmt die wertvollsten Karten des Filters."
|
||||
Style.button(self, "Auffüllen", Rect2(840, 262, 380, 46), fill_up)
|
||||
Style.button(self, "Leeren", Rect2(840, 318, 380, 46), clear_deck, Color("5a3c28"))
|
||||
msg_label = Style.label(self, "", 15, Style.RED, Rect2(840, 382, 380, 48), "center")
|
||||
refresh()
|
||||
|
||||
|
||||
@@ -50,13 +55,13 @@ func toggle(card: Cards.Card) -> void:
|
||||
refresh()
|
||||
|
||||
|
||||
## Füllt mit den teuersten eigenen Karten des gewählten Filters bis zum Höchstwert auf.
|
||||
## Füllt mit den wertvollsten eigenen Karten des gewählten Filters bis zum Höchstwert auf.
|
||||
func fill_up() -> void:
|
||||
var p: Profile = app.profile
|
||||
var pool: Array = p.collection.keys()
|
||||
if filter != "":
|
||||
pool = pool.filter(func(id): return filter in Cards.by_id(id).elements)
|
||||
p.deck = DeckGen.fill(p.deck, pool, Game.DECK_MAX)
|
||||
p.deck = DeckGen.fill(p.deck, pool, Game.DECK_MAX, p.collection)
|
||||
app.save()
|
||||
refresh()
|
||||
|
||||
@@ -67,7 +72,7 @@ func clear_deck() -> void:
|
||||
refresh()
|
||||
|
||||
|
||||
## Eigene Karten, die im Stapel zuerst, sonst nach Kosten.
|
||||
## Eigene Karten, die im Stapel zuerst, dann höchste Stufen vorn, dann nach Kosten.
|
||||
func cards() -> Array:
|
||||
var p: Profile = app.profile
|
||||
var out: Array = p.collection.keys().map(func(id): return Cards.by_id(id))
|
||||
@@ -78,6 +83,8 @@ func cards() -> Array:
|
||||
var ib: bool = b.id in p.deck
|
||||
if ia != ib:
|
||||
return ia
|
||||
if p.level(a.id) != p.level(b.id):
|
||||
return p.level(a.id) > p.level(b.id)
|
||||
return a.cost < b.cost if a.cost != b.cost else a.id < b.id)
|
||||
return out
|
||||
|
||||
@@ -86,6 +93,7 @@ func refresh() -> void:
|
||||
var p: Profile = app.profile
|
||||
count_label.text = tr("Stapel: %d Karten (%d bis %d)") % [p.deck.size(), Game.DECK_MIN, Game.DECK_MAX]
|
||||
count_label.add_theme_color_override("font_color", Style.RED if p.deck.size() < Game.DECK_MIN else Style.BLUE)
|
||||
strength_label.text = tr("Stärke %d") % DeckGen.strength(p.deck, p.collection)
|
||||
var shown := cards()
|
||||
grid.fill(shown.map(func(c): return c.leveled(p.level(c.id))), func(c): return Cards.RARITIES[c.look])
|
||||
var views := grid.views()
|
||||
|
||||
@@ -2,8 +2,8 @@ class_name ElementView
|
||||
extends Control
|
||||
## Elementzähler einer Seite als Liste neben ihrem Brett. Es stehen nur Elemente da, bei denen
|
||||
## etwas passiert ist: je Zeile Name, zweimal das Symbol seines Sondereffekts (leuchtend je
|
||||
## offenem Wesen, beide: Effekt aktiv), die Zahl der ausgespielten Karten und ab
|
||||
## Game.ELEMENT_COUNT der Bonus +1/+1.
|
||||
## offenem Wesen, beide: Effekt aktiv; Neutral hat keinen), die Zahl der ausgespielten Karten
|
||||
## und ab Game.ELEMENT_COUNT der Bonus +1/+1.
|
||||
|
||||
const ROW := 32.0
|
||||
|
||||
@@ -15,20 +15,24 @@ func _init(rect: Rect2) -> void:
|
||||
position = rect.position
|
||||
size = rect.size
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
for e in Cards.ELEMENTS:
|
||||
for e in all():
|
||||
own[e] = 0
|
||||
played[e] = 0
|
||||
|
||||
|
||||
static func all() -> Array:
|
||||
return Cards.ELEMENTS + [Cards.NEUTRAL]
|
||||
|
||||
|
||||
func update_from(_g: Game, me: Game.Player) -> void:
|
||||
for e in Cards.ELEMENTS:
|
||||
for e in all():
|
||||
own[e] = me.element_count(e) if me != null else 0
|
||||
played[e] = int(me.played.get(e, 0)) if me != null else 0
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func paired(e: String) -> bool:
|
||||
return int(own.get(e, 0)) >= Game.ELEMENT_PAIR
|
||||
return Cards.ELEMENT_KEYWORD.has(e) and int(own.get(e, 0)) >= Game.ELEMENT_PAIR
|
||||
|
||||
|
||||
func bonus(e: String) -> int:
|
||||
@@ -37,7 +41,7 @@ func bonus(e: String) -> int:
|
||||
|
||||
## Elemente, bei denen sich etwas getan hat (offene Wesen oder ausgespielte Karten).
|
||||
func shown() -> Array:
|
||||
return Cards.ELEMENTS.filter(func(e): return int(own.get(e, 0)) > 0 or int(played.get(e, 0)) > 0)
|
||||
return all().filter(func(e): return int(own.get(e, 0)) > 0 or int(played.get(e, 0)) > 0)
|
||||
|
||||
|
||||
## Der Inhalt als Text (Tests, Protokoll): Element offen/gespielt, dazu Effekt und Bonus.
|
||||
@@ -62,9 +66,10 @@ func _draw() -> void:
|
||||
var px := 10.0
|
||||
Style.text(self, tr(e), 13, Style.TEXT if active else Style.MUTED, Vector2(px, y), "midleft", active)
|
||||
px = 76
|
||||
for k in Game.ELEMENT_PAIR:
|
||||
var filled := k < int(own.get(e, 0))
|
||||
Style.keyword_icon(self, Cards.ELEMENT_KEYWORD[e], Vector2(px + k * 24 + 10, y), 8, Style.lighter(col, 0.45) if filled else Color(col, 0.3))
|
||||
if Cards.ELEMENT_KEYWORD.has(e):
|
||||
for k in Game.ELEMENT_PAIR:
|
||||
var filled := k < int(own.get(e, 0))
|
||||
Style.keyword_icon(self, Cards.ELEMENT_KEYWORD[e], Vector2(px + k * 24 + 10, y), 8, Style.lighter(col, 0.45) if filled else Color(col, 0.3))
|
||||
px += Game.ELEMENT_PAIR * 24 + 8
|
||||
Style.text(self, str(int(played.get(e, 0))), 15, Style.lighter(col, 0.45), Vector2(px, y), "midleft", true)
|
||||
if bonus(e) > 0:
|
||||
|
||||
@@ -91,6 +91,7 @@ func _map_gamepad() -> void:
|
||||
if not InputMap.action_get_events(action).any(func(e): return e is InputEventJoypadButton):
|
||||
var ev := InputEventJoypadButton.new()
|
||||
ev.button_index = pair[1]
|
||||
ev.device = -1 # jedes Gamepad
|
||||
InputMap.action_add_event(action, ev)
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ func _ready() -> void:
|
||||
sample.sort_custom(func(a, b): return Cards.by_id(a).cost > Cards.by_id(b).cost)
|
||||
for k in 3:
|
||||
var c: Cards.Card = Cards.by_id(sample[k * 7])
|
||||
var v := CardView.new(c, Vector2(110, 140), false, "Common")
|
||||
var v := CardView.new(c, Vector2(120, 152), false, "Common")
|
||||
v.position = Vector2(x + 20 + k * 40, 210 + k * 14)
|
||||
v.rotation_degrees = -6 + k * 6
|
||||
v.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
|
||||
@@ -151,7 +151,9 @@ static func header(parent: Node, title: String, on_back: Callable) -> Button:
|
||||
## Kleine Kachel mit Text, etwa für Münzen und Staub.
|
||||
static func chip(parent: Node, text: String, rect: Rect2, color: Color = GOLD) -> Label:
|
||||
panel(parent, rect, darker(PANEL, 0.9), rect.size.y / 2, PANEL_LIGHT, 1)
|
||||
return label(parent, text, 17, color, rect, "center", true)
|
||||
var l := label(parent, text, 17, color, rect, "center", true)
|
||||
l.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
return l
|
||||
|
||||
|
||||
## Vertikal zentrierte Spalte (Container-Layout für Menübildschirme).
|
||||
@@ -309,13 +311,14 @@ static func border(ci: CanvasItem, rect: Rect2, color: Color, width: int, radius
|
||||
|
||||
|
||||
# --- Seltenheit als Aussehen ------------------------------------------------------
|
||||
# Common matt; Rare silberner Name und Rahmen mit stehendem Glanzstreifen; Super Rare Holo-Bild
|
||||
# (wandernder Regenbogenschimmer); Ultra Rare goldener Name und Rahmen plus Holo-Bild;
|
||||
# Ultimate Rare Relief, Holo auf Bild und Rahmen, Funkeln, schillernde Farben.
|
||||
# Common matt; Rare silbern glänzender Name; Super Rare Holo-Bild (wandernder Regenbogen im
|
||||
# Bildkreis); Ultra Rare goldener Name und Holo-Bild; Ultimate Rare goldener Name, goldenes
|
||||
# Bild, glänzende Werte, Schimmer über die ganze Karte, Funkeln und schillernder Rahmen.
|
||||
# Rahmen und Schein tragen ab Rare die Stufenfarbe.
|
||||
|
||||
## Ab Super Rare bewegt sich der Glanz; dann zeichnet die Karte jeden Frame neu.
|
||||
## Ab Rare bewegt sich der Glanz; dann zeichnet die Karte jeden Frame neu.
|
||||
static func rarity_animated(rar: String) -> bool:
|
||||
return animations and Cards.RARITIES.find(rar) >= 2
|
||||
return animations and Cards.RARITIES.find(rar) >= 1
|
||||
|
||||
|
||||
## Farbe einer Stufe; Ultimate Rare schillert über die Zeit durch alle Farben.
|
||||
@@ -330,56 +333,138 @@ static func rarity_frame(rar: String, seed: int = 0) -> Color:
|
||||
return Color("3a3e4c") if rar == "Common" else rarity_color(rar, seed)
|
||||
|
||||
|
||||
## Farbe des Kartennamens: Rare silbern, Ultra Rare golden, Ultimate Rare schillernd, sonst weiß.
|
||||
static func rarity_name_color(rar: String, seed: int = 0) -> Color:
|
||||
## Farbe des Kartennamens: Rare silbern, Ultra und Ultimate Rare golden, sonst weiß.
|
||||
static func rarity_name_color(rar: String) -> Color:
|
||||
match rar:
|
||||
"Rare", "Ultra Rare", "Ultimate Rare":
|
||||
return lighter(rarity_color(rar, seed), 0.25)
|
||||
"Rare":
|
||||
return Cards.RARITY_COLORS["Rare"]
|
||||
"Ultra Rare", "Ultimate Rare":
|
||||
return Cards.RARITY_COLORS["Ultra Rare"]
|
||||
return TEXT
|
||||
|
||||
|
||||
## Der Name glänzt bei Rare (silbern), Ultra und Ultimate Rare (golden).
|
||||
static func rarity_name_shiny(rar: String) -> bool:
|
||||
return rar in ["Rare", "Ultra Rare", "Ultimate Rare"]
|
||||
|
||||
|
||||
## Ultimate Rare: auch Kosten, Angriff und Leben glänzen.
|
||||
static func rarity_stats_shiny(rar: String) -> bool:
|
||||
return rar == "Ultimate Rare"
|
||||
|
||||
|
||||
## Ultimate Rare zeigt das Bild als Relief (Licht- und Schattenkante).
|
||||
static func rarity_relief(rar: String) -> bool:
|
||||
return Cards.RARITIES.find(rar) >= 4
|
||||
return rar == "Ultimate Rare"
|
||||
|
||||
|
||||
## Glanz wie auf einer Folienkarte. Rare: ein stehender weißer Streifen über die Karte.
|
||||
## Ab Super Rare: Holo, ein wandernder Regenbogenschimmer über das Bild (picture); Ultimate Rare
|
||||
## zusätzlich schwach über die ganze Karte.
|
||||
static func rarity_sheen(ci: CanvasItem, rect: Rect2, rar: String, seed: int, picture: Rect2 = Rect2()) -> void:
|
||||
var lvl := Cards.RARITIES.find(rar)
|
||||
if lvl < 1:
|
||||
return
|
||||
if picture.size.x <= 0:
|
||||
picture = rect
|
||||
if lvl == 1:
|
||||
_sheen_bands(ci, rect, (seed % 100) / 100.0, [[1.0, Color(1, 1, 1, 0.07)], [0.55, Color(1, 1, 1, 0.09)], [0.22, Color(1, 1, 1, 0.13)]])
|
||||
## Kartenname links in rect, immer eine Zeile, nie abgeschnitten: die Schrift schrumpft bis
|
||||
## drei Punkte unter die Vorgabe, danach wird der Text schmaler gezeichnet (wie bei langen
|
||||
## Yu-Gi-Oh-Namen).
|
||||
static func card_name(ci: CanvasItem, s: String, size_in: int, rar: String, rect: Rect2, seed: int) -> void:
|
||||
var fsize := fit_size(s, true, size_in, rect.size.x, maxi(8, size_in - 3))
|
||||
var w := FONT_BOLD.get_string_size(s, HORIZONTAL_ALIGNMENT_LEFT, -1, fs(fsize)).x
|
||||
var squeeze := minf(1.0, rect.size.x / maxf(w, 1.0))
|
||||
var y := rect.position.y + (rect.size.y - FONT_BOLD.get_height(fs(fsize))) / 2
|
||||
ci.draw_set_transform(Vector2(rect.position.x, y), 0.0, Vector2(squeeze, 1.0))
|
||||
if rarity_name_shiny(rar):
|
||||
shiny_text(ci, s, fsize, rarity_name_color(rar), Vector2.ZERO, "topleft", seed)
|
||||
else:
|
||||
text(ci, s, fsize, rarity_name_color(rar), Vector2.ZERO, "topleft", true)
|
||||
ci.draw_set_transform(Vector2.ZERO)
|
||||
|
||||
|
||||
## Metallisch glänzender fetter Text: dunkler Schatten, Grundfarbe, darüber Buchstabe für
|
||||
## Buchstabe ein heller Streifen, der mit Animation über den Text läuft.
|
||||
static func shiny_text(ci: CanvasItem, s: String, size_in: int, color: Color, pos: Vector2, anchor: String, seed: int) -> void:
|
||||
var size := fs(size_in)
|
||||
var dim := FONT_BOLD.get_string_size(s, HORIZONTAL_ALIGNMENT_LEFT, -1, size)
|
||||
var x := pos.x
|
||||
var y := pos.y
|
||||
match anchor:
|
||||
"center":
|
||||
x -= dim.x / 2
|
||||
y -= dim.y / 2
|
||||
"midtop":
|
||||
x -= dim.x / 2
|
||||
"midleft":
|
||||
y -= dim.y / 2
|
||||
var phase := (seed % 100) / 100.0
|
||||
if animations:
|
||||
phase = fmod(Time.get_ticks_msec() / 1800.0 + phase, 1.0)
|
||||
var at := Vector2(x, y + FONT_BOLD.get_ascent(size))
|
||||
ci.draw_string(FONT_BOLD, at + Vector2(1, 1), s, HORIZONTAL_ALIGNMENT_LEFT, -1, size, Color(0, 0, 0, 0.55))
|
||||
var cx := x
|
||||
for i in s.length():
|
||||
var ch := s[i]
|
||||
var w := FONT_BOLD.get_string_size(ch, HORIZONTAL_ALIGNMENT_LEFT, -1, size).x
|
||||
var d := absf(fmod((cx + w / 2 - x) / maxf(dim.x, 1.0) - phase * 1.6 + 0.3 + 2.0, 1.0) - 0.5) * 2.0 # 0 am Streifen
|
||||
var k := clampf(1.0 - d * 2.4, 0.0, 1.0)
|
||||
ci.draw_string(FONT_BOLD, Vector2(cx, at.y), ch, HORIZONTAL_ALIGNMENT_LEFT, -1, size, color.lerp(Color.WHITE, 0.1 + 0.75 * k))
|
||||
cx += w
|
||||
|
||||
|
||||
## Glanz wie auf einer Folienkarte, auf clip beschnitten. lvl 2 und 3: Holo, ein wandernder
|
||||
## Regenbogenschimmer; lvl 4: goldene Bänder.
|
||||
static func sheen(ci: CanvasItem, clip: PackedVector2Array, bounds: Rect2, seed: int, gold: bool = false) -> void:
|
||||
var phase := (seed % 100) / 100.0
|
||||
if animations:
|
||||
phase = fmod(Time.get_ticks_msec() / 2500.0 + phase, 1.0)
|
||||
var layers := []
|
||||
for i in 4:
|
||||
var col := Color(1.0, 0.86, 0.45, 0.2 - i * 0.03) if gold else Color.from_hsv(fmod(phase + i * 0.17, 1.0), 0.6, 1.0, 0.16)
|
||||
layers.append([1.0 - i * 0.22, col])
|
||||
_sheen_bands(ci, clip, bounds, phase, layers)
|
||||
|
||||
|
||||
## Ultimate Rare: schwacher schillernder Schimmer über die ganze Karte.
|
||||
static func card_sheen(ci: CanvasItem, rect: Rect2, rar: String, seed: int) -> void:
|
||||
if rar != "Ultimate Rare":
|
||||
return
|
||||
var phase := (seed % 100) / 100.0
|
||||
if animations:
|
||||
phase = fmod(Time.get_ticks_msec() / 2500.0 + phase, 1.0)
|
||||
var holo := []
|
||||
for i in 4:
|
||||
holo.append([1.0 - i * 0.22, Color.from_hsv(fmod(phase + i * 0.17, 1.0), 0.6, 1.0, 0.16)])
|
||||
_sheen_bands(ci, picture, phase, holo)
|
||||
if lvl >= 4:
|
||||
var soft := []
|
||||
for i in 3:
|
||||
soft.append([1.0 - i * 0.3, Color.from_hsv(fmod(phase + 0.5 + i * 0.2, 1.0), 0.5, 1.0, 0.07)])
|
||||
_sheen_bands(ci, rect, fmod(phase + 0.5, 1.0), soft)
|
||||
var soft := []
|
||||
for i in 3:
|
||||
soft.append([1.0 - i * 0.3, Color.from_hsv(fmod(phase + 0.5 + i * 0.2, 1.0), 0.5, 1.0, 0.07)])
|
||||
_sheen_bands(ci, rect_poly(rect), rect, fmod(phase + 0.5, 1.0), soft)
|
||||
|
||||
|
||||
## Schräge Streifen über rect, auf rect beschnitten. layers: [Breitenanteil, Farbe].
|
||||
static func _sheen_bands(ci: CanvasItem, rect: Rect2, phase: float, layers: Array) -> void:
|
||||
var frame := PackedVector2Array([rect.position, Vector2(rect.end.x, rect.position.y), rect.end, Vector2(rect.position.x, rect.end.y)])
|
||||
var w := rect.size.x * 0.34
|
||||
var k := rect.size.y * 0.75 # Schräge
|
||||
var x0 := rect.position.x - w + (rect.size.x + w + k) * phase
|
||||
static func rect_poly(rect: Rect2) -> PackedVector2Array:
|
||||
return PackedVector2Array([rect.position, Vector2(rect.end.x, rect.position.y), rect.end, Vector2(rect.position.x, rect.end.y)])
|
||||
|
||||
|
||||
static func circle_poly(c: Vector2, r: float, steps: int = 40) -> PackedVector2Array:
|
||||
var pts := PackedVector2Array()
|
||||
for i in steps:
|
||||
var a := TAU * i / steps
|
||||
pts.append(c + Vector2(cos(a), sin(a)) * r)
|
||||
return pts
|
||||
|
||||
|
||||
## Rechteck mit runden Ecken als Polygon (für Beschnitt).
|
||||
static func rounded_poly(rect: Rect2, radius: float) -> PackedVector2Array:
|
||||
var pts := PackedVector2Array()
|
||||
var r := minf(radius, minf(rect.size.x, rect.size.y) / 2)
|
||||
var corners := [[rect.end - Vector2(r, r), 0.0], [Vector2(rect.position.x + r, rect.end.y - r), PI / 2],
|
||||
[rect.position + Vector2(r, r), PI], [Vector2(rect.end.x - r, rect.position.y + r), PI * 1.5]]
|
||||
for corner in corners:
|
||||
for k in 7:
|
||||
var a: float = corner[1] + PI / 2 * k / 6
|
||||
pts.append(corner[0] + Vector2(cos(a), sin(a)) * r)
|
||||
return pts
|
||||
|
||||
|
||||
## Schräge Streifen über bounds, auf clip beschnitten. layers: [Breitenanteil, Farbe].
|
||||
static func _sheen_bands(ci: CanvasItem, clip: PackedVector2Array, bounds: Rect2, phase: float, layers: Array) -> void:
|
||||
var w := bounds.size.x * 0.34
|
||||
var k := bounds.size.y * 0.75 # Schräge
|
||||
var x0 := bounds.position.x - w + (bounds.size.x + w + k) * phase
|
||||
for layer in layers:
|
||||
var lw: float = w * layer[0]
|
||||
var lx := x0 + (w - lw) / 2
|
||||
var band := PackedVector2Array([Vector2(lx, rect.position.y), Vector2(lx + lw, rect.position.y), Vector2(lx + lw - k, rect.end.y), Vector2(lx - k, rect.end.y)])
|
||||
for poly in Geometry2D.intersect_polygons(band, frame):
|
||||
var band := PackedVector2Array([Vector2(lx, bounds.position.y), Vector2(lx + lw, bounds.position.y), Vector2(lx + lw - k, bounds.end.y), Vector2(lx - k, bounds.end.y)])
|
||||
for poly in Geometry2D.intersect_polygons(band, clip):
|
||||
if poly.size() >= 3 and _area(poly) > 2.0: # Splitter am Rand lassen sich nicht zeichnen
|
||||
ci.draw_colored_polygon(poly, layer[1])
|
||||
|
||||
@@ -394,16 +479,15 @@ static func _area(poly: PackedVector2Array) -> float:
|
||||
|
||||
|
||||
## Funkeln bei Ultimate Rare: kleine Sterne, die aufblitzen und vergehen; ganz zum Schluss zeichnen.
|
||||
## avoid: Bereich, der frei bleibt (der Kartentext).
|
||||
static func rarity_sparkle(ci: CanvasItem, rect: Rect2, rar: String, seed: int, avoid: Rect2 = Rect2()) -> void:
|
||||
if Cards.RARITIES.find(rar) < 4:
|
||||
static func rarity_sparkle(ci: CanvasItem, rect: Rect2, rar: String, seed: int) -> void:
|
||||
if rar != "Ultimate Rare":
|
||||
return
|
||||
var t := Time.get_ticks_msec() / 1000.0
|
||||
for i in 9:
|
||||
var h := absi(hash(Vector2i(seed, i)))
|
||||
var p := rect.position + Vector2(8 + (h % 1000) / 1000.0 * (rect.size.x - 16), 8 + ((h / 1000) % 1000) / 1000.0 * (rect.size.y - 16))
|
||||
var tw := 0.5 + 0.5 * sin(t * 2.5 + i * 1.9 + seed % 7) if animations else 0.35 + 0.65 * ((i * 7 + seed) % 5) / 4.0
|
||||
if tw < 0.15 or avoid.has_point(p):
|
||||
if tw < 0.15:
|
||||
continue
|
||||
var s := 2.0 + 4.0 * tw
|
||||
var col := Color(1, 1, 1, 0.3 + 0.65 * tw)
|
||||
@@ -422,65 +506,70 @@ static func glow(ci: CanvasItem, rect: Rect2, color: Color, radius: int = 8, spr
|
||||
ci.draw_style_box(sb, rect)
|
||||
|
||||
|
||||
## Edelstein in der Farbe der Stufe (ab Rare).
|
||||
static func rarity_gem(ci: CanvasItem, rar: String, c: Vector2, s: float, seed: int = 0) -> void:
|
||||
if rar == "Common":
|
||||
return
|
||||
var col := rarity_color(rar, seed)
|
||||
var diamond := func(h: float, k: Color) -> void:
|
||||
ci.draw_colored_polygon(PackedVector2Array([c + Vector2(0, -h), c + Vector2(h * 0.72, 0), c + Vector2(0, h), c + Vector2(-h * 0.72, 0)]), k)
|
||||
diamond.call(s + 2, Color("14141c"))
|
||||
diamond.call(s, col)
|
||||
ci.draw_colored_polygon(PackedVector2Array([c + Vector2(0, -s * 0.7), c + Vector2(s * 0.3, -s * 0.15), c + Vector2(-s * 0.35, -s * 0.1)]), Color(1, 1, 1, 0.55))
|
||||
|
||||
|
||||
## Hintergrund in der Farbe des ersten Elements; weitere Elemente zeigt element_medallion.
|
||||
## Hintergrund in den Farben der Elemente: ein Element füllt die Karte, verschmolzene Wesen
|
||||
## tragen je Element einen senkrechten Streifen.
|
||||
static func fill_elements(ci: CanvasItem, rect: Rect2, elements: Array, radius: int = 8) -> void:
|
||||
ci.draw_style_box(box(Cards.ELEMENT_COLORS[elements[0]], radius), rect)
|
||||
|
||||
|
||||
## Medaillon eines verschmolzenen Wesens: ein Kreis hinter dem Bild, als Tortenstücke in den
|
||||
## Farben seiner Elemente, mit dunklem Rand.
|
||||
static func element_medallion(ci: CanvasItem, c: Vector2, r: float, elements: Array) -> void:
|
||||
ci.draw_circle(c, r + 3, Color(0.05, 0.05, 0.08, 0.75))
|
||||
var n := elements.size()
|
||||
for i in n:
|
||||
var from := -PI / 2 + TAU * i / n
|
||||
var to := -PI / 2 + TAU * (i + 1) / n
|
||||
var pts := PackedVector2Array([c])
|
||||
var steps := 24
|
||||
for k in steps + 1:
|
||||
var ang := lerpf(from, to, float(k) / steps)
|
||||
pts.append(c + Vector2(cos(ang), sin(ang)) * r)
|
||||
ci.draw_colored_polygon(pts, Cards.ELEMENT_COLORS[elements[i]])
|
||||
for i in n:
|
||||
var ang := -PI / 2 + TAU * i / n
|
||||
ci.draw_line(c, c + Vector2(cos(ang), sin(ang)) * r, Color(0.05, 0.05, 0.08, 0.75), 2.0)
|
||||
if elements.size() < 2:
|
||||
return
|
||||
var clip := rounded_poly(rect, radius)
|
||||
var w := rect.size.x / elements.size()
|
||||
for i in range(1, elements.size()):
|
||||
var strip := Rect2(rect.position.x + w * i, rect.position.y, w, rect.size.y)
|
||||
for poly in Geometry2D.intersect_polygons(rect_poly(strip), clip):
|
||||
if poly.size() >= 3:
|
||||
ci.draw_colored_polygon(poly, Cards.ELEMENT_COLORS[elements[i]])
|
||||
ci.draw_line(strip.position + Vector2(0, 2), Vector2(strip.position.x, strip.end.y - 2), Color(0.05, 0.05, 0.08, 0.5), 1.5)
|
||||
|
||||
|
||||
# --- Kartenteile ------------------------------------------------------
|
||||
|
||||
## Kostenkristall: sechseckig, blau, Zahl in der Mitte.
|
||||
static func cost_crystal(ci: CanvasItem, c: Vector2, r: float, cost: int, font: int) -> void:
|
||||
## Bild der Karte: dunkler Kreis mit Ring in der Elementfarbe, darin das Symbol der Bildkarte
|
||||
## (bei Verschmelzungen die des stärkeren Elternteils). Ab Super Rare glänzt der Kreis (Holo),
|
||||
## bei Ultimate Rare golden und als Relief.
|
||||
static func picture(ci: CanvasItem, card, c: Vector2, r: float, rar: String, seed: int) -> void:
|
||||
var src = card.art_card()
|
||||
var ring := lighter(Cards.ELEMENT_COLORS[src.elements[0]], 0.3)
|
||||
ci.draw_circle(c, r + 3, Color(0.05, 0.05, 0.08, 0.8))
|
||||
ci.draw_circle(c, r, Color(0.09, 0.09, 0.13, 0.9))
|
||||
glyph(ci, src, c, r * 1.3, 0.95, rarity_relief(rar))
|
||||
var lvl := Cards.RARITIES.find(rar)
|
||||
if lvl >= 2:
|
||||
sheen(ci, circle_poly(c, r), Rect2(c - Vector2(r, r), Vector2(r * 2, r * 2)), seed, lvl >= 4)
|
||||
ci.draw_arc(c, r + 1.5, 0, TAU, 48, Color(1.0, 0.86, 0.45) if lvl >= 4 else ring, 3.0, true)
|
||||
|
||||
|
||||
## Kostenkristall: sechseckig, blau, Zahl in der Mitte; shiny: goldene, glänzende Zahl.
|
||||
static func cost_crystal(ci: CanvasItem, c: Vector2, r: float, cost: int, font: int, shiny: bool = false) -> void:
|
||||
var pts := PackedVector2Array()
|
||||
var outer := PackedVector2Array()
|
||||
for i in 6:
|
||||
var a := -PI / 2 + i * PI / 3
|
||||
pts.append(c + Vector2(cos(a), sin(a)) * r)
|
||||
outer.append(c + Vector2(cos(a), sin(a)) * (r + 2))
|
||||
ci.draw_colored_polygon(outer, Color("14141c"))
|
||||
ci.draw_colored_polygon(outer, Color(1.0, 0.86, 0.45) if shiny else Color("14141c"))
|
||||
ci.draw_colored_polygon(pts, MANA)
|
||||
var shine := PackedVector2Array([pts[5], pts[0], c + Vector2(0, -r * 0.1), c + Vector2(-r * 0.55, r * 0.15)])
|
||||
ci.draw_colored_polygon(shine, Color(1, 1, 1, 0.22))
|
||||
text_outlined(ci, str(cost), font, Color.WHITE, c, "center", 3)
|
||||
_stat_text(ci, str(cost), font, Color.WHITE, c, shiny)
|
||||
|
||||
|
||||
## Wertkreis (Angriff gelb, Leben rot) mit Zahl.
|
||||
static func stat_circle(ci: CanvasItem, c: Vector2, r: float, value: String, color: Color, font: int, text_col: Color = Color.WHITE) -> void:
|
||||
ci.draw_circle(c, r + 2, Color("14141c"))
|
||||
## Wertkreis (Angriff gelb, Leben blau) mit Zahl; shiny: goldener Ring, glänzende Zahl.
|
||||
static func stat_circle(ci: CanvasItem, c: Vector2, r: float, value: String, color: Color, font: int, text_col: Color = Color.WHITE, shiny: bool = false) -> void:
|
||||
ci.draw_circle(c, r + 2, Color(1.0, 0.86, 0.45) if shiny else Color("14141c"))
|
||||
ci.draw_circle(c, r, color)
|
||||
ci.draw_circle(c + Vector2(-r * 0.25, -r * 0.3), r * 0.45, Color(1, 1, 1, 0.16))
|
||||
text_outlined(ci, value, font, text_col, c, "center", 3)
|
||||
_stat_text(ci, value, font, text_col, c, shiny)
|
||||
|
||||
|
||||
static func _stat_text(ci: CanvasItem, s: String, font: int, col: Color, c: Vector2, shiny: bool) -> void:
|
||||
if shiny and col == Color.WHITE:
|
||||
var size := fs(font)
|
||||
var dim := FONT_BOLD.get_string_size(s, HORIZONTAL_ALIGNMENT_LEFT, -1, size)
|
||||
ci.draw_string_outline(FONT_BOLD, c - dim / 2 + Vector2(0, FONT_BOLD.get_ascent(size)), s, HORIZONTAL_ALIGNMENT_LEFT, -1, size, 3, Color(0.05, 0.05, 0.08, 0.95))
|
||||
shiny_text(ci, s, font, Color(1.0, 0.86, 0.45), c, "center", int(c.x + c.y))
|
||||
else:
|
||||
text_outlined(ci, s, font, col, c, "center", 3)
|
||||
|
||||
|
||||
## Eigenschaft als Symbol auf dunklem Kreis: Spott Ausrufezeichen, Flink Doppelpfeil,
|
||||
@@ -568,6 +657,11 @@ static func _element_glyph(ci: CanvasItem, element: String, c: Vector2, size: fl
|
||||
for i in 2 + variant:
|
||||
var r := h * (1.0 - i * 0.28)
|
||||
ci.draw_arc(c + Vector2(i * size * 0.06, 0), r, PI * 0.2 + i * 0.4, PI * 1.5 + i * 0.4, 20, col, maxf(2, size * 0.09))
|
||||
_: # Neutral: ein schlichter Schild
|
||||
var w := h * 0.8
|
||||
var pts := PackedVector2Array([c + Vector2(-w, -h * 0.8), c + Vector2(w, -h * 0.8), c + Vector2(w, h * 0.1), c + Vector2(0, h), c + Vector2(-w, h * 0.1)])
|
||||
ci.draw_polyline(pts + PackedVector2Array([pts[0]]), col, maxf(2, size * 0.09))
|
||||
ci.draw_line(c + Vector2(0, -h * 0.5), c + Vector2(0, h * 0.6), col, maxf(2, size * 0.07))
|
||||
|
||||
|
||||
static func _flame(ci: CanvasItem, c: Vector2, h: float, col: Color) -> void:
|
||||
|
||||
Reference in New Issue
Block a user