update
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
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.
|
||||
## Modi: Einzelspieler, Kampagne, Netzwerk (Host, Mitspieler oder Zuschauer).
|
||||
|
||||
var app
|
||||
@@ -12,7 +14,6 @@ var game: Game
|
||||
var perspective: Game.Player # der Mensch, dessen Hand unten liegt
|
||||
var fx: BattleFx
|
||||
var selected = null # Game.Creature
|
||||
var pending = null # Cards.Card, wartet auf Ziel
|
||||
var fusing := false # Verschmelzen gewählt: zwei eigene Wesen anklicken
|
||||
var fuse_first = null # Game.Creature, erstes Wesen der Verschmelzung
|
||||
var msg_t := 0.0
|
||||
@@ -39,7 +40,7 @@ var undo_t := 0.0
|
||||
# Ansichten (auch für Tests)
|
||||
var tiles := {}
|
||||
var hand_views: Array = []
|
||||
var shop_views: Array = []
|
||||
var pick_views: Array = [] # Karten der offenen Wahl
|
||||
var own_views: Array = []
|
||||
var enemy_views: Array = []
|
||||
var hero_panels := {} # Player -> Panel
|
||||
@@ -49,7 +50,10 @@ var flash_until := {} # Spielerindex -> Zeitpunkt (ms)
|
||||
var hero_layer: Control
|
||||
var tile_layer: Control
|
||||
var hand_layer: Control
|
||||
var shop_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
|
||||
var status_panel: Panel
|
||||
@@ -64,15 +68,14 @@ var status_label: Label
|
||||
var msg_label: Label
|
||||
var b_end: Button
|
||||
var b_fuse: Button
|
||||
var b_reroll: Button
|
||||
var b_giveup: Button
|
||||
var field_view: FieldView
|
||||
var link_view: Links # Resonanzlinien unter den Feldreihen
|
||||
var info_label: Label # Vorschau: Resonanz, Feld, Feldkombo
|
||||
var board_panel: Panel # eigenes Brett, färbt sich mit dem Feld
|
||||
var element_view: ElementView
|
||||
var info_label: Label # Vorschau: Elementbonus, Merkmale
|
||||
var board_panel: Panel # eigenes Brett
|
||||
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 banner_turn := -1 # Zug, für den die Zugansage schon lief
|
||||
var deal_shop := false # nächster Aufbau teilt den Laden animiert aus
|
||||
var overlay: Control
|
||||
var result_label: Label
|
||||
var reward_label: Label
|
||||
@@ -172,10 +175,10 @@ func _build_static() -> void:
|
||||
b_giveup.add_theme_font_size_override("font_size", 13)
|
||||
b_log = Style.button(self, "Verlauf", L.B_LOG, toggle_log)
|
||||
b_log.add_theme_font_size_override("font_size", 13)
|
||||
# Mitte: Feldband, Meldung, eigenes Brett mit fünf Plätzen, eigene Leiste links daneben
|
||||
# Mitte: Elementzähler, Meldung, eigenes Brett mit fünf Plätzen, eigene Leiste links daneben
|
||||
Style.panel(self, L.FIELD, Style.darker(Style.PANEL, 0.9), 8)
|
||||
field_view = FieldView.new(L.FIELD)
|
||||
add_child(field_view)
|
||||
element_view = ElementView.new(L.FIELD)
|
||||
add_child(element_view)
|
||||
msg_label = Style.label(self, "", 15, Style.ORANGE, L.MSG, "center", true)
|
||||
board_panel = Style.panel(self, L.OWN_PANEL, Style.darker(Style.PANEL, 0.8), 10)
|
||||
var slots := Control.new()
|
||||
@@ -200,9 +203,10 @@ func _build_static() -> void:
|
||||
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
|
||||
# unten: Laden mit Neuwürfeln links und Zug beenden rechts
|
||||
b_reroll = Style.button(self, "", L.B_REROLL, do_reroll, Color("285a5a"))
|
||||
b_reroll.add_theme_font_size_override("font_size", 15)
|
||||
# unten: Zieh-Stapel links, Hand in der Mitte, Zug beenden rechts
|
||||
deck = Deck.new(L.DECK)
|
||||
deck.clicked.connect(func(): on_click("deck", null))
|
||||
add_child(deck)
|
||||
b_end = Style.button(self, "Zug beenden", L.B_END, end_turn, Style.PRIMARY)
|
||||
b_end.add_theme_font_size_override("font_size", 20)
|
||||
b_skip = Style.button(self, "Gegnerzüge überspringen", L.B_END, func(): skip_ai = true)
|
||||
@@ -211,16 +215,27 @@ func _build_static() -> void:
|
||||
wait_label = Style.label(self, "Warte auf den Host …", 22, Style.MUTED, Rect2(0, 300, 1280, 40), "center")
|
||||
wait_label.visible = false
|
||||
hero_layer = _layer()
|
||||
link_view = Links.new()
|
||||
add_child(link_view)
|
||||
tile_layer = _layer()
|
||||
hand_layer = _layer()
|
||||
shop_layer = _layer()
|
||||
preview_holder = _layer()
|
||||
info_label = Style.label(self, "", 13, Style.TEXT, Rect2(0, 0, 220, 60), "center")
|
||||
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)
|
||||
menu_layer.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
menu_layer.visible = false
|
||||
menu_layer.gui_input.connect(func(ev):
|
||||
if ev is InputEventMouseButton and ev.pressed and ev.button_index == MOUSE_BUTTON_LEFT:
|
||||
close_menu())
|
||||
add_child(menu_layer)
|
||||
cursor_layer = _layer()
|
||||
# Verlauf als Überlagerung
|
||||
log_overlay = Style.overlay(self)
|
||||
@@ -339,14 +354,23 @@ func my_turn() -> bool:
|
||||
|
||||
|
||||
func any_overlay() -> bool:
|
||||
return confirm.visible or overlay.visible or log_overlay.visible
|
||||
return confirm.visible or overlay.visible or log_overlay.visible or menu_layer.visible
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if game == null:
|
||||
return
|
||||
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_RIGHT:
|
||||
cancel_all()
|
||||
var under := get_viewport().gui_get_hovered_control()
|
||||
if menu_layer.visible:
|
||||
close_menu()
|
||||
elif under is CardView and under in hand_views and my_turn():
|
||||
open_menu(under.card, Rect2(under.position, under.size))
|
||||
elif under is CreatureView and under in own_views and my_turn():
|
||||
open_menu(under.creature, Rect2(under.slot, under.size))
|
||||
else:
|
||||
cancel_all()
|
||||
get_viewport().set_input_as_handled() # sonst schließt derselbe Klick das Menü gleich wieder
|
||||
return
|
||||
if event is InputEventJoypadButton and event.pressed:
|
||||
pad.button(event.button_index)
|
||||
@@ -357,9 +381,11 @@ func _input(event: InputEvent) -> void:
|
||||
KEY_ESCAPE:
|
||||
if confirm.visible:
|
||||
confirm.visible = false
|
||||
elif menu_layer.visible:
|
||||
close_menu()
|
||||
elif log_overlay.visible:
|
||||
log_overlay.visible = false
|
||||
elif selected != null or pending != null or fusing:
|
||||
elif selected != null or fusing:
|
||||
cancel_all()
|
||||
elif not any_overlay():
|
||||
confirm.visible = true
|
||||
@@ -375,15 +401,20 @@ func _input(event: InputEvent) -> void:
|
||||
if overlay.visible:
|
||||
leave()
|
||||
_:
|
||||
if event.keycode >= KEY_1 and event.keycode <= KEY_9 and my_turn() and not any_overlay():
|
||||
if event.keycode >= KEY_1 and event.keycode <= KEY_9 and not spectating() and not any_overlay():
|
||||
var idx: int = event.keycode - KEY_1
|
||||
if idx < perspective.hand.size():
|
||||
if not perspective.offer.is_empty():
|
||||
do_pick(idx)
|
||||
elif my_turn() and 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)
|
||||
|
||||
|
||||
func cancel_all() -> void:
|
||||
if confirm.visible:
|
||||
confirm.visible = false
|
||||
close_menu()
|
||||
cancel()
|
||||
refresh()
|
||||
|
||||
@@ -438,7 +469,7 @@ func _process(delta: float) -> void:
|
||||
|
||||
|
||||
func _start_ai(cur: Game.Player) -> void:
|
||||
ai_copy = game.clone()
|
||||
ai_copy = game.masked_clone(cur) # verdeckte Wesen des Menschen kennt die KI nicht
|
||||
var copy := ai_copy
|
||||
var idx := game.current_idx
|
||||
var box := ai_box
|
||||
@@ -472,25 +503,24 @@ func say(m: String) -> void:
|
||||
|
||||
func cancel() -> void:
|
||||
selected = null
|
||||
pending = null
|
||||
fusing = false
|
||||
fuse_first = null
|
||||
|
||||
|
||||
func play(card: Cards.Card, target) -> void:
|
||||
func play(card: Cards.Card, face_down: bool = false) -> void:
|
||||
if is_client():
|
||||
net.send_action({"type": "play", "hand": perspective.hand.find(card), "target": Net.ref_dict(target) if target != null else null})
|
||||
net.send_action({"type": "play", "hand": perspective.hand.find(card), "face_down": face_down})
|
||||
return
|
||||
var before: Game = game.clone() if not is_online() else null
|
||||
var log_len := game.log.size()
|
||||
var from := _hand_rect_of(card)
|
||||
var err := game.play_card(perspective, card, target)
|
||||
var err := game.play_card(perspective, card, face_down)
|
||||
if err != "":
|
||||
say(tr(err))
|
||||
return
|
||||
if from.size.x > 0 and not perspective.board.is_empty() and perspective.board[-1].card == card:
|
||||
arrivals[perspective.board[-1].uid] = from
|
||||
# Rückgängig nur, wenn nichts weiter passiert ist (keine Effekte und keine Kombos)
|
||||
# Rückgängig nur, wenn nichts weiter passiert ist (kein Wesen gestorben)
|
||||
if before != null and game.events.size() == 1 and game.events[0].type == "play":
|
||||
undo_state = before
|
||||
undo_log_len = log_len
|
||||
@@ -499,6 +529,84 @@ func play(card: Cards.Card, target) -> void:
|
||||
clear_undo()
|
||||
|
||||
|
||||
## Deckt ein eigenes verdecktes Wesen auf (kostenlos).
|
||||
func do_reveal(cr: Game.Creature) -> void:
|
||||
clear_undo()
|
||||
if is_client():
|
||||
net.send_action({"type": "reveal", "uid": cr.uid})
|
||||
return
|
||||
var err := game.reveal(perspective, cr)
|
||||
if err != "":
|
||||
say(tr(err))
|
||||
after_action()
|
||||
|
||||
|
||||
## Kauft eine Eigenschaft für eine Handkarte oder ein eigenes Wesen.
|
||||
func do_upgrade(target, keyword: String) -> void:
|
||||
clear_undo()
|
||||
if is_client():
|
||||
if target is Game.Creature:
|
||||
net.send_action({"type": "upgrade", "uid": target.uid, "keyword": keyword})
|
||||
else:
|
||||
net.send_action({"type": "upgrade", "hand": perspective.hand.find(target), "keyword": keyword})
|
||||
return
|
||||
var err := game.upgrade(perspective, target, keyword)
|
||||
if err != "":
|
||||
say(tr(err))
|
||||
after_action()
|
||||
|
||||
|
||||
## Kartenmenü neben einer Handkarte oder einem eigenen Wesen.
|
||||
func open_menu(target, anchor: Rect2) -> void:
|
||||
if spectating() or not my_turn():
|
||||
return
|
||||
Style.clear(menu_layer)
|
||||
menu_target = target
|
||||
var entries := [] # [Text, Callable, aktiv]
|
||||
var is_card: bool = target is Cards.Card
|
||||
var card: Cards.Card = target if is_card else target.card
|
||||
if is_card:
|
||||
var ok := game.can_play(perspective, card)
|
||||
entries.append([tr("Verdeckt legen ◆ %d") % card.cost, func():
|
||||
play(card, true)
|
||||
after_action(), ok[0]])
|
||||
elif target.hidden:
|
||||
entries.append([tr("Aufdecken"), func(): do_reveal(target), game.can_reveal(perspective, target)[0]])
|
||||
for kw in Cards.KEYWORDS:
|
||||
var k: String = kw
|
||||
var ok := game.can_upgrade(perspective, target, k)
|
||||
var text := "%s ◆ %d" % [tr(k), game.upgrade_cost(perspective, k)]
|
||||
if k in card.keywords:
|
||||
text = "%s ✓" % tr(k)
|
||||
entries.append([text, func(): do_upgrade(target, k), ok[0]])
|
||||
var w := 230.0
|
||||
var h := 18.0 + entries.size() * 44 + 34
|
||||
var x := clampf(anchor.end.x + 8, 10, 1270 - w)
|
||||
if x + w > anchor.position.x and x < anchor.end.x:
|
||||
x = clampf(anchor.position.x - 8 - w, 10, 1270 - w)
|
||||
var y := clampf(anchor.position.y, 10, 790 - h)
|
||||
var box := Rect2(x, y, w, h)
|
||||
Style.panel(menu_layer, box, Style.PANEL, 10, Style.GOLD, 2)
|
||||
Style.label(menu_layer, card.title(), 15, Style.GOLD, Rect2(box.position.x + 12, box.position.y + 8, w - 24, 22), "left", true)
|
||||
var by := box.position.y + 36
|
||||
for e in entries:
|
||||
var act: Callable = e[1]
|
||||
var b := Style.button(menu_layer, e[0], Rect2(box.position.x + 12, by, w - 24, 38), func():
|
||||
close_menu()
|
||||
act.call())
|
||||
b.add_theme_font_size_override("font_size", 15)
|
||||
b.disabled = not e[2]
|
||||
by += 44
|
||||
menu_layer.visible = true
|
||||
|
||||
|
||||
func close_menu() -> void:
|
||||
if menu_layer.visible:
|
||||
menu_layer.visible = false
|
||||
Style.clear(menu_layer)
|
||||
menu_target = null
|
||||
|
||||
|
||||
## Verschmelzen an- und abwählen (zwei eigene Wesen, siehe Game.fuse).
|
||||
func toggle_fuse() -> void:
|
||||
var on := not fusing
|
||||
@@ -508,37 +616,36 @@ func toggle_fuse() -> void:
|
||||
after_action()
|
||||
|
||||
|
||||
## Kauft ein Angebot aus dem Laden auf die Hand.
|
||||
func do_buy(slot: int) -> void:
|
||||
## Ziehen: kostet Mana, öffnet die Wahl aus drei Karten.
|
||||
func do_draw() -> void:
|
||||
clear_undo()
|
||||
if is_client():
|
||||
net.send_action({"type": "buy", "slot": slot})
|
||||
net.send_action({"type": "draw"})
|
||||
return
|
||||
var from := BattleLayout.shop_rect(slot)
|
||||
var offer = perspective.shop[slot] if slot < perspective.shop.size() else null
|
||||
var err := game.buy(perspective, slot)
|
||||
if err != "":
|
||||
say(tr(err))
|
||||
elif offer != null:
|
||||
fx.sfx("buy")
|
||||
fx.fly_card(offer, from, BattleLayout.hand_rect(perspective.hand.size(), perspective.hand.size() - 1),
|
||||
Cards.RARITIES[int(perspective.looks.get(offer.id, 0))])
|
||||
|
||||
|
||||
## Würfelt den Laden neu (kostet Mana).
|
||||
func do_reroll() -> void:
|
||||
if not my_turn() or any_overlay():
|
||||
return
|
||||
clear_undo()
|
||||
if is_client():
|
||||
net.send_action({"type": "reroll"})
|
||||
after_action()
|
||||
return
|
||||
var err := game.reroll(perspective)
|
||||
var err := game.draw(perspective)
|
||||
if err != "":
|
||||
say(tr(err))
|
||||
else:
|
||||
deal_shop = true
|
||||
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()
|
||||
|
||||
|
||||
@@ -609,15 +716,6 @@ func on_click(kind: String, obj) -> void:
|
||||
do_fuse(fuse_first, obj)
|
||||
after_action()
|
||||
return
|
||||
if pending != null:
|
||||
var card = pending
|
||||
pending = null
|
||||
if obj in g.valid_targets(perspective, card.target_type()):
|
||||
play(card, obj)
|
||||
else:
|
||||
say(tr("Abgebrochen"))
|
||||
after_action()
|
||||
return
|
||||
if selected != null:
|
||||
var cr = selected
|
||||
selected = null
|
||||
@@ -632,18 +730,18 @@ func on_click(kind: String, obj) -> void:
|
||||
after_action()
|
||||
return
|
||||
match kind:
|
||||
"shop":
|
||||
do_buy(int(obj))
|
||||
"deck":
|
||||
do_draw()
|
||||
"hand":
|
||||
var ok := g.can_play(perspective, obj)
|
||||
if not ok[0]:
|
||||
say(tr(ok[1]))
|
||||
elif obj.target_type() != "" and not g.valid_targets(perspective, obj.target_type()).is_empty():
|
||||
pending = obj
|
||||
say(tr("Wähle ein Ziel"))
|
||||
else:
|
||||
play(obj, null)
|
||||
play(obj)
|
||||
"own":
|
||||
if obj.hidden:
|
||||
do_reveal(obj)
|
||||
return
|
||||
if obj.can_attack():
|
||||
selected = obj
|
||||
say(tr("Wähle ein Angriffsziel"))
|
||||
@@ -669,8 +767,6 @@ func announce_turn() -> void:
|
||||
banner_turn = game.turn
|
||||
fx.banner(tr("Dein Zug"), Style.GOLD)
|
||||
fx.sfx("turn")
|
||||
deal_shop = true
|
||||
refresh()
|
||||
|
||||
|
||||
func _hand_rect_of(card: Cards.Card) -> Rect2:
|
||||
@@ -696,87 +792,109 @@ func show_preview(card, entered: bool, owner_looks: Dictionary = {}, info: Strin
|
||||
info_label.size = ir.size
|
||||
|
||||
|
||||
## Eigene Hand- und Ladenkarte: zeigt, was das Ausspielen jetzt brächte, und markiert den Kombo-Partner.
|
||||
## Eigene Hand- und Wahlkarte: zeigt den Elementbonus, den sie offen gelegt bekäme.
|
||||
func show_hand_preview(card: Cards.Card, entered: bool, anchor: Rect2 = Rect2()) -> void:
|
||||
var f := game.forecast(perspective, card)
|
||||
var lines: Array[String] = []
|
||||
var parts: Array[String] = []
|
||||
if f.resonance > 0:
|
||||
parts.append(tr("Resonanz +%d") % f.resonance)
|
||||
if f.field > 0:
|
||||
parts.append(tr("Feld +%d") % f.field)
|
||||
if not parts.is_empty():
|
||||
lines.append(" · ".join(parts))
|
||||
if f.combo != "":
|
||||
lines.append(tr("Feldkombo %s mit %s: %s") % [tr(f.combo), f.partner.card.title(), tr(Cards.FIELD_COMBO_TEXT[f.combo])])
|
||||
for t in tiles.values():
|
||||
t.mark = ""
|
||||
t.queue_redraw()
|
||||
if entered and f.partner != null and tiles.has(f.partner.uid):
|
||||
tiles[f.partner.uid].mark = f.combo
|
||||
tiles[f.partner.uid].queue_redraw()
|
||||
show_preview(card, entered, perspective.looks, "\n".join(lines), anchor)
|
||||
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)
|
||||
|
||||
|
||||
## Hand- und Ladenkarte tragen ihren Bonus und ihre Feldkombo als Vorschau.
|
||||
## 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:
|
||||
var f := game.forecast(perspective, v.card)
|
||||
v.bonus = f.resonance + f.field
|
||||
v.tag = f.combo
|
||||
v.bonus = game.forecast(perspective, v.card)
|
||||
|
||||
|
||||
## Was den Angriff eines Wesens gerade hebt.
|
||||
## Was den Angriff eines Wesens gerade hebt oder senkt.
|
||||
func creature_info(cr: Game.Creature) -> String:
|
||||
var parts: Array[String] = []
|
||||
if cr.resonance > 0:
|
||||
parts.append(tr("Resonanz +%d") % cr.resonance)
|
||||
if cr.field_bonus > 0:
|
||||
parts.append(tr("Feld +%d") % cr.field_bonus)
|
||||
var perk := cr.power() - cr.attack - cr.resonance - cr.field_bonus
|
||||
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)
|
||||
if cr.hidden:
|
||||
parts.append(tr("verdeckt"))
|
||||
return " · ".join(parts)
|
||||
|
||||
|
||||
## Elementvorteil gegen ein mögliches Ziel: des gewählten Angreifers oder eines Schadenszaubers auf der Hand.
|
||||
func matchup_for(cr: Game.Creature, valid: Array) -> int:
|
||||
if selected != null and game.can_attack(selected, cr)[0]:
|
||||
## Elementvorteil des gewählten Angreifers gegen ein mögliches Ziel; gegen Verdeckte unbekannt.
|
||||
func matchup_for(cr: Game.Creature) -> int:
|
||||
if selected != null and not cr.hidden and game.can_attack(selected, cr)[0]:
|
||||
return Game.matchup_bonus(selected, cr)
|
||||
if pending != null and cr in valid and pending.effects.any(func(fx): return fx.kind == "damage" and fx.target in Cards.CHOSEN_TARGETS):
|
||||
return Cards.matchup(pending.elements, cr.card.elements)
|
||||
return 0
|
||||
|
||||
|
||||
## Resonanzgruppen je Reihe: Wesen mit gleichem Element werden unter der Reihe verbunden.
|
||||
func resonance_groups() -> Array:
|
||||
var out := []
|
||||
for p in game.players:
|
||||
var k := 0
|
||||
for e in Cards.ELEMENTS:
|
||||
var xs: Array[float] = []
|
||||
for j in p.board.size():
|
||||
if e in p.board[j].card.elements:
|
||||
xs.append(slot_rect(p, j).get_center().x)
|
||||
if xs.size() < 2:
|
||||
continue
|
||||
out.append({"color": Style.lighter(Cards.ELEMENT_COLORS[e], 0.25), "y": slot_rect(p, 0).end.y + 4 + k * 6, "xs": xs})
|
||||
k += 1
|
||||
return out
|
||||
class Deck extends Control:
|
||||
signal clicked
|
||||
var enabled := true
|
||||
var count := 0
|
||||
var cost := Game.DRAW_COST
|
||||
|
||||
func _init(rect: Rect2) -> void:
|
||||
position = rect.position
|
||||
size = rect.size
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
|
||||
class Links extends Control:
|
||||
var groups: Array = []
|
||||
|
||||
func _init() -> void:
|
||||
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
accept_event()
|
||||
clicked.emit()
|
||||
|
||||
func _draw() -> void:
|
||||
for gr in groups:
|
||||
var y: float = gr.y
|
||||
draw_line(Vector2(gr.xs[0], y), Vector2(gr.xs[-1], y), gr.color, 3.0)
|
||||
for x in gr.xs:
|
||||
draw_circle(Vector2(x, y), 4.0, gr.color)
|
||||
for k in range(2, -1, -1):
|
||||
var r := Rect2(Vector2(k * 3, -k * 3), size - Vector2(6, 12)).grow(0)
|
||||
r.position.y += 6
|
||||
draw_style_box(Style.box(Color("3c285a"), 8, Style.GOLD if enabled else Style.MUTED, 2), r)
|
||||
if k == 0:
|
||||
Style.border(self, r.grow(-12), Color("5a3c82"), 2, 6)
|
||||
Style.text(self, tr("Ziehen"), 16, Style.TEXT if enabled else Style.MUTED, r.get_center() + Vector2(0, -12), "center", true)
|
||||
Style.text(self, "◆ %d" % cost, 20, Style.BLUE if enabled else Style.MUTED, r.get_center() + Vector2(0, 14), "center", true)
|
||||
Style.text(self, str(count), 12, Style.MUTED, Vector2(r.get_center().x, r.end.y - 18), "center")
|
||||
if not enabled:
|
||||
draw_rect(Rect2(Vector2.ZERO, size), Color(0, 0, 0, 0.35))
|
||||
|
||||
|
||||
|
||||
|
||||
# --- Netzwerk -------------------------------------------------------------------------------
|
||||
@@ -845,9 +963,7 @@ func refresh() -> void:
|
||||
var h := perspective
|
||||
var now := Time.get_ticks_msec()
|
||||
var mine := my_turn()
|
||||
var valid: Array = g.valid_targets(h, pending.target_type()) if pending != null else []
|
||||
hand_views = []
|
||||
shop_views = []
|
||||
own_views = []
|
||||
enemy_views = []
|
||||
|
||||
@@ -858,7 +974,7 @@ func refresh() -> void:
|
||||
if g.current() == o:
|
||||
bcol = Style.BLUE
|
||||
bw = 3
|
||||
if o in valid or (selected != null and o.alive() and g.can_attack(selected, o)[0]):
|
||||
if selected != null and o.alive() and g.can_attack(selected, o)[0]:
|
||||
bcol = Style.ORANGE
|
||||
bw = 3
|
||||
if int(flash_until.get(g.players.find(o), 0)) > now:
|
||||
@@ -870,7 +986,7 @@ func refresh() -> void:
|
||||
labels.name.add_theme_color_override("font_color", Style.TEXT if o.alive() else Style.MUTED)
|
||||
if o.alive():
|
||||
labels.life.text = "♥ %d" % maxi(o.life, 0)
|
||||
labels.stats.text = "Hand %d ◆ %d (+%d)" % [o.hand.size(), o.energy, o.max_energy]
|
||||
labels.stats.text = "Hand %d ◆ %d" % [o.hand.size(), o.energy]
|
||||
else:
|
||||
labels.life.text = "besiegt"
|
||||
labels.life.add_theme_color_override("font_color", Style.MUTED)
|
||||
@@ -898,7 +1014,11 @@ func refresh() -> void:
|
||||
tiles[cr.uid] = tile
|
||||
tile_layer.add_child(tile)
|
||||
var looks: Dictionary = p.looks
|
||||
tile.hover.connect(func(card, entered): show_preview(card, entered, looks, creature_info(tile.creature) if entered else "", Rect2(tile.slot, tile.size)))
|
||||
tile.hover.connect(func(card, entered):
|
||||
if tile.secret:
|
||||
show_preview(null, false)
|
||||
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):
|
||||
var c := game.find_creature(uid)
|
||||
@@ -920,7 +1040,7 @@ func refresh() -> void:
|
||||
elif fusing and p == h and mine:
|
||||
b = Style.PURPLE
|
||||
w = 3
|
||||
elif cr in valid or (selected != null and g.can_attack(selected, cr)[0]):
|
||||
elif selected != null and g.can_attack(selected, cr)[0]:
|
||||
b = Style.ORANGE
|
||||
w = 3
|
||||
elif p == h and mine and cr.can_attack():
|
||||
@@ -928,8 +1048,8 @@ func refresh() -> void:
|
||||
w = 3
|
||||
tile.border = b
|
||||
tile.width = w
|
||||
tile.mark = ""
|
||||
tile.matchup = matchup_for(cr, valid)
|
||||
tile.secret = cr.hidden and p != h
|
||||
tile.matchup = matchup_for(cr)
|
||||
tile.queue_redraw()
|
||||
if p == h:
|
||||
own_views.append(tile)
|
||||
@@ -940,31 +1060,12 @@ func refresh() -> void:
|
||||
var t: CreatureView = tiles[uid]
|
||||
tiles.erase(uid)
|
||||
fx.fade_out(t)
|
||||
link_view.groups = resonance_groups()
|
||||
link_view.queue_redraw()
|
||||
|
||||
Style.clear(shop_layer)
|
||||
if not spectating():
|
||||
for i in h.shop.size():
|
||||
var offer = h.shop[i]
|
||||
var sr := BattleLayout.shop_rect(i)
|
||||
if offer == null:
|
||||
Style.panel(shop_layer, sr, Style.darker(Style.PANEL, 0.8), 8, Style.PANEL_LIGHT, 2)
|
||||
continue
|
||||
var sv := CardView.new(offer, sr.size, false, Cards.RARITIES[int(h.looks.get(offer.id, 0))])
|
||||
sv.position = sr.position
|
||||
sv.lift = mine
|
||||
apply_forecast(sv)
|
||||
sv.dim = not mine or h.energy < offer.cost or h.hand.size() >= Game.MAX_HAND
|
||||
sv.hover.connect(func(c, e): show_hand_preview(c, e, Rect2(sv.position, sv.size)))
|
||||
var slot := i
|
||||
sv.set_meta("slot", slot) # der Platz im Laden, auch für das Gamepad
|
||||
sv.clicked.connect(func(_view): on_click("shop", slot))
|
||||
shop_layer.add_child(sv)
|
||||
shop_views.append(sv)
|
||||
if deal_shop:
|
||||
fx.deal(sv, i)
|
||||
deal_shop = false
|
||||
build_pick()
|
||||
deck.visible = not spectating()
|
||||
deck.enabled = mine and g.can_draw(h)[0]
|
||||
deck.count = g.deck.size()
|
||||
deck.cost = g.draw_cost(h)
|
||||
deck.queue_redraw()
|
||||
|
||||
Style.clear(hand_layer)
|
||||
if spectating():
|
||||
@@ -978,9 +1079,7 @@ func refresh() -> void:
|
||||
v.position = r.position
|
||||
v.lift = mine
|
||||
apply_forecast(v)
|
||||
if card == pending:
|
||||
v.border = Style.YELLOW
|
||||
elif playable:
|
||||
if playable:
|
||||
v.border = Style.GREEN
|
||||
v.dim = not playable
|
||||
v.hover.connect(func(c, e): show_hand_preview(c, e, Rect2(v.position, v.size)))
|
||||
@@ -992,14 +1091,10 @@ func refresh() -> void:
|
||||
if int(flash_until.get(g.players.find(h), 0)) > now:
|
||||
bar_border = Style.RED
|
||||
status_panel.add_theme_stylebox_override("panel", Style.box(Style.PANEL, 8, bar_border, 3 if bar_border != null else 0))
|
||||
var board_col := Style.darker(Style.PANEL, 0.8)
|
||||
if g.field_element != "":
|
||||
board_col = board_col.lerp(Cards.ELEMENT_COLORS[g.field_element], 0.22) # das Feld färbt das Brett
|
||||
board_panel.add_theme_stylebox_override("panel", Style.box(board_col, 10))
|
||||
name_label.text = h.name
|
||||
life_label.text = "♥ %d" % maxi(h.life, 0)
|
||||
energy_label.text = "◆ %d" % h.energy
|
||||
income_label.text = tr("+%d je Zug") % h.max_energy
|
||||
income_label.text = tr("nächster Zug ◆ %d") % (h.max_energy + 1)
|
||||
status_label.text = (tr("Zug %d · Du bist dran") % g.turn) if mine else (tr("Zug %d · %s ist dran …") % [g.turn, g.current().name])
|
||||
if spectating():
|
||||
status_label.text = tr("Zuschauer") + " · " + status_label.text
|
||||
@@ -1009,21 +1104,18 @@ func refresh() -> void:
|
||||
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_reroll.visible = mine and not spectating()
|
||||
b_reroll.text = tr("Neu würfeln ◆ %d") % Game.REROLL_COST
|
||||
b_reroll.disabled = h.energy < Game.REROLL_COST
|
||||
b_fuse.text = tr("Abbrechen") if fusing else tr("Verschmelzen")
|
||||
field_view.update_from(g)
|
||||
element_view.update_from(g)
|
||||
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 line.begins_with("Das Feld wird"):
|
||||
elif " verschmelzen zu " in line or " wird aufgedeckt." in line:
|
||||
lines.append("[color=#8c78dc]%s[/color]" % line)
|
||||
elif line.begins_with("Feldkombo"):
|
||||
lines.append("[color=#f5c850]%s[/color]" % line)
|
||||
elif " bekommt " in line:
|
||||
lines.append("[color=#5ad282]%s[/color]" % line)
|
||||
else:
|
||||
lines.append(line)
|
||||
log_box.text = "\n".join(lines)
|
||||
|
||||
Reference in New Issue
Block a user