update
This commit is contained in:
@@ -32,8 +32,9 @@ func play_events(evs: Array, centers: Dictionary) -> void:
|
||||
"damage":
|
||||
sfx("damage")
|
||||
popup("-%d" % ev.amount, Style.RED, center_of(ev.target, centers), delay)
|
||||
"blocked":
|
||||
popup("Schild!", Style.BLUE, center_of(ev.target, centers), delay)
|
||||
"heal":
|
||||
sfx("heal")
|
||||
popup(tr("Regeneration"), Style.GREEN, screen.target_center({"kind": "creature", "uid": ev.uid}), 0.0)
|
||||
"play":
|
||||
sfx("click" if ev.get("hidden", false) else "play")
|
||||
"reveal":
|
||||
@@ -174,33 +175,6 @@ func fly_in(tile: CreatureView, from: Rect2) -> void:
|
||||
tile.position = tile.slot)
|
||||
|
||||
|
||||
## Eine gekaufte Karte fliegt aus dem Laden auf die Hand.
|
||||
func fly_card(card: Cards.Card, from: Rect2, to: Rect2, rarity: String) -> void:
|
||||
if not enabled:
|
||||
return
|
||||
var v := CardView.new(card, from.size, false, rarity)
|
||||
v.position = from.position
|
||||
v.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
v.pivot_offset = Vector2.ZERO
|
||||
screen.fx_layer.add_child(v)
|
||||
var tw: Tween = screen.create_tween().set_parallel(true)
|
||||
tw.tween_property(v, "position", to.position, 0.25).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN_OUT)
|
||||
tw.tween_property(v, "scale", to.size / from.size, 0.25).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN_OUT)
|
||||
tw.tween_property(v, "modulate:a", 0.0, 0.1).set_delay(0.2)
|
||||
tw.chain().tween_callback(v.queue_free)
|
||||
|
||||
|
||||
## Ladenkarten werden nacheinander ausgeteilt.
|
||||
func deal(v: CardView, i: int) -> void:
|
||||
if not enabled:
|
||||
return
|
||||
v.modulate.a = 0.0
|
||||
v.position.y += 24
|
||||
var tw: Tween = screen.create_tween().set_parallel(true)
|
||||
tw.tween_property(v, "modulate:a", 1.0, 0.15).set_delay(i * 0.05)
|
||||
tw.tween_property(v, "position:y", v.position.y - 24, 0.2).set_delay(i * 0.05).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
|
||||
|
||||
|
||||
## Kurzes Zittern des Bretts beim Treffer.
|
||||
func shake() -> void:
|
||||
if not enabled:
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
class_name BattleGamepad
|
||||
extends RefCounted
|
||||
## Gamepad-Steuerung des Kampfes: Steuerkreuz bewegt einen Zeiger über Stapel, Hand, eigene Wesen
|
||||
## und Ziele (bei offener Wahl über deren Karten), A bestätigt, B bricht ab, Start beendet den
|
||||
## Zug, Y öffnet das Kartenmenü (im Gegnerzug: überspringen), X macht rückgängig.
|
||||
## und Ziele, A bestätigt, B bricht ab, Start beendet den Zug, Y öffnet das Kartenmenü (im
|
||||
## Gegnerzug: überspringen), X macht rückgängig. Verschmelzen: im Menü „Verschmelzen mit …“,
|
||||
## dann A auf dem Partner.
|
||||
|
||||
var screen # BattleScreen
|
||||
var active := false
|
||||
@@ -19,12 +20,6 @@ func button(button: int) -> void:
|
||||
active = true
|
||||
if screen.game == null:
|
||||
return
|
||||
if not screen.pick_views.is_empty() and zone != "pick":
|
||||
zone = "pick"
|
||||
index = 0
|
||||
elif screen.pick_views.is_empty() and zone == "pick":
|
||||
zone = "hand"
|
||||
index = 0
|
||||
match button:
|
||||
JOY_BUTTON_START:
|
||||
if screen.my_turn() and not screen.any_overlay():
|
||||
@@ -44,9 +39,9 @@ func button(button: int) -> void:
|
||||
if not items.is_empty() and zone in ["hand", "own"]:
|
||||
var item = items[clampi(index, 0, items.size() - 1)]
|
||||
if zone == "hand":
|
||||
screen.open_menu(item.card, Rect2(item.position, item.size))
|
||||
screen.open_menu(item.card, Rect2(item.position, item.size), true)
|
||||
else:
|
||||
screen.open_menu(item.creature, Rect2(item.slot, item.size))
|
||||
screen.open_menu(item.creature, Rect2(item.slot, item.size), true)
|
||||
JOY_BUTTON_X:
|
||||
if screen.b_undo.visible:
|
||||
screen.undo()
|
||||
@@ -82,14 +77,10 @@ func items() -> Array:
|
||||
match zone:
|
||||
"deck":
|
||||
return [screen.deck] if screen.deck.visible else []
|
||||
"pick":
|
||||
return screen.pick_views
|
||||
"hand":
|
||||
return screen.hand_views
|
||||
"own":
|
||||
if screen.fusing: # zum Verschmelzen zählt jedes eigene Wesen, nicht nur angriffsbereite
|
||||
return screen.own_views
|
||||
return screen.own_views.filter(func(v): return v.creature.can_attack())
|
||||
return screen.own_views
|
||||
"target":
|
||||
return targets
|
||||
return []
|
||||
@@ -115,9 +106,6 @@ func confirm() -> void:
|
||||
match zone:
|
||||
"deck":
|
||||
screen.on_click("deck", null)
|
||||
"pick":
|
||||
screen.do_pick(index)
|
||||
index = 0
|
||||
"hand":
|
||||
screen.on_click("hand", item.card)
|
||||
"own":
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class_name BattleLayout
|
||||
## Feste Bildschirmaufteilung des Duells (1280x800), von unten nach oben: Hand mit dem
|
||||
## Zieh-Stapel links und Zug beenden rechts, eigenes Brett (mit eigener Leiste links),
|
||||
## Feldband, oben der Gegner mit seinem Brett. Eine offene Wahl liegt als Überlagerung darüber.
|
||||
## Elementband, oben der Gegner mit seinem Brett.
|
||||
|
||||
const B_GIVEUP := Rect2(10, 10, 96, 32)
|
||||
const B_LOG := Rect2(112, 10, 96, 32)
|
||||
@@ -10,8 +10,7 @@ const FIELD := Rect2(240, 226, 800, 32)
|
||||
const MSG := Rect2(240, 262, 800, 22)
|
||||
const OWN_PANEL := Rect2(240, 288, 800, 160)
|
||||
const STATUS := Rect2(20, 288, 210, 160)
|
||||
const B_FUSE := Rect2(1050, 298, 210, 40)
|
||||
const B_UNDO := Rect2(1050, 346, 210, 40)
|
||||
const B_UNDO := Rect2(1050, 298, 210, 40)
|
||||
const OWN_X := 345
|
||||
const HAND_W := 150
|
||||
const HAND_H := 205
|
||||
@@ -19,8 +18,6 @@ const HAND_GAP := 10
|
||||
const HAND_Y := 532
|
||||
const DECK := Rect2(50, 540, 130, 182)
|
||||
const B_END := Rect2(1060, 600, 200, 60)
|
||||
const PICK_PANEL := Rect2(120, 150, 1040, 440)
|
||||
const PICK_CARD := Vector2(180, 252)
|
||||
const LOG_PANEL := Rect2(240, 60, 800, 560)
|
||||
const PREVIEW_SIZE := Vector2(180, 252)
|
||||
const CENTER_X := 240
|
||||
@@ -36,14 +33,6 @@ static func opp_slot(j: int) -> Rect2:
|
||||
return Rect2(OWN_X + j * 120, 76, 110, 140)
|
||||
|
||||
|
||||
## Angebotene Karten einer Wahl, mittig im Wahlfenster.
|
||||
static func pick_rect(count: int, i: int) -> Rect2:
|
||||
var gap := 20
|
||||
var total := count * PICK_CARD.x + maxi(0, count - 1) * gap
|
||||
var x := PICK_PANEL.position.x + (PICK_PANEL.size.x - total) / 2
|
||||
return Rect2(Vector2(x + i * (PICK_CARD.x + gap), PICK_PANEL.position.y + 96), PICK_CARD)
|
||||
|
||||
|
||||
## Hand: bis zu fünf große Karten unten, mittig.
|
||||
static func hand_rect(count: int, i: int) -> Rect2:
|
||||
var total := count * HAND_W + maxi(0, count - 1) * HAND_GAP
|
||||
@@ -51,7 +40,7 @@ static func hand_rect(count: int, i: int) -> Rect2:
|
||||
return Rect2(x + i * (HAND_W + HAND_GAP), HAND_Y, HAND_W, HAND_H)
|
||||
|
||||
|
||||
## Schwebende Vorschau neben der gezeigten Karte: über Hand- und Ladenkarten, sonst rechts
|
||||
## Schwebende Vorschau neben der gezeigten Karte: über Handkarten, sonst rechts
|
||||
## davon (links, wenn rechts kein Platz ist); nie aus dem Bild.
|
||||
static func preview_rect(anchor: Rect2) -> Rect2:
|
||||
if anchor.position.y >= HAND_Y - 10:
|
||||
@@ -64,7 +53,7 @@ static func preview_rect(anchor: Rect2) -> Rect2:
|
||||
return Rect2(Vector2(x, y), PREVIEW_SIZE)
|
||||
|
||||
|
||||
## Zusatzzeile (Resonanz, Feld, Feldkombo) neben der Vorschau, rechts davon oder links.
|
||||
## Zusatzzeile (Elementbonus, Eigenschaften) neben der Vorschau, rechts davon oder links.
|
||||
static func info_rect(preview: Rect2) -> Rect2:
|
||||
var w := 230.0
|
||||
var x := preview.end.x + 8
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
class_name BattleScreen
|
||||
extends Control
|
||||
## Kampf: Zustand, Eingabe und Abgleich der Ansicht. Layout in BattleLayout, Animationen in BattleFx.
|
||||
## Rechtsklick (oder Y) auf eine Handkarte oder ein eigenes Wesen öffnet das Kartenmenü:
|
||||
## verdeckt legen, aufdecken, Eigenschaften kaufen.
|
||||
## 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 …“.
|
||||
## Modi: Einzelspieler, Kampagne, Netzwerk (Host, Mitspieler oder Zuschauer).
|
||||
|
||||
var app
|
||||
@@ -14,8 +16,16 @@ var game: Game
|
||||
var perspective: Game.Player # der Mensch, dessen Hand unten liegt
|
||||
var fx: BattleFx
|
||||
var selected = null # Game.Creature
|
||||
var fusing := false # Verschmelzen gewählt: zwei eigene Wesen anklicken
|
||||
var fuse_first = null # Game.Creature, erstes Wesen der Verschmelzung
|
||||
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 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
|
||||
@@ -40,7 +50,6 @@ var undo_t := 0.0
|
||||
# Ansichten (auch für Tests)
|
||||
var tiles := {}
|
||||
var hand_views: Array = []
|
||||
var pick_views: Array = [] # Karten der offenen Wahl
|
||||
var own_views: Array = []
|
||||
var enemy_views: Array = []
|
||||
var hero_panels := {} # Player -> Panel
|
||||
@@ -50,9 +59,6 @@ var flash_until := {} # Spielerindex -> Zeitpunkt (ms)
|
||||
var hero_layer: Control
|
||||
var tile_layer: Control
|
||||
var hand_layer: Control
|
||||
var pick_layer: Control # Überlagerung der offenen Wahl
|
||||
var pick_box: Control # ihr Inhalt (wird je Wahl neu aufgebaut)
|
||||
var pick_key := "" # welche Wahl gerade aufgebaut ist
|
||||
var deck: Deck
|
||||
var log_overlay: Control
|
||||
var b_log: Button
|
||||
@@ -67,7 +73,6 @@ var income_label: Label
|
||||
var status_label: Label
|
||||
var msg_label: Label
|
||||
var b_end: Button
|
||||
var b_fuse: Button
|
||||
var b_giveup: Button
|
||||
var element_view: ElementView
|
||||
var info_label: Label # Vorschau: Elementbonus, Merkmale
|
||||
@@ -197,9 +202,6 @@ func _build_static() -> void:
|
||||
energy_label = Style.label(self, "", 26, Style.BLUE, Rect2(sx + 12, sy + 78, 100, 34), "left", true)
|
||||
income_label = Style.label(self, "", 15, Style.MUTED, Rect2(sx + 100, sy + 88, 100, 22), "left")
|
||||
status_label = Style.label(self, "", 13, Style.MUTED, Rect2(sx + 12, sy + 124, 190, 22), "left")
|
||||
b_fuse = Style.button(self, "Verschmelzen", L.B_FUSE, toggle_fuse, Color("3c2858"))
|
||||
b_fuse.add_theme_font_size_override("font_size", 15)
|
||||
b_fuse.visible = false
|
||||
b_undo = Style.button(self, "Rückgängig", L.B_UNDO, undo)
|
||||
b_undo.add_theme_font_size_override("font_size", 15)
|
||||
b_undo.visible = false
|
||||
@@ -222,11 +224,6 @@ func _build_static() -> void:
|
||||
info_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
info_label.visible = false
|
||||
fx_layer = _layer()
|
||||
# offene Wahl: drei sehen, eine nehmen (zu Beginn fünf sehen, drei nehmen)
|
||||
pick_layer = Style.overlay(self)
|
||||
pick_box = Control.new()
|
||||
pick_box.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
pick_layer.add_child(pick_box)
|
||||
# Kartenmenü: durchsichtiger Fänger, der das Menü beim Klick daneben schließt
|
||||
menu_layer = Control.new()
|
||||
menu_layer.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
@@ -372,6 +369,20 @@ func _input(event: InputEvent) -> void:
|
||||
cancel_all()
|
||||
get_viewport().set_input_as_handled() # sonst schließt derselbe Klick das Menü gleich wieder
|
||||
return
|
||||
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_MIDDLE:
|
||||
var under := get_viewport().gui_get_hovered_control()
|
||||
if under is CardView and under in hand_views and my_turn() and not any_overlay():
|
||||
cancel()
|
||||
play(under.card, true)
|
||||
after_action()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if event is InputEventMouseMotion:
|
||||
_drag_motion(event.position)
|
||||
return
|
||||
if event is InputEventMouseButton and not event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
_release()
|
||||
return
|
||||
if event is InputEventJoypadButton and event.pressed:
|
||||
pad.button(event.button_index)
|
||||
return
|
||||
@@ -385,7 +396,7 @@ func _input(event: InputEvent) -> void:
|
||||
close_menu()
|
||||
elif log_overlay.visible:
|
||||
log_overlay.visible = false
|
||||
elif selected != null or fusing:
|
||||
elif selected != null or fuse_first != null:
|
||||
cancel_all()
|
||||
elif not any_overlay():
|
||||
confirm.visible = true
|
||||
@@ -401,11 +412,9 @@ func _input(event: InputEvent) -> void:
|
||||
if overlay.visible:
|
||||
leave()
|
||||
_:
|
||||
if event.keycode >= KEY_1 and event.keycode <= KEY_9 and not spectating() and not any_overlay():
|
||||
if event.keycode >= KEY_1 and event.keycode <= KEY_9 and my_turn() and not any_overlay():
|
||||
var idx: int = event.keycode - KEY_1
|
||||
if not perspective.offer.is_empty():
|
||||
do_pick(idx)
|
||||
elif my_turn() and idx < perspective.hand.size():
|
||||
if idx < perspective.hand.size():
|
||||
on_click("hand", perspective.hand[idx])
|
||||
elif event.keycode == KEY_Z and my_turn() and not any_overlay():
|
||||
on_click("deck", null)
|
||||
@@ -429,6 +438,15 @@ 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 is_online() and net.is_host:
|
||||
net.tick()
|
||||
if game.winner != "":
|
||||
@@ -503,8 +521,109 @@ func say(m: String) -> void:
|
||||
|
||||
func cancel() -> void:
|
||||
selected = null
|
||||
fusing = false
|
||||
fuse_first = null
|
||||
press_target = null
|
||||
_end_drag()
|
||||
|
||||
|
||||
# --- Ziehen mit der Maus: Klick beim Loslassen, Verschmelzen nach einer Sekunde über dem Ziel ---
|
||||
|
||||
func _press(obj, view: Control, pos: Vector2) -> void:
|
||||
_end_drag()
|
||||
press_target = obj
|
||||
press_view = view
|
||||
press_pos = pos
|
||||
|
||||
|
||||
func _drag_motion(pos: Vector2) -> void:
|
||||
if press_target == null:
|
||||
return
|
||||
if drag_ghost == null:
|
||||
if pos.distance_to(press_pos) < 8.0 or not my_turn() or any_overlay():
|
||||
return
|
||||
_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)
|
||||
drag_target = t
|
||||
drag_target_view = _view_of(t)
|
||||
hold_pos = pos
|
||||
|
||||
|
||||
## Loslassen ohne Ziehen ist der Klick.
|
||||
func _release() -> void:
|
||||
var obj = press_target
|
||||
var dragged := drag_ghost != null
|
||||
press_target = null
|
||||
_end_drag()
|
||||
if obj == null:
|
||||
return
|
||||
if dragged:
|
||||
refresh()
|
||||
elif obj is Cards.Card:
|
||||
on_click("hand", obj)
|
||||
else:
|
||||
on_tile_click(obj)
|
||||
|
||||
|
||||
func _start_drag(pos: Vector2) -> void:
|
||||
show_preview(null, false)
|
||||
if press_target is Cards.Card:
|
||||
var size := Vector2(BattleLayout.HAND_W, BattleLayout.HAND_H) * 0.8
|
||||
drag_ghost = CardView.new(press_target, size, false, press_target.rarity_in(perspective.looks))
|
||||
else:
|
||||
drag_ghost = CreatureView.new(press_target, BattleLayout.own_slot(0).size)
|
||||
drag_ghost.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
drag_ghost.modulate.a = 0.85
|
||||
drag_ghost.position = pos - drag_ghost.size / 2
|
||||
fx_layer.add_child(drag_ghost)
|
||||
if press_view != null:
|
||||
press_view.modulate.a = 0.4
|
||||
|
||||
|
||||
func _end_drag() -> void:
|
||||
if drag_ghost != null:
|
||||
drag_ghost.queue_free()
|
||||
drag_ghost = null
|
||||
if is_instance_valid(press_view):
|
||||
press_view.modulate.a = 1.0
|
||||
_set_hold(0.0)
|
||||
drag_target = null
|
||||
drag_target_view = null
|
||||
|
||||
|
||||
func _set_hold(v: float) -> void:
|
||||
hold_t = v
|
||||
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.queue_redraw()
|
||||
|
||||
|
||||
## Karte oder 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):
|
||||
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]:
|
||||
return obj
|
||||
return null
|
||||
|
||||
|
||||
func _view_of(obj) -> Control:
|
||||
if obj is Cards.Card:
|
||||
for v in hand_views:
|
||||
if v.card == obj:
|
||||
return v
|
||||
elif obj is Game.Creature and tiles.has(obj.uid):
|
||||
return tiles[obj.uid]
|
||||
return null
|
||||
|
||||
|
||||
## Mögliche Partner für „Verschmelzen mit …“: andere Handkarten bzw. andere eigene Wesen.
|
||||
func fusion_partners(target) -> Array:
|
||||
var pool: Array = perspective.hand if target is Cards.Card else perspective.board
|
||||
return pool.filter(func(o): return o != target and game.can_fuse(perspective, target, o)[0])
|
||||
|
||||
|
||||
func play(card: Cards.Card, face_down: bool = false) -> void:
|
||||
@@ -556,10 +675,12 @@ func do_upgrade(target, keyword: String) -> void:
|
||||
after_action()
|
||||
|
||||
|
||||
## Kartenmenü neben einer Handkarte oder einem eigenen Wesen.
|
||||
func open_menu(target, anchor: Rect2) -> void:
|
||||
## Kartenmenü neben einer Handkarte oder einem eigenen Wesen. full: auch verschmelzen anbieten
|
||||
## (Gamepad, das nicht ziehen kann).
|
||||
func open_menu(target, anchor: Rect2, full: bool = false) -> void:
|
||||
if spectating() or not my_turn():
|
||||
return
|
||||
cancel()
|
||||
Style.clear(menu_layer)
|
||||
menu_target = target
|
||||
var entries := [] # [Text, Callable, aktiv]
|
||||
@@ -570,8 +691,14 @@ func open_menu(target, anchor: Rect2) -> void:
|
||||
entries.append([tr("Verdeckt legen ◆ %d") % card.cost, func():
|
||||
play(card, true)
|
||||
after_action(), ok[0]])
|
||||
elif target.hidden:
|
||||
elif not is_card and target.hidden:
|
||||
entries.append([tr("Aufdecken"), func(): do_reveal(target), game.can_reveal(perspective, target)[0]])
|
||||
if full:
|
||||
var partners := fusion_partners(target)
|
||||
entries.append([tr("Verschmelzen mit … ◆ %d") % card.fusion_cost(), func():
|
||||
fuse_first = target
|
||||
say(tr("Ziel wählen"))
|
||||
refresh(), not partners.is_empty()])
|
||||
for kw in Cards.KEYWORDS:
|
||||
var k: String = kw
|
||||
var ok := game.can_upgrade(perspective, target, k)
|
||||
@@ -607,16 +734,7 @@ func close_menu() -> void:
|
||||
menu_target = null
|
||||
|
||||
|
||||
## Verschmelzen an- und abwählen (zwei eigene Wesen, siehe Game.fuse).
|
||||
func toggle_fuse() -> void:
|
||||
var on := not fusing
|
||||
cancel()
|
||||
fusing = on
|
||||
say(tr("Zwei eigene Wesen wählen") if on else tr("Abgebrochen"))
|
||||
after_action()
|
||||
|
||||
|
||||
## Ziehen: kostet Mana, öffnet die Wahl aus drei Karten.
|
||||
## Ziehen: kostet Mana, eine Karte kommt auf die Hand.
|
||||
func do_draw() -> void:
|
||||
clear_undo()
|
||||
if is_client():
|
||||
@@ -629,31 +747,15 @@ func do_draw() -> void:
|
||||
fx.sfx("buy")
|
||||
|
||||
|
||||
## Nimmt eine Karte der offenen Wahl auf die Hand (jederzeit, auch außerhalb des Zugs).
|
||||
func do_pick(index: int) -> void:
|
||||
if spectating() or index < 0 or index >= pick_views.size():
|
||||
return
|
||||
clear_undo()
|
||||
var from := Rect2(pick_views[index].position, pick_views[index].size)
|
||||
var card: Cards.Card = pick_views[index].card
|
||||
if is_client():
|
||||
net.send_action({"type": "pick", "index": index})
|
||||
return
|
||||
var err := game.pick(perspective, index)
|
||||
if err != "":
|
||||
say(tr(err))
|
||||
return
|
||||
fx.sfx("play")
|
||||
fx.fly_card(card, from, BattleLayout.hand_rect(perspective.hand.size(), perspective.hand.size() - 1),
|
||||
Cards.RARITIES[int(perspective.looks.get(card.id, 0))])
|
||||
after_action()
|
||||
|
||||
|
||||
## Verschmilzt zwei Handkarten oder zwei eigene Wesen (siehe Game.fuse).
|
||||
func do_fuse(a, b) -> void:
|
||||
cancel()
|
||||
clear_undo()
|
||||
if is_client():
|
||||
net.send_action({"type": "fuse", "uid": a.uid, "other": b.uid})
|
||||
if a is Cards.Card:
|
||||
net.send_action({"type": "fuse", "hand": perspective.hand.find(a), "other_hand": perspective.hand.find(b)})
|
||||
else:
|
||||
net.send_action({"type": "fuse", "uid": a.uid, "other": b.uid})
|
||||
return
|
||||
var err := game.fuse(perspective, a, b)
|
||||
if err != "":
|
||||
@@ -703,17 +805,13 @@ func on_click(kind: String, obj) -> void:
|
||||
var g := game
|
||||
if not my_turn() or any_overlay():
|
||||
return
|
||||
if fusing:
|
||||
if kind != "own":
|
||||
cancel()
|
||||
say(tr("Abgebrochen"))
|
||||
elif fuse_first == null:
|
||||
fuse_first = obj
|
||||
say(tr("Zweites Wesen wählen"))
|
||||
elif obj == fuse_first:
|
||||
fuse_first = null
|
||||
if fuse_first != null:
|
||||
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)
|
||||
else:
|
||||
do_fuse(fuse_first, obj)
|
||||
say(tr("Abgebrochen"))
|
||||
after_action()
|
||||
return
|
||||
if selected != null:
|
||||
@@ -745,6 +843,8 @@ func on_click(kind: String, obj) -> void:
|
||||
if obj.can_attack():
|
||||
selected = obj
|
||||
say(tr("Wähle ein Angriffsziel"))
|
||||
elif not obj.ready:
|
||||
say(tr("%s greift erst im nächsten Zug an") % obj.card.title())
|
||||
else:
|
||||
say(tr("%s kann nicht angreifen") % obj.card.title())
|
||||
after_action()
|
||||
@@ -783,7 +883,7 @@ func show_preview(card, entered: bool, owner_looks: Dictionary = {}, info: Strin
|
||||
info_label.text = info if entered else ""
|
||||
if entered and card != null:
|
||||
var r := BattleLayout.preview_rect(anchor)
|
||||
var v := CardView.new(card, r.size, false, Cards.RARITIES[int(owner_looks.get(card.id, 0))])
|
||||
var v := CardView.new(card, r.size, false, card.rarity_in(owner_looks))
|
||||
v.position = r.position
|
||||
v.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
preview_holder.add_child(v)
|
||||
@@ -792,69 +892,32 @@ func show_preview(card, entered: bool, owner_looks: Dictionary = {}, info: Strin
|
||||
info_label.size = ir.size
|
||||
|
||||
|
||||
## Eigene Hand- und Wahlkarte: zeigt den Elementbonus, den sie offen gelegt bekäme.
|
||||
## Eigene Handkarte: zeigt den Elementbonus, den sie offen gelegt bekäme.
|
||||
func show_hand_preview(card: Cards.Card, entered: bool, anchor: Rect2 = Rect2()) -> void:
|
||||
if drag_ghost != null:
|
||||
return
|
||||
var a := game.forecast(perspective, card)
|
||||
var info := ""
|
||||
if a > 0:
|
||||
info = tr("Elementbonus +%d/+%d") % [a, a]
|
||||
elif a < 0:
|
||||
info = tr("Elementbonus %d/%d") % [a, a]
|
||||
show_preview(card, entered, perspective.looks, info, anchor)
|
||||
|
||||
|
||||
## Offene Wahl als Überlagerung: nur neu aufbauen, wenn sich das Angebot geändert hat.
|
||||
func build_pick() -> void:
|
||||
var h := perspective
|
||||
var key := ""
|
||||
if not spectating() and not h.offer.is_empty():
|
||||
key = "%d:%s" % [h.picks_left, ",".join(h.offer.map(func(c): return c.id))]
|
||||
if key == pick_key:
|
||||
return
|
||||
pick_key = key
|
||||
Style.clear(pick_box)
|
||||
pick_views = []
|
||||
if key == "":
|
||||
pick_layer.visible = false
|
||||
return
|
||||
var box := BattleLayout.PICK_PANEL
|
||||
Style.panel(pick_box, box, Style.PANEL, 12, Style.GOLD, 2)
|
||||
Style.label(pick_box, tr("Wähle %d von %d") % [h.picks_left, h.offer.size()], 26, Style.GOLD,
|
||||
Rect2(box.position.x, box.position.y + 24, box.size.x, 40), "center", true)
|
||||
for i in h.offer.size():
|
||||
var card: Cards.Card = h.offer[i]
|
||||
var r := BattleLayout.pick_rect(h.offer.size(), i)
|
||||
var v := CardView.new(card, r.size, false, Cards.RARITIES[int(h.looks.get(card.id, 0))])
|
||||
v.position = r.position
|
||||
v.lift = true
|
||||
apply_forecast(v)
|
||||
var idx := i
|
||||
v.clicked.connect(func(_view): do_pick(idx))
|
||||
pick_box.add_child(v)
|
||||
pick_views.append(v)
|
||||
pick_layer.visible = true
|
||||
if Style.animations:
|
||||
for i in pick_views.size():
|
||||
fx.deal(pick_views[i], i)
|
||||
|
||||
|
||||
## Hand- und Wahlkarten tragen den Elementbonus als Vorschau.
|
||||
func apply_forecast(v: CardView) -> void:
|
||||
v.bonus = game.forecast(perspective, v.card)
|
||||
|
||||
|
||||
## Was den Angriff eines Wesens gerade hebt oder senkt.
|
||||
## Was ein Wesen gerade hebt, was es kann und ob es schon angreift.
|
||||
func creature_info(cr: Game.Creature) -> String:
|
||||
var parts: Array[String] = []
|
||||
if cr.aura > 0:
|
||||
parts.append(tr("Elementbonus +%d/+%d") % [cr.aura, cr.aura])
|
||||
elif cr.aura < 0:
|
||||
parts.append(tr("Elementbonus %d/%d") % [cr.aura, cr.aura])
|
||||
var perk := cr.power() - cr.attack - cr.aura
|
||||
if perk > 0:
|
||||
parts.append(tr("Merkmal +%d") % perk)
|
||||
for k in cr.keywords():
|
||||
if not (k in cr.card.keywords):
|
||||
parts.append(tr("%s durch das Element") % tr(k))
|
||||
if cr.hidden:
|
||||
parts.append(tr("verdeckt"))
|
||||
elif not cr.ready and not cr.has("Flink") and cr.owner() == perspective:
|
||||
parts.append(tr("greift erst im nächsten Zug an"))
|
||||
return " · ".join(parts)
|
||||
|
||||
|
||||
@@ -1020,10 +1083,10 @@ func refresh() -> void:
|
||||
else:
|
||||
show_preview(card, entered, looks, creature_info(tile.creature) if entered else "", Rect2(tile.slot, tile.size)))
|
||||
var uid := cr.uid
|
||||
tile.clicked.connect(func(_v):
|
||||
tile.pressed.connect(func(_v, pos):
|
||||
var c := game.find_creature(uid)
|
||||
if c != null:
|
||||
on_tile_click(c))
|
||||
_press(c, tile, pos))
|
||||
if arrivals.has(cr.uid):
|
||||
fx.fly_in(tile, arrivals[cr.uid])
|
||||
arrivals.erase(cr.uid)
|
||||
@@ -1037,7 +1100,7 @@ func refresh() -> void:
|
||||
if cr == selected or cr == fuse_first:
|
||||
b = Style.YELLOW
|
||||
w = 4
|
||||
elif fusing and p == h and mine:
|
||||
elif fuse_first != null and p == h and mine and g.can_fuse(h, fuse_first, cr)[0]:
|
||||
b = Style.PURPLE
|
||||
w = 3
|
||||
elif selected != null and g.can_attack(selected, cr)[0]:
|
||||
@@ -1060,7 +1123,6 @@ func refresh() -> void:
|
||||
var t: CreatureView = tiles[uid]
|
||||
tiles.erase(uid)
|
||||
fx.fade_out(t)
|
||||
build_pick()
|
||||
deck.visible = not spectating()
|
||||
deck.enabled = mine and g.can_draw(h)[0]
|
||||
deck.count = g.deck.size()
|
||||
@@ -1075,15 +1137,20 @@ func refresh() -> void:
|
||||
var card: Cards.Card = h.hand[i]
|
||||
var playable: bool = mine and g.can_play(h, card)[0]
|
||||
var r := BattleLayout.hand_rect(h.hand.size(), i)
|
||||
var v := CardView.new(card, r.size, false, Cards.RARITIES[int(h.looks.get(card.id, 0))])
|
||||
var v := CardView.new(card, r.size, false, card.rarity_in(h.looks))
|
||||
v.position = r.position
|
||||
v.lift = mine
|
||||
apply_forecast(v)
|
||||
if playable:
|
||||
v.bonus = g.forecast(h, card)
|
||||
if fuse_first != null and mine and g.can_fuse(h, fuse_first, card)[0]:
|
||||
v.border = Style.PURPLE
|
||||
elif card == fuse_first:
|
||||
v.border = Style.YELLOW
|
||||
elif playable:
|
||||
v.border = Style.GREEN
|
||||
v.dim = not playable
|
||||
v.dim = not playable and card != fuse_first
|
||||
v.drag_enabled = true
|
||||
v.hover.connect(func(c, e): show_hand_preview(c, e, Rect2(v.position, v.size)))
|
||||
v.clicked.connect(func(_view): on_click("hand", card))
|
||||
v.pressed.connect(func(_view, pos): _press(card, v, pos))
|
||||
hand_layer.add_child(v)
|
||||
hand_views.append(v)
|
||||
|
||||
@@ -1103,18 +1170,16 @@ func refresh() -> void:
|
||||
b_skip.visible = ai_running
|
||||
b_end.visible = not ai_running and not spectating()
|
||||
b_undo.visible = undo_state != null and mine
|
||||
b_fuse.visible = mine and not spectating() and h.board.size() >= 2
|
||||
b_fuse.text = tr("Abbrechen") if fusing else tr("Verschmelzen")
|
||||
element_view.update_from(g)
|
||||
element_view.update_from(g, h)
|
||||
var lines: Array[String] = []
|
||||
var start := maxi(0, g.log.size() - 60)
|
||||
for i in range(start, g.log.size()):
|
||||
var line := g.log[i]
|
||||
if line.begins_with("---"):
|
||||
lines.append("[color=#6eaaf0]%s[/color]" % line)
|
||||
elif " verschmelzen zu " in line or " wird aufgedeckt." in line:
|
||||
elif " verschmelzen " in line or " wird aufgedeckt." in line:
|
||||
lines.append("[color=#8c78dc]%s[/color]" % line)
|
||||
elif " bekommt " in line:
|
||||
elif " bekommt " in line or " regeneriert sich." in line:
|
||||
lines.append("[color=#5ad282]%s[/color]" % line)
|
||||
else:
|
||||
lines.append(line)
|
||||
|
||||
@@ -87,7 +87,7 @@ func buy() -> void:
|
||||
func reveal(i: int) -> void:
|
||||
revealed[i] = true
|
||||
fresh = {i: 0.0}
|
||||
app.sfx.play("combo" if cards[i].rarity != "gewöhnlich" else "click")
|
||||
app.sfx.play("combo" if cards[i].rarity != "Common" else "click")
|
||||
refresh()
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ func flip(v: CardView, delay: float, rarity: String) -> void:
|
||||
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 == "gewöhnlich":
|
||||
if rarity == "Common":
|
||||
return
|
||||
var burst := Style.panel(fx_layer, Rect2(v.position, v.size).grow(6), Color(Cards.RARITY_COLORS[rarity], 0.6), 12)
|
||||
burst.pivot_offset = burst.size / 2
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
class_name CardView
|
||||
extends Control
|
||||
## Zeichnet eine Karte (Laden, Hand, Sammlung, Booster, Vorschau).
|
||||
## Aufbau wie bei Hearthstone: Kostenkristall oben links, Bild, Namensband, Schlüsselwörter als
|
||||
## Symbole, Effekttext, Angriff und Leben als Kreise unten. Das Element steht im Hintergrund,
|
||||
## die Seltenheit in Rahmen, Glanz und Edelstein. Sehr flache Karten (Hand) zeigen nur das Nötigste.
|
||||
## 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 := "gewöhnlich" # nur Optik
|
||||
var rarity := "Common" # nur Optik
|
||||
var dim := false
|
||||
var bonus := 0 # Vorschau im Kampf: Elementbonus, den die Karte offen gelegt bekäme (+1 oder -1)
|
||||
var lift := false # hebt sich beim Zeigen an (Hand und Laden)
|
||||
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 = "gewöhnlich") -> void:
|
||||
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
|
||||
@@ -55,7 +59,10 @@ func _process(_delta: float) -> void:
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
accept_event()
|
||||
clicked.emit(self)
|
||||
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)
|
||||
@@ -65,7 +72,7 @@ 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 != "gewöhnlich":
|
||||
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:
|
||||
@@ -76,9 +83,14 @@ func _draw() -> void:
|
||||
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: Laden, Sammlung, Booster, Vorschau.
|
||||
## 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
|
||||
@@ -90,14 +102,18 @@ func _draw_full(rect: Rect2, frame: Color, seed: int) -> void:
|
||||
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
|
||||
Style.glyph(self, card, Vector2(size.x / 2, band_top * 0.56), minf(size.x * 0.62, band_top * 0.95), 0.4)
|
||||
Style.rarity_sheen(self, rect, rarity, seed)
|
||||
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.TEXT, Vector2(size.x / 2, band.get_center().y), "center", true)
|
||||
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():
|
||||
@@ -146,9 +162,8 @@ func _draw_full(rect: Rect2, frame: Color, seed: int) -> void:
|
||||
# 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.CRIMSON, f_stat)
|
||||
if bonus != 0:
|
||||
Style.text_outlined(self, ("+%d/+%d" % [bonus, bonus]) if bonus > 0 else ("%d/%d" % [bonus, bonus]), f_text,
|
||||
Style.GREEN if bonus > 0 else Style.RED, Vector2(r_stat * 2 + 4, size.y - r_stat + 2), "midleft", 3)
|
||||
if bonus > 0:
|
||||
Style.text_outlined(self, "+%d/+%d" % [bonus, 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)
|
||||
@@ -160,7 +175,7 @@ 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 != "gewöhnlich" else 0)
|
||||
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)
|
||||
@@ -174,6 +189,6 @@ func _draw_compact(rect: Rect2, frame: Color, seed: int) -> void:
|
||||
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.CRIMSON, 12)
|
||||
if bonus != 0:
|
||||
Style.text_outlined(self, ("+%d" % bonus) if bonus > 0 else str(bonus), 10, Style.GREEN if bonus > 0 else Style.RED, Vector2(4, size.y - 29), "midleft", 3)
|
||||
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)
|
||||
|
||||
@@ -2,7 +2,7 @@ class_name CreatureView
|
||||
extends Control
|
||||
## Zeichnet ein Wesen auf dem Feld. Bleibt über Zustandsänderungen bestehen (für Animationen).
|
||||
|
||||
signal clicked(view: CreatureView)
|
||||
signal pressed(view: CreatureView, pos: Vector2) # linke Maustaste gedrückt; der Kampf macht daraus Klick oder Ziehen
|
||||
signal hover(card: Cards.Card, entered: bool)
|
||||
|
||||
var creature: Game.Creature
|
||||
@@ -13,6 +13,7 @@ var slot: Vector2 # Zielposition im Raster
|
||||
var busy := false # wird gerade animiert (kein Neupositionieren)
|
||||
var matchup := 0 # Elementvorteil des gewählten Angreifers gegen dieses Wesen (+1/-1)
|
||||
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)
|
||||
|
||||
|
||||
func _init(cr: Game.Creature, sz: Vector2, b = null, w: int = 2) -> void:
|
||||
@@ -35,27 +36,31 @@ func _process(_delta: float) -> void:
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
accept_event()
|
||||
clicked.emit(self)
|
||||
pressed.emit(self, event.global_position)
|
||||
|
||||
|
||||
func rarity() -> String:
|
||||
var owner = creature.owner()
|
||||
return Cards.RARITIES[int(owner.looks.get(creature.card.id, 0)) if owner != null else 0]
|
||||
return creature.card.rarity_in(owner.looks if owner != null else {})
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var rect := Rect2(Vector2.ZERO, size)
|
||||
if secret:
|
||||
_draw_back(rect)
|
||||
_draw_hold()
|
||||
return
|
||||
var small := size.x < 100
|
||||
var rar := rarity()
|
||||
var seed := hash(creature.card.id) + creature.uid
|
||||
if rar != "gewöhnlich":
|
||||
if rar != "Common":
|
||||
Style.glow(self, rect, Style.rarity_color(rar, seed), 6, 6)
|
||||
Style.fill_elements(self, rect, creature.card.elements, 6)
|
||||
Style.glyph(self, creature.card, Vector2(size.x / 2, size.y * 0.5), size.x * 0.75, 0.3)
|
||||
Style.rarity_sheen(self, rect, rar, seed)
|
||||
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
|
||||
@@ -64,19 +69,19 @@ func _draw() -> void:
|
||||
var name_w: float = size.x - (30 if matchup != 0 else 6) # Platz für den Vorteilskreis oben rechts
|
||||
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.TEXT, Vector2(3 + name_w / 2, 6), "midtop", true)
|
||||
# Schlüsselwörter als Symbole (Schild nur, solange er steht)
|
||||
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)
|
||||
# Eigenschaften als Symbole: gekaufte weiß, die vom Elementpaar in der Elementfarbe
|
||||
var icon := 7.0 if small else 9.0
|
||||
var kws: Array[String] = []
|
||||
for k in Cards.KEYWORDS:
|
||||
if k in creature.card.keywords and (k != "Schild" or creature.shield):
|
||||
kws.append(k)
|
||||
if creature.shield and not ("Schild" in kws):
|
||||
kws.append("Schild")
|
||||
var kws := creature.keywords()
|
||||
var x := size.x / 2 - (kws.size() * (icon * 2 + 6) - 6) / 2.0
|
||||
var y := Style.fs(fs) + 14 + icon
|
||||
for k in kws:
|
||||
Style.keyword_icon(self, k, Vector2(x + icon, y), icon)
|
||||
var col := Color("f2f2f6")
|
||||
if not (k in creature.card.keywords):
|
||||
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, 10 if small else 12, 12.0 if small else 14.0)
|
||||
Style.rarity_gem(self, rar, Vector2(size.x / 2, size.y - 9), 5, seed)
|
||||
@@ -84,6 +89,17 @@ func _draw() -> void:
|
||||
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)
|
||||
Style.rarity_sparkle(self, rect, rar, seed)
|
||||
_draw_hold()
|
||||
|
||||
|
||||
## 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)
|
||||
|
||||
|
||||
## Verdecktes fremdes Wesen: ein Kartenrücken.
|
||||
@@ -98,7 +114,7 @@ func _draw_back(rect: Rect2) -> void:
|
||||
pass # unbekanntes Element: kein Vorteil anzeigen
|
||||
|
||||
|
||||
## Angriff (grün oder rot, wenn die Elementregel ihn hebt oder senkt), Leben, Elementvorteil.
|
||||
## Angriff (grün, wenn der Elementbonus ihn hebt), Leben, Elementvorteil des Angreifers.
|
||||
func _draw_stats(f: int, f_small: int, r: float) -> void:
|
||||
var extra := creature.power() - creature.attack
|
||||
var atk_col := Style.GREEN if extra > 0 else (Style.RED if extra < 0 else Style.AMBER)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
class_name ElementView
|
||||
extends Control
|
||||
## Die Vier-Elemente-Regel auf einen Blick: offene Wesen je Element auf dem Tisch. Ab vier
|
||||
## bekommt das Element +1/+1 und das Element, das es schlägt, -1/-1.
|
||||
## Elementboni auf einen Blick: offene Wesen je Element auf dem Tisch (ab drei +1/+1) und der
|
||||
## Sondereffekt der eigenen Seite (ab zwei eigenen: Erde Spott, Luft Flink, Wasser Regeneration,
|
||||
## Feuer Doppelschlag), als Symbol in der Elementfarbe.
|
||||
|
||||
var counts := {}
|
||||
var counts := {} # Tisch
|
||||
var own := {} # eigene Seite
|
||||
|
||||
|
||||
func _init(rect: Rect2) -> void:
|
||||
@@ -12,10 +14,13 @@ func _init(rect: Rect2) -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
for e in Cards.ELEMENTS:
|
||||
counts[e] = 0
|
||||
own[e] = 0
|
||||
|
||||
|
||||
func update_from(g: Game) -> void:
|
||||
func update_from(g: Game, me: Game.Player) -> void:
|
||||
counts = g.element_counts()
|
||||
for e in Cards.ELEMENTS:
|
||||
own[e] = me.element_count(e) if me != null else 0
|
||||
queue_redraw()
|
||||
|
||||
|
||||
@@ -23,9 +28,8 @@ func strong(e: String) -> bool:
|
||||
return int(counts.get(e, 0)) >= Game.ELEMENT_STACK
|
||||
|
||||
|
||||
func weak(e: String) -> bool:
|
||||
var predator := Cards.beaten_by(e)
|
||||
return predator != "" and strong(predator)
|
||||
func paired(e: String) -> bool:
|
||||
return int(own.get(e, 0)) >= Game.ELEMENT_PAIR
|
||||
|
||||
|
||||
## Der Inhalt als Text (Tests, Protokoll).
|
||||
@@ -35,8 +39,8 @@ func text() -> String:
|
||||
var s := "%s %d" % [tr(e), int(counts.get(e, 0))]
|
||||
if strong(e):
|
||||
s += " (+1/+1)"
|
||||
if weak(e):
|
||||
s += " (-1/-1)"
|
||||
if paired(e):
|
||||
s += " (%s)" % tr(Cards.ELEMENT_KEYWORD[e])
|
||||
parts.append(s)
|
||||
return " · ".join(parts)
|
||||
|
||||
@@ -51,9 +55,10 @@ func _draw() -> void:
|
||||
var n := int(counts.get(e, 0))
|
||||
if strong(e):
|
||||
draw_style_box(Style.box(Color(col, 0.35), 6, col, 2), Rect2(x0 + 2, 0, w - 4, size.y))
|
||||
elif weak(e):
|
||||
draw_style_box(Style.box(Color(Style.RED, 0.12), 6, Style.RED, 1), Rect2(x0 + 2, 0, w - 4, size.y))
|
||||
Style.text(self, tr(e), 13, Style.TEXT if strong(e) else Style.MUTED, Vector2(x0 + 12, cy), "midleft", strong(e))
|
||||
elif paired(e):
|
||||
draw_style_box(Style.box(Color(col, 0.18), 6, Color(col, 0.6), 1), Rect2(x0 + 2, 0, w - 4, size.y))
|
||||
var active := strong(e) or paired(e)
|
||||
Style.text(self, tr(e), 13, Style.TEXT if active else Style.MUTED, Vector2(x0 + 12, cy), "midleft", active)
|
||||
var px := x0 + 12 + Style.FONT_BOLD.get_string_size(tr(e), HORIZONTAL_ALIGNMENT_LEFT, -1, Style.fs(13)).x + 12
|
||||
for k in Game.ELEMENT_STACK:
|
||||
var c := Vector2(px + k * 13, cy)
|
||||
@@ -62,12 +67,11 @@ func _draw() -> void:
|
||||
else:
|
||||
draw_circle(c, 4.5, Color(0, 0, 0, 0.35))
|
||||
draw_arc(c, 4.5, 0, TAU, 16, Color(col, 0.6), 1.0)
|
||||
px += Game.ELEMENT_STACK * 13 + 2
|
||||
if n > Game.ELEMENT_STACK:
|
||||
Style.text(self, "+%d" % (n - Game.ELEMENT_STACK), 11, Style.MUTED, Vector2(px + Game.ELEMENT_STACK * 13 + 2, cy), "midleft")
|
||||
var tag := ""
|
||||
Style.text(self, "+%d" % (n - Game.ELEMENT_STACK), 11, Style.MUTED, Vector2(px, cy), "midleft")
|
||||
px += 18
|
||||
if paired(e):
|
||||
Style.keyword_icon(self, Cards.ELEMENT_KEYWORD[e], Vector2(px + 10, cy), 7, Style.lighter(col, 0.45))
|
||||
if strong(e):
|
||||
tag = "+1/+1"
|
||||
elif weak(e):
|
||||
tag = "-1/-1"
|
||||
if tag != "":
|
||||
Style.text(self, tag, 13, Style.GREEN if strong(e) else Style.RED, Vector2(x0 + w - 12, cy), "midright", true)
|
||||
Style.text(self, "+1/+1", 13, Style.GREEN, Vector2(x0 + w - 12, cy), "midright", true)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class_name OpponentSelectScreen
|
||||
extends Control
|
||||
## Einzelspieler: Schwierigkeit wählen und ein Duell starten. Die Karten kommen im Kampf
|
||||
## per Ziehen (siehe DeckGen.offer_cards).
|
||||
## vom gemeinsamen Stapel (siehe Game.start).
|
||||
|
||||
var app
|
||||
var diff_buttons := {}
|
||||
|
||||
@@ -309,43 +309,79 @@ 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.
|
||||
|
||||
## Ab episch bewegt sich der Glanz; dann zeichnet die Karte jeden Frame neu.
|
||||
## Ab Super 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
|
||||
|
||||
|
||||
## Farbe einer Stufe; mythisch schillert über die Zeit durch alle Farben.
|
||||
## Farbe einer Stufe; Ultimate Rare schillert über die Zeit durch alle Farben.
|
||||
static func rarity_color(rar: String, seed: int = 0) -> Color:
|
||||
if rar == "mythisch" and animations:
|
||||
if rar == "Ultimate Rare" and animations:
|
||||
return Color.from_hsv(fmod(Time.get_ticks_msec() / 1000.0 * 0.12 + (seed % 97) / 97.0, 1.0), 0.55, 1.0)
|
||||
return Cards.RARITY_COLORS[rar]
|
||||
|
||||
|
||||
## Rahmenfarbe einer Stufe: gewöhnlich bleibt schlicht, alles darüber trägt seine Farbe.
|
||||
## Rahmenfarbe einer Stufe: Common bleibt schlicht, alles darüber trägt seine Farbe.
|
||||
static func rarity_frame(rar: String, seed: int = 0) -> Color:
|
||||
return Color("3a3e4c") if rar == "gewöhnlich" else rarity_color(rar, seed)
|
||||
return Color("3a3e4c") if rar == "Common" else rarity_color(rar, seed)
|
||||
|
||||
|
||||
## Lichtstreifen wie auf einer Folienkarte: selten steht er, ab episch wandert er über die Karte.
|
||||
static func rarity_sheen(ci: CanvasItem, rect: Rect2, rar: String, seed: int) -> void:
|
||||
## Farbe des Kartennamens: Rare silbern, Ultra Rare golden, Ultimate Rare schillernd, sonst weiß.
|
||||
static func rarity_name_color(rar: String, seed: int = 0) -> Color:
|
||||
match rar:
|
||||
"Rare", "Ultra Rare", "Ultimate Rare":
|
||||
return lighter(rarity_color(rar, seed), 0.25)
|
||||
return TEXT
|
||||
|
||||
|
||||
## Ultimate Rare zeigt das Bild als Relief (Licht- und Schattenkante).
|
||||
static func rarity_relief(rar: String) -> bool:
|
||||
return Cards.RARITIES.find(rar) >= 4
|
||||
|
||||
|
||||
## 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)]])
|
||||
return
|
||||
var phase := (seed % 100) / 100.0
|
||||
if lvl >= 2 and animations:
|
||||
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)
|
||||
|
||||
|
||||
## 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
|
||||
for layer in [[1.0, 0.07], [0.55, 0.09], [0.22, 0.13]]:
|
||||
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):
|
||||
if poly.size() >= 3 and _area(poly) > 2.0: # Splitter am Rand lassen sich nicht zeichnen
|
||||
ci.draw_colored_polygon(poly, Color(1, 1, 1, layer[1]))
|
||||
ci.draw_colored_polygon(poly, layer[1])
|
||||
|
||||
|
||||
static func _area(poly: PackedVector2Array) -> float:
|
||||
@@ -357,15 +393,13 @@ static func _area(poly: PackedVector2Array) -> float:
|
||||
return absf(a) / 2.0
|
||||
|
||||
|
||||
## Funkeln ab legendär: kleine Sterne, die aufblitzen und vergehen; ganz zum Schluss zeichnen.
|
||||
## 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:
|
||||
var lvl := Cards.RARITIES.find(rar)
|
||||
if lvl < 3:
|
||||
if Cards.RARITIES.find(rar) < 4:
|
||||
return
|
||||
var t := Time.get_ticks_msec() / 1000.0
|
||||
var n := 5 if lvl == 3 else 9
|
||||
for i in n:
|
||||
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
|
||||
@@ -388,9 +422,9 @@ 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 selten).
|
||||
## 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 == "gewöhnlich":
|
||||
if rar == "Common":
|
||||
return
|
||||
var col := rarity_color(rar, seed)
|
||||
var diamond := func(h: float, k: Color) -> void:
|
||||
@@ -400,23 +434,28 @@ static func rarity_gem(ci: CanvasItem, rar: String, c: Vector2, s: float, seed:
|
||||
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 Elementfarbe; ein verschmolzenes Wesen zeigt seine Elemente als Streifen.
|
||||
## Hintergrund in der Farbe des ersten Elements; weitere Elemente zeigt element_medallion.
|
||||
static func fill_elements(ci: CanvasItem, rect: Rect2, elements: Array, radius: int = 8) -> void:
|
||||
if elements.size() == 1:
|
||||
ci.draw_style_box(box(Cards.ELEMENT_COLORS[elements[0]], radius), rect)
|
||||
return
|
||||
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()
|
||||
var w := rect.size.x / n
|
||||
for i in n:
|
||||
var sb := StyleBoxFlat.new()
|
||||
sb.bg_color = Cards.ELEMENT_COLORS[elements[i]]
|
||||
if i == 0:
|
||||
sb.corner_radius_top_left = radius
|
||||
sb.corner_radius_bottom_left = radius
|
||||
if i == n - 1:
|
||||
sb.corner_radius_top_right = radius
|
||||
sb.corner_radius_bottom_right = radius
|
||||
ci.draw_style_box(sb, Rect2(rect.position + Vector2(floorf(i * w), 0), Vector2(ceilf(w), rect.size.y)))
|
||||
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)
|
||||
|
||||
|
||||
# --- Kartenteile ------------------------------------------------------
|
||||
@@ -444,20 +483,18 @@ static func stat_circle(ci: CanvasItem, c: Vector2, r: float, value: String, col
|
||||
text_outlined(ci, value, font, Color.WHITE, c, "center", 3)
|
||||
|
||||
|
||||
## Schlüsselwort als Symbol auf dunklem Kreis: Schild Schild, Doppelschlag gekreuzte Klingen.
|
||||
## Eigenschaft als Symbol auf dunklem Kreis: Spott Ausrufezeichen, Flink Doppelpfeil,
|
||||
## Regeneration Kreuz, Doppelschlag gekreuzte Klingen.
|
||||
static func keyword_icon(ci: CanvasItem, kw: String, c: Vector2, r: float, col: Color = Color("f2f2f6")) -> void:
|
||||
ci.draw_circle(c, r + 3, Color(0.05, 0.05, 0.08, 0.7))
|
||||
var w := maxf(1.5, r * 0.22)
|
||||
match kw:
|
||||
"Wächter":
|
||||
ci.draw_rect(Rect2(c + Vector2(-r * 0.75, -r * 0.15), Vector2(r * 1.5, r * 0.85)), col)
|
||||
for i in 3:
|
||||
ci.draw_rect(Rect2(c + Vector2(-r * 0.75 + i * r * 0.6, -r * 0.6), Vector2(r * 0.3, r * 0.5)), col)
|
||||
"Schild":
|
||||
var pts := PackedVector2Array([c + Vector2(-r * 0.7, -r * 0.65), c + Vector2(r * 0.7, -r * 0.65), c + Vector2(r * 0.7, r * 0.05),
|
||||
c + Vector2(0, r * 0.8), c + Vector2(-r * 0.7, r * 0.05)])
|
||||
ci.draw_colored_polygon(pts, col)
|
||||
ci.draw_line(c + Vector2(0, -r * 0.55), c + Vector2(0, r * 0.6), Color(0.05, 0.05, 0.08, 0.6), w)
|
||||
"Spott":
|
||||
ci.draw_line(c + Vector2(0, -r * 0.7), c + Vector2(0, r * 0.2), col, w * 1.6)
|
||||
ci.draw_circle(c + Vector2(0, r * 0.6), w * 0.95, col)
|
||||
"Regeneration":
|
||||
ci.draw_line(c + Vector2(0, -r * 0.7), c + Vector2(0, r * 0.7), col, w * 1.5)
|
||||
ci.draw_line(c + Vector2(-r * 0.7, 0), c + Vector2(r * 0.7, 0), col, w * 1.5)
|
||||
"Flink":
|
||||
for i in 2:
|
||||
var x := c.x - r * 0.55 + i * r * 0.55
|
||||
@@ -471,10 +508,24 @@ static func keyword_icon(ci: CanvasItem, kw: String, c: Vector2, r: float, col:
|
||||
|
||||
# --- Kartensymbole (prozedural, je Element) --------------------------
|
||||
|
||||
static func glyph(ci: CanvasItem, card, center: Vector2, size: float, alpha: float = 0.95) -> void:
|
||||
## relief: Schatten- und Lichtkante unter dem Symbol (Ultimate Rare).
|
||||
static func glyph(ci: CanvasItem, card, center: Vector2, size: float, alpha: float = 0.95, relief: bool = false) -> void:
|
||||
if relief:
|
||||
ci.draw_set_transform(Vector2(2, 2))
|
||||
_glyph_plain(ci, card, center, size, Color(0, 0, 0, alpha * 0.6))
|
||||
ci.draw_set_transform(Vector2(-1, -1))
|
||||
_glyph_plain(ci, card, center, size, Color(1, 1, 1, alpha * 0.5))
|
||||
ci.draw_set_transform(Vector2.ZERO)
|
||||
_glyph_plain(ci, card, center, size, Color(0, 0, 0, 0), alpha)
|
||||
|
||||
|
||||
## Symbol in einer festen Farbe (Relief) oder, bei durchsichtigem tint, in Elementfarben.
|
||||
static func _glyph_plain(ci: CanvasItem, card, center: Vector2, size: float, tint: Color, alpha: float = 0.95) -> void:
|
||||
var variant := absi(hash(card.id)) % 3
|
||||
var col := lighter(Cards.ELEMENT_COLORS[card.elements[0]], 0.38)
|
||||
col.a = alpha
|
||||
if tint.a > 0:
|
||||
col = tint
|
||||
var elems: Array = card.elements
|
||||
if elems.size() == 1:
|
||||
_element_glyph(ci, elems[0], center, size, variant, col)
|
||||
@@ -483,6 +534,8 @@ static func glyph(ci: CanvasItem, card, center: Vector2, size: float, alpha: flo
|
||||
_element_glyph(ci, elems[0], center + Vector2(-size * 0.28, 0), size * 0.62, variant, col)
|
||||
var col2 := lighter(Cards.ELEMENT_COLORS[elems[1]], 0.38)
|
||||
col2.a = alpha
|
||||
if tint.a > 0:
|
||||
col2 = tint
|
||||
_element_glyph(ci, elems[1], center + Vector2(size * 0.28, 0), size * 0.62, variant, col2)
|
||||
if elems.size() >= 3:
|
||||
ci.draw_arc(center, size * 0.72, 0, TAU, 40, Color(col, 0.5), 2)
|
||||
|
||||
Reference in New Issue
Block a user