update
This commit is contained in:
@@ -1,17 +1,16 @@
|
||||
class_name BattleScreen
|
||||
extends Control
|
||||
## Kampf: Zustand, Eingabe und Abgleich der Ansicht. Layout in BattleLayout, Animationen in BattleFx.
|
||||
## Esc öffnet die Optionen (Verlauf, Aufgeben, Einstellungen, Hauptmenü, Beenden); den Zug beendet
|
||||
## 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 …“.
|
||||
## Modi: Einzelspieler, Kampagne, Netzwerk (Host, Mitspieler oder Zuschauer).
|
||||
## Modi: Einzelspieler und Kampagne.
|
||||
|
||||
var app
|
||||
var stage := -1 # Kampagnenstufe oder -1
|
||||
var net_play := false # Netzwerkspiel (auch als Zuschauer)
|
||||
var online_seat := -1 # Netzwerk: eigener Platz, Zuschauer -1
|
||||
var net: Net
|
||||
var game: Game
|
||||
var perspective: Game.Player # der Mensch, dessen Hand unten liegt
|
||||
var fx: BattleFx
|
||||
@@ -60,12 +59,10 @@ var flash_until := {} # Spielerindex -> Zeitpunkt (ms)
|
||||
var hero_layer: Control
|
||||
var tile_layer: Control
|
||||
var hand_layer: Control
|
||||
var offer_views: Array = [] # die offenen Karten des Stapels (CardView)
|
||||
var offer_layer: Control
|
||||
var deck_label: Label
|
||||
var draw_enabled := false
|
||||
var deck: Deck
|
||||
var opp_deck: Deck # Stapel des Gegners, nur zum Ansehen
|
||||
var log_overlay: Control
|
||||
var b_log: Button
|
||||
var options: Control # Optionsmenü (Esc): Verlauf, Aufgeben, Einstellungen, Hauptmenü, Beenden
|
||||
var status_panel: Panel
|
||||
var fx_layer: Control
|
||||
var log_box: RichTextLabel
|
||||
@@ -74,8 +71,6 @@ var name_label: Label
|
||||
var life_label: Label
|
||||
var energy_label: Label
|
||||
var msg_label: Label
|
||||
var b_end: Button
|
||||
var b_giveup: Button
|
||||
var element_view: ElementView # eigene Elementleiste
|
||||
var opp_element_view: ElementView # die des Gegners darüber
|
||||
var info_label: Label # Vorschau: Elementbonus, Merkmale
|
||||
@@ -89,7 +84,6 @@ var reward_label: Label
|
||||
var notice_label: Label
|
||||
var confirm: Control
|
||||
var cursor_layer: Control
|
||||
var wait_label: Label
|
||||
var b_skip: Button
|
||||
var b_undo: Button
|
||||
|
||||
@@ -98,30 +92,6 @@ func _init(campaign_stage: int = -1) -> void:
|
||||
stage = campaign_stage
|
||||
|
||||
|
||||
static func online(seat: int) -> BattleScreen:
|
||||
var b := BattleScreen.new(-1)
|
||||
b.net_play = true
|
||||
b.online_seat = seat
|
||||
return b
|
||||
|
||||
|
||||
func is_online() -> bool:
|
||||
return net_play
|
||||
|
||||
|
||||
## Zuschauer im Netzwerk: kein eigener Platz, keine Hand.
|
||||
func spectating() -> bool:
|
||||
return is_online() and online_seat < 0
|
||||
|
||||
|
||||
func is_client() -> bool:
|
||||
return is_online() and not net.is_host
|
||||
|
||||
|
||||
func counts_for_profile() -> bool:
|
||||
return not is_online()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var s: Dictionary = app.profile.settings
|
||||
fx = BattleFx.new(self)
|
||||
@@ -129,31 +99,19 @@ func _ready() -> void:
|
||||
pad = BattleGamepad.new(self)
|
||||
ai_delay = 0.75 / float(s.ai_speed)
|
||||
_build_static()
|
||||
if is_online():
|
||||
net = app.net
|
||||
net.ended.connect(_on_net_ended)
|
||||
net.migrated.connect(_on_migrated)
|
||||
net.status_changed.connect(_on_net_status)
|
||||
if net.is_host:
|
||||
game = net.game
|
||||
net.action_applied.connect(after_action)
|
||||
else:
|
||||
net.snapshot_received.connect(_on_snapshot)
|
||||
wait_label.visible = true
|
||||
return # der erste Zustand kommt vom Host
|
||||
elif stage >= 0:
|
||||
if stage >= 0:
|
||||
var r := Game.make_rng()
|
||||
game = Game.new_battle_with(Campaign.opponent(stage), r, false, own_rules())
|
||||
game = Game.new_battle_with(Campaign.opponent(stage, r), r, false, own_rules())
|
||||
else:
|
||||
game = Game.new_battle(Game.make_rng(), own_rules())
|
||||
smart_ai = bool(app.profile.difficulty().smart) or not counts_for_profile()
|
||||
smart_ai = bool(app.profile.difficulty().smart)
|
||||
_begin()
|
||||
|
||||
|
||||
## Kartenoptik, Gegnerleben (Schwierigkeit) und das eigene Merkmal (nach Kapitel 1).
|
||||
## Eigener Stapel und Kartenstufen, Gegnerleben (Schwierigkeit) und das eigene Merkmal (nach Kapitel 1).
|
||||
func own_rules() -> Dictionary:
|
||||
var p: Profile = app.profile
|
||||
var rules := {"looks": p.looks()}
|
||||
var rules := {"looks": p.looks(), "deck": p.deck_cards()}
|
||||
if stage < 0:
|
||||
rules["opponent_life"] = int(p.difficulty().life)
|
||||
if p.perk != "" and p.perk_unlocked():
|
||||
@@ -162,37 +120,22 @@ func own_rules() -> Dictionary:
|
||||
|
||||
|
||||
func _begin() -> void:
|
||||
if is_online():
|
||||
perspective = game.players[maxi(online_seat, 0)]
|
||||
else:
|
||||
game.events.clear()
|
||||
perspective = game.current() if game.current().is_human else game.humans[0]
|
||||
game.events.clear()
|
||||
perspective = game.human
|
||||
build_hero_boxes()
|
||||
if is_online() and net.is_host:
|
||||
net.broadcast()
|
||||
game.events.clear()
|
||||
refresh()
|
||||
announce_turn()
|
||||
|
||||
|
||||
func _build_static() -> void:
|
||||
var L := BattleLayout
|
||||
# oben links: Aufgeben und Verlauf, klein
|
||||
b_giveup = Style.button(self, "Aufgeben", L.B_GIVEUP, func(): confirm.visible = true)
|
||||
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)
|
||||
b_end = Style.button(self, "Zug beenden", L.B_END, end_turn, Style.PRIMARY)
|
||||
b_end.add_theme_font_size_override("font_size", 15)
|
||||
b_skip = Style.button(self, "Überspringen", L.B_END, func(): skip_ai = true)
|
||||
b_skip.add_theme_font_size_override("font_size", 15)
|
||||
b_skip = Style.button(self, "Überspringen", L.B_SKIP, func(): skip_ai = true)
|
||||
b_skip.add_theme_font_size_override("font_size", 13)
|
||||
b_skip.visible = false
|
||||
# Mitte: Elementzähler, Meldung, eigenes Brett mit fünf Plätzen, eigene Leiste links daneben
|
||||
Style.panel(self, L.FIELD_OPP, Style.darker(Style.PANEL, 0.9), 8)
|
||||
opp_element_view = ElementView.new(L.FIELD_OPP)
|
||||
# Elementzähler beider Seiten links neben ihrem Brett
|
||||
opp_element_view = ElementView.new(L.OPP_ELEMENTS)
|
||||
add_child(opp_element_view)
|
||||
Style.panel(self, L.FIELD, Style.darker(Style.PANEL, 0.9), 8)
|
||||
element_view = ElementView.new(L.FIELD)
|
||||
element_view = ElementView.new(L.OWN_ELEMENTS)
|
||||
add_child(element_view)
|
||||
msg_label = Style.label(self, "", 14, Style.ORANGE, L.MSG, "center", true)
|
||||
msg_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
@@ -207,18 +150,21 @@ func _build_static() -> void:
|
||||
var sx: float = L.STATUS.position.x
|
||||
var sy: float = L.STATUS.position.y
|
||||
var sw: float = L.STATUS.size.x
|
||||
name_label = Style.label(self, "", 18, Style.TEXT, Rect2(sx + 8, sy + 6, sw - 16, 26), "center", true)
|
||||
name_label = Style.label(self, "", 16, Style.TEXT, Rect2(sx + 8, sy + 4, sw - 16, 22), "center", true)
|
||||
name_label.clip_text = true
|
||||
life_label = Style.label(self, "", 30, Style.RED, Rect2(sx + 8, sy + 42, sw / 2 - 8, 40), "left", true)
|
||||
energy_label = Style.label(self, "", 30, Style.BLUE, Rect2(sx + sw / 2, sy + 42, sw / 2 - 8, 40), "right", true)
|
||||
life_label = Style.label(self, "", 26, Style.RED, Rect2(sx + 10, sy + 30, sw / 2 - 10, 34), "left", true)
|
||||
energy_label = Style.label(self, "", 26, Style.BLUE, Rect2(sx + sw / 2, sy + 30, sw / 2 - 10, 34), "right", true)
|
||||
b_undo = Style.button(self, "Rückgängig", L.B_UNDO, undo)
|
||||
b_undo.add_theme_font_size_override("font_size", 13)
|
||||
b_undo.visible = false
|
||||
deck_label = Style.label(self, "", 13, Style.MUTED, L.DECK_LABEL, "center")
|
||||
wait_label = Style.label(self, "Warte auf den Host …", 22, Style.MUTED, Rect2(0, 300, 1280, 40), "center")
|
||||
wait_label.visible = false
|
||||
deck = Deck.new(L.DECK)
|
||||
deck.clicked.connect(func(): on_click("deck", null))
|
||||
add_child(deck)
|
||||
opp_deck = Deck.new(L.OPP_DECK)
|
||||
opp_deck.interactive = false
|
||||
opp_deck.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(opp_deck)
|
||||
hero_layer = _layer()
|
||||
offer_layer = _layer()
|
||||
tile_layer = _layer()
|
||||
hand_layer = _layer()
|
||||
preview_holder = _layer()
|
||||
@@ -252,6 +198,28 @@ func _build_static() -> void:
|
||||
log_box.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
log_overlay.add_child(log_box)
|
||||
Style.button(log_overlay, "Schließen", Rect2(L.LOG_PANEL.end.x - 180, L.LOG_PANEL.end.y - 52, 160, 40), func(): log_overlay.visible = false)
|
||||
# Optionsmenü (Esc)
|
||||
options = Style.overlay(self)
|
||||
options.get_child(0).gui_input.connect(func(ev):
|
||||
if ev is InputEventMouseButton and ev.pressed:
|
||||
options.visible = false)
|
||||
Style.panel(options, L.OPTIONS, Style.PANEL, 10, Style.GOLD, 2)
|
||||
Style.label(options, "Optionen", 24, Style.GOLD, Rect2(L.OPTIONS.position.x, L.OPTIONS.position.y + 18, L.OPTIONS.size.x, 34), "center", true)
|
||||
var oy := L.OPTIONS.position.y + 76
|
||||
for entry in [["Verlauf", func():
|
||||
options.visible = false
|
||||
log_overlay.visible = true],
|
||||
["Aufgeben", func():
|
||||
options.visible = false
|
||||
confirm.visible = true],
|
||||
["Einstellungen", func():
|
||||
options.visible = false
|
||||
app.show_screen(SettingsScreen.new(), true)],
|
||||
["Hauptmenü", leave],
|
||||
["Beenden", func(): get_tree().quit()]]:
|
||||
var ob := Style.button(options, entry[0], Rect2(L.OPTIONS.position.x + 60, oy, L.OPTIONS.size.x - 120, 48), entry[1])
|
||||
ob.add_theme_font_size_override("font_size", 18)
|
||||
oy += 60
|
||||
# Spielende
|
||||
overlay = _overlay()
|
||||
result_label = Style.label(overlay, "", 56, Style.GREEN, Rect2(0, 250, 1280, 80), "center", true)
|
||||
@@ -261,8 +229,8 @@ func _build_static() -> void:
|
||||
confirm = Style.confirm(self, "Wirklich aufgeben?", "", "Ja, aufgeben", leave, "Weiterspielen")
|
||||
|
||||
|
||||
func toggle_log() -> void:
|
||||
log_overlay.visible = not log_overlay.visible
|
||||
func toggle_options() -> void:
|
||||
options.visible = not options.visible
|
||||
|
||||
|
||||
func _layer() -> Control:
|
||||
@@ -277,8 +245,6 @@ func _overlay() -> Control:
|
||||
|
||||
|
||||
func leave() -> void:
|
||||
if is_online():
|
||||
net.leave()
|
||||
app.show_screen(MenuScreen.new())
|
||||
|
||||
|
||||
@@ -336,24 +302,22 @@ func build_hero_boxes() -> void:
|
||||
var title: String = ("%s · %s" % [o.name, o.title]) if o.title != "" else o.name
|
||||
if not o.perks.is_empty():
|
||||
title += " ★"
|
||||
labels["name"] = Style.label(hero_layer, title, 18, Style.TEXT, Rect2(box.position.x + 8, box.position.y + 6, box.size.x - 16, 26), "center", true)
|
||||
labels["name"] = Style.label(hero_layer, title, 16, Style.TEXT, Rect2(box.position.x + 8, box.position.y + 4, box.size.x - 16, 22), "center", true)
|
||||
labels.name.clip_text = true
|
||||
labels.name.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
labels["life"] = Style.label(hero_layer, "", 30, Style.RED, Rect2(box.position.x + 8, box.position.y + 42, box.size.x / 2 - 8, 40), "left", true)
|
||||
labels["stats"] = Style.label(hero_layer, "", 30, Style.BLUE, Rect2(box.position.x + box.size.x / 2, box.position.y + 42, box.size.x / 2 - 8, 40), "right", true)
|
||||
labels["life"] = Style.label(hero_layer, "", 26, Style.RED, Rect2(box.position.x + 10, box.position.y + 30, box.size.x / 2 - 10, 34), "left", true)
|
||||
labels["stats"] = Style.label(hero_layer, "", 26, Style.BLUE, Rect2(box.position.x + box.size.x / 2, box.position.y + 30, box.size.x / 2 - 10, 34), "right", true)
|
||||
hero_labels[o] = labels
|
||||
|
||||
|
||||
# --- Ablauf und Eingabe ------------------------------------------------------------------
|
||||
|
||||
func my_turn() -> bool:
|
||||
if spectating():
|
||||
return false
|
||||
return game != null and game.winner == "" and game.current() == perspective
|
||||
|
||||
|
||||
func any_overlay() -> bool:
|
||||
return confirm.visible or overlay.visible or log_overlay.visible or menu_layer.visible
|
||||
return confirm.visible or overlay.visible or log_overlay.visible or menu_layer.visible or options.visible
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
@@ -400,10 +364,12 @@ func _input(event: InputEvent) -> void:
|
||||
close_menu()
|
||||
elif log_overlay.visible:
|
||||
log_overlay.visible = false
|
||||
elif options.visible:
|
||||
options.visible = false
|
||||
elif selected != null or fuse_first != null:
|
||||
cancel_all()
|
||||
elif not any_overlay():
|
||||
confirm.visible = true
|
||||
options.visible = true
|
||||
KEY_SPACE:
|
||||
if my_turn() and not any_overlay():
|
||||
end_turn()
|
||||
@@ -451,8 +417,6 @@ func _process(delta: float) -> void:
|
||||
_end_drag()
|
||||
do_fuse(a, b)
|
||||
after_action()
|
||||
if is_online() and net.is_host:
|
||||
net.tick()
|
||||
if game.winner != "":
|
||||
if not ended:
|
||||
ended = true
|
||||
@@ -461,8 +425,6 @@ func _process(delta: float) -> void:
|
||||
var cur := game.current()
|
||||
if cur.is_human:
|
||||
return
|
||||
if is_client():
|
||||
return
|
||||
if skip_ai:
|
||||
ai_timer = 0.0
|
||||
else:
|
||||
@@ -506,15 +468,10 @@ func _exit_tree() -> void:
|
||||
|
||||
func _finish() -> void:
|
||||
var won := game.winner == "human"
|
||||
if counts_for_profile():
|
||||
var factor: float = float(app.profile.difficulty().reward) if stage < 0 else 1.0
|
||||
reward = app.profile.record_battle(won, stage, factor)
|
||||
app.save()
|
||||
app.sfx.play("win" if won else "lose")
|
||||
else:
|
||||
var wp := game.winner_player
|
||||
var mine: bool = wp != null and wp == perspective
|
||||
app.sfx.play("win" if mine else "lose")
|
||||
var factor: float = float(app.profile.difficulty().reward) if stage < 0 else 1.0
|
||||
reward = app.profile.record_battle(won, stage, factor)
|
||||
app.save()
|
||||
app.sfx.play("win" if won else "lose")
|
||||
refresh()
|
||||
|
||||
|
||||
@@ -631,10 +588,7 @@ func fusion_partners(target) -> Array:
|
||||
|
||||
|
||||
func play(card: Cards.Card, face_down: bool = false) -> void:
|
||||
if is_client():
|
||||
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 before: Game = game.clone()
|
||||
var log_len := game.log.size()
|
||||
var from := _hand_rect_of(card)
|
||||
var err := game.play_card(perspective, card, face_down)
|
||||
@@ -655,9 +609,6 @@ func play(card: Cards.Card, face_down: bool = false) -> void:
|
||||
## 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))
|
||||
@@ -667,7 +618,7 @@ func do_reveal(cr: Game.Creature) -> 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():
|
||||
if not my_turn():
|
||||
return
|
||||
cancel()
|
||||
Style.clear(menu_layer)
|
||||
@@ -685,7 +636,7 @@ func open_menu(target, anchor: Rect2, full: bool = false) -> void:
|
||||
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():
|
||||
entries.append([tr("Verschmelzen mit …"), func():
|
||||
fuse_first = target
|
||||
say(tr("Ziel wählen"))
|
||||
refresh(), not partners.is_empty()])
|
||||
@@ -729,22 +680,16 @@ func close_menu() -> void:
|
||||
## Abwerfen: eine Handkarte gegen Mana loswerden.
|
||||
func do_discard(card: Cards.Card) -> void:
|
||||
clear_undo()
|
||||
if is_client():
|
||||
net.send_action({"type": "discard", "hand": perspective.hand.find(card)})
|
||||
return
|
||||
var err := game.discard(perspective, card)
|
||||
if err != "":
|
||||
say(tr(err))
|
||||
after_action()
|
||||
|
||||
|
||||
## Ziehen: kostet Mana, eine Karte kommt auf die Hand.
|
||||
func do_draw(index: int = 0) -> void:
|
||||
## Ziehen: eine Karte vom eigenen Stapel, die erste im Zug frei.
|
||||
func do_draw() -> void:
|
||||
clear_undo()
|
||||
if is_client():
|
||||
net.send_action({"type": "draw", "index": index})
|
||||
return
|
||||
var err := game.draw(perspective, index)
|
||||
var err := game.draw(perspective)
|
||||
if err != "":
|
||||
say(tr(err))
|
||||
else:
|
||||
@@ -755,12 +700,6 @@ func do_draw(index: int = 0) -> void:
|
||||
func do_fuse(a, b) -> void:
|
||||
cancel()
|
||||
clear_undo()
|
||||
if is_client():
|
||||
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 != "":
|
||||
say(tr(err))
|
||||
@@ -784,8 +723,6 @@ func undo() -> void:
|
||||
restored.say("Rückgängig.")
|
||||
game = restored
|
||||
perspective = game.players[idx]
|
||||
if is_online() and net.is_host:
|
||||
net.game = game
|
||||
clear_undo()
|
||||
cancel()
|
||||
build_hero_boxes()
|
||||
@@ -797,9 +734,6 @@ func end_turn() -> void:
|
||||
return
|
||||
cancel()
|
||||
clear_undo()
|
||||
if is_client():
|
||||
net.send_action({"type": "end"})
|
||||
return
|
||||
game.end_turn()
|
||||
ai_timer = ai_delay
|
||||
after_action()
|
||||
@@ -823,17 +757,14 @@ func on_click(kind: String, obj) -> void:
|
||||
selected = null
|
||||
if kind == "enemy" or kind == "hero":
|
||||
clear_undo()
|
||||
if is_client():
|
||||
net.send_action({"type": "attack", "uid": cr.uid, "target": Net.ref_dict(obj)})
|
||||
else:
|
||||
var err := g.attack(cr, obj)
|
||||
if err != "":
|
||||
say(tr(err))
|
||||
var err := g.attack(cr, obj)
|
||||
if err != "":
|
||||
say(tr(err))
|
||||
after_action()
|
||||
return
|
||||
match kind:
|
||||
"deck":
|
||||
do_draw(int(obj) if obj != null else 0)
|
||||
do_draw()
|
||||
"hand":
|
||||
var ok := g.can_play(perspective, obj)
|
||||
if not ok[0]:
|
||||
@@ -949,6 +880,41 @@ func update_attack_preview() -> void:
|
||||
v.queue_redraw()
|
||||
|
||||
|
||||
## Der eigene Stapel: Kartenstoß mit Kosten des nächsten Ziehens und Restzahl.
|
||||
class Deck extends Control:
|
||||
signal clicked
|
||||
var enabled := true
|
||||
var interactive := true # false: der Stapel des Gegners, nur Anzahl
|
||||
var count := 0
|
||||
var cost := 0
|
||||
|
||||
func _init(rect: Rect2) -> void:
|
||||
position = rect.position
|
||||
size = rect.size
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
|
||||
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 k in range(2, -1, -1):
|
||||
var r := Rect2(Vector2(k * 3, -k * 3), size - Vector2(6, 12))
|
||||
r.position.y += 6
|
||||
draw_style_box(Style.box(Color("3c285a"), 8, Style.GOLD if enabled and interactive else Style.MUTED, 2), r)
|
||||
if k == 0:
|
||||
Style.border(self, r.grow(-12), Color("5a3c82"), 2, 6)
|
||||
if interactive:
|
||||
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")
|
||||
else:
|
||||
Style.text(self, str(count), 22, Style.TEXT, r.get_center(), "center", true)
|
||||
if interactive and not enabled:
|
||||
draw_rect(Rect2(Vector2.ZERO, size), Color(0, 0, 0, 0.35))
|
||||
|
||||
|
||||
## Eine verdeckte Handkarte des Gegners in voller Größe.
|
||||
class CardBack extends Control:
|
||||
func _init(rect: Rect2) -> void:
|
||||
@@ -967,51 +933,10 @@ class CardBack extends Control:
|
||||
|
||||
|
||||
|
||||
# --- Netzwerk -------------------------------------------------------------------------------
|
||||
|
||||
func _on_snapshot(snap: Dictionary) -> void:
|
||||
var first := game == null
|
||||
game = Game.from_snapshot(snap)
|
||||
wait_label.visible = false
|
||||
if first:
|
||||
_begin()
|
||||
return
|
||||
perspective = game.players[maxi(online_seat, 0)]
|
||||
build_hero_boxes()
|
||||
after_action()
|
||||
|
||||
|
||||
## Host-Wechsel: das Spiel läuft jetzt auf diesem Rechner weiter.
|
||||
func _on_migrated() -> void:
|
||||
game = net.game
|
||||
if not net.action_applied.is_connected(after_action):
|
||||
net.action_applied.connect(after_action)
|
||||
wait_label.visible = false
|
||||
perspective = game.players[online_seat]
|
||||
build_hero_boxes()
|
||||
say("Du bist jetzt Host.")
|
||||
after_action()
|
||||
|
||||
|
||||
func _on_net_status() -> void:
|
||||
wait_label.text = net.status
|
||||
wait_label.visible = true
|
||||
|
||||
|
||||
func _on_net_ended(reason: String) -> void:
|
||||
if game == null or game.winner == "":
|
||||
say(reason)
|
||||
await get_tree().create_timer(1.5).timeout
|
||||
if is_inside_tree():
|
||||
app.show_screen(MenuScreen.new())
|
||||
|
||||
|
||||
# --- Ereignisse ----------------------------------------------------------------------------
|
||||
|
||||
## Nach jeder Aktion: Ereignisse sichern, Ansicht angleichen, Animationen abspielen.
|
||||
func after_action() -> void:
|
||||
if is_online() and net.is_host:
|
||||
net.broadcast()
|
||||
var evs := game.events.duplicate()
|
||||
game.events.clear()
|
||||
var centers := {}
|
||||
@@ -1138,23 +1063,12 @@ func refresh() -> void:
|
||||
tiles.erase(uid)
|
||||
fx.fade_out(t)
|
||||
update_attack_preview()
|
||||
Style.clear(offer_layer)
|
||||
offer_views = []
|
||||
draw_enabled = mine and g.can_draw(h)[0]
|
||||
if not spectating():
|
||||
for i in h.offer.size():
|
||||
var oc: Cards.Card = h.offer[i]
|
||||
var orect := Rect2(BattleLayout.OFFER_X, BattleLayout.OFFER_Y + i * (BattleLayout.HAND_H + BattleLayout.OFFER_GAP), BattleLayout.HAND_W, BattleLayout.HAND_H)
|
||||
var ov := CardView.new(oc, orect.size, false, oc.rarity_in(h.looks))
|
||||
ov.position = orect.position
|
||||
ov.dim = not draw_enabled
|
||||
var idx := i
|
||||
ov.clicked.connect(func(_v): on_click("deck", idx))
|
||||
ov.hover.connect(func(c, e): show_preview(c, e, h.looks, "", orect))
|
||||
offer_layer.add_child(ov)
|
||||
offer_views.append(ov)
|
||||
deck_label.text = "%s ◆ %d · %d" % [tr("Ziehen"), g.draw_cost(h), g.deck.size()]
|
||||
deck_label.visible = not spectating()
|
||||
deck.enabled = mine and g.can_draw(h)[0]
|
||||
deck.count = h.deck.size()
|
||||
deck.cost = g.draw_cost(h)
|
||||
deck.queue_redraw()
|
||||
opp_deck.count = opponent().deck.size()
|
||||
opp_deck.queue_redraw()
|
||||
|
||||
Style.clear(hand_layer)
|
||||
for o in others(): # die Hand des Gegners oben, nur Rückseiten, spiegelverkehrter Bogen
|
||||
@@ -1165,9 +1079,7 @@ func refresh() -> void:
|
||||
back.rotation_degrees = arc[0]
|
||||
back.position.y += arc[1]
|
||||
hand_layer.add_child(back)
|
||||
if spectating():
|
||||
Style.label(hand_layer, "Zuschauer: Hände sind verdeckt", 18, Style.MUTED, Rect2(240, BattleLayout.HAND_Y + 18, 800, 30), "center")
|
||||
else:
|
||||
if true:
|
||||
for i in h.hand.size():
|
||||
var card: Cards.Card = h.hand[i]
|
||||
var playable: bool = mine and g.can_play(h, card)[0]
|
||||
@@ -1199,10 +1111,8 @@ func refresh() -> void:
|
||||
name_label.text = h.name
|
||||
life_label.text = "♥ %d" % maxi(h.life, 0)
|
||||
energy_label.text = "◆ %d" % h.energy
|
||||
b_end.disabled = not mine
|
||||
var ai_running: bool = g.winner == "" and not g.current().is_human and not is_client()
|
||||
var ai_running: bool = g.winner == "" and not g.current().is_human
|
||||
b_skip.visible = ai_running
|
||||
b_end.visible = not ai_running and not spectating()
|
||||
b_undo.visible = undo_state != null and mine
|
||||
element_view.update_from(g, h)
|
||||
opp_element_view.update_from(g, opponent())
|
||||
@@ -1222,20 +1132,13 @@ func refresh() -> void:
|
||||
pad.draw_cursor()
|
||||
|
||||
if g.winner != "":
|
||||
var wp := g.winner_player
|
||||
if counts_for_profile():
|
||||
var won := g.winner == "human"
|
||||
result_label.text = "Sieg!" if won else "Niederlage"
|
||||
result_label.add_theme_color_override("font_color", Style.GREEN if won else Style.RED)
|
||||
reward_label.text = "+%d Münzen" % maxi(reward, 0)
|
||||
var notes: Array[String] = app.profile.take_notices()
|
||||
if not notes.is_empty():
|
||||
notice_label.text = "\n".join(notes)
|
||||
else:
|
||||
var mine_win: bool = wp != null and wp == perspective
|
||||
result_label.text = ("%s gewinnt!" % wp.name) if wp != null and wp.is_human else "Die KI gewinnt"
|
||||
result_label.add_theme_color_override("font_color", Style.GREEN if mine_win else Style.RED)
|
||||
reward_label.text = "Gemeinsame Partie, keine Münzen"
|
||||
var won := g.winner == "human"
|
||||
result_label.text = "Sieg!" if won else "Niederlage"
|
||||
result_label.add_theme_color_override("font_color", Style.GREEN if won else Style.RED)
|
||||
reward_label.text = "+%d Münzen" % maxi(reward, 0)
|
||||
var notes: Array[String] = app.profile.take_notices()
|
||||
if not notes.is_empty():
|
||||
notice_label.text = "\n".join(notes)
|
||||
overlay.visible = true
|
||||
confirm.visible = false
|
||||
|
||||
|
||||
Reference in New Issue
Block a user