- KI-Bewertung mit Bedrohung und Führenden-Bremse (62:38 gegen die alte im Duell, gleichauf zu viert) - Balance: Sand +1/+1 statt Schild, Erde+Luft und Erde geschwächt, Wasser angehoben, Messlauf mit N=200 möglich - Einzelspieler-Kämpfe werden laufend gespeichert (voller Spielstand, viewer -1) und im Menü fortgesetzt - Karten, Deckzeilen und Kartenrücken fokussierbar: Gamepad in Deckbau, Werkstatt und Booster - Kartennamen (name_en) und Kartentexte übersetzbar, Übersetzungstest deckt cards.gd mit ab - Deck-Code DINGER1:id*anzahl mit Zwischenablage; acht neue Karten; Kampagne Kapitel 3; Elementbooster 120 Münzen - Netzwerk: Nachfolge-Host mit vollem Spielstand, Neuverbindung der anderen, Zuschauer (Sicht -2); Bot kann hosten - Große Schrift: Kartennamen, Kartentext, Knopfleisten, Gegnervorschau, Dialogtext - Anleitung 1 garantiert ein 1-Energie-Wesen; Tests: DINGER_ONLY für einzelne Oberflächentests - Oberflächentest auch auf Xephyr mit llvmpipe (wie Xvfb in der CI) grün Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPgKvy5W9WRNFHjkm5QsHx
160 lines
5.9 KiB
GDScript
160 lines
5.9 KiB
GDScript
class_name BattleFx
|
|
extends RefCounted
|
|
## Animationen und Töne des Kampfes aus den Spielereignissen.
|
|
|
|
var screen # BattleScreen
|
|
var enabled := true
|
|
|
|
|
|
func _init(s) -> void:
|
|
screen = s
|
|
|
|
|
|
func sfx(kind: String) -> void:
|
|
screen.app.sfx.play(kind)
|
|
|
|
|
|
## Spielt die Ereignisse einer Aktion ab. centers: vorher gemerkte Positionen (für gestorbene Wesen).
|
|
func play_events(evs: Array, centers: Dictionary) -> void:
|
|
var g: Game = screen.game
|
|
var delay := 0.0
|
|
for ev in evs:
|
|
match ev.type:
|
|
"attack":
|
|
sfx("attack")
|
|
var atk: CreatureView = screen.tiles.get(ev.attacker.uid)
|
|
lunge(atk, center_of(ev.target, centers))
|
|
flash_target(ev.target)
|
|
delay = 0.12
|
|
"damage":
|
|
sfx("damage")
|
|
popup("-%d" % ev.amount, Style.RED, center_of(ev.target, centers), delay)
|
|
"heal":
|
|
sfx("heal")
|
|
popup("+%d" % ev.amount, Style.GREEN, center_of(ev.target, centers), delay)
|
|
"buff":
|
|
popup(ev.text, Style.GREEN, center_of(ev.target, centers), delay)
|
|
"blocked":
|
|
popup("Schild!", Style.BLUE, center_of(ev.target, centers), delay)
|
|
"freeze":
|
|
popup("Eis", Color("aaddff"), center_of(ev.target, centers), delay)
|
|
"combo":
|
|
sfx("combo")
|
|
var where: Vector2 = screen.target_center({"kind": "creature", "uid": ev.uid})
|
|
if where.x < 0:
|
|
where = screen.target_center({"kind": "player", "index": ev.player})
|
|
popup("Feldkombo %s!" % ev.name, Style.GOLD, where + Vector2(0, -30), 0.1)
|
|
"play":
|
|
sfx("play")
|
|
"spell":
|
|
sfx("play")
|
|
var caster: Game.Player = g.players[ev.player]
|
|
popup(ev.card.name, Style.GOLD, screen.target_center({"kind": "player", "index": ev.player}), 0.0)
|
|
if caster != screen.perspective:
|
|
screen.show_preview(ev.card, true)
|
|
screen.preview_timer = 1.6
|
|
"trap_set":
|
|
sfx("click")
|
|
popup("Falle gelegt", Style.MUTED, screen.target_center({"kind": "player", "index": ev.player}), 0.0)
|
|
"trap":
|
|
sfx("combo")
|
|
popup("Falle: %s!" % ev.card.name, Style.ORANGE, screen.target_center({"kind": "player", "index": ev.player}) + Vector2(0, -20), 0.0)
|
|
screen.show_preview(ev.card, true)
|
|
screen.preview_timer = 1.8
|
|
"mulligan":
|
|
if ev.count > 0:
|
|
popup(("tauscht 1 Karte" if int(ev.count) == 1 else "tauscht %d Karten" % ev.count), Style.MUTED, screen.target_center({"kind": "player", "index": ev.player}), 0.0)
|
|
|
|
|
|
func center_of(ref: Dictionary, centers: Dictionary) -> Vector2:
|
|
var c: Vector2 = centers.get(screen.ref_key(ref), Vector2(-1, -1))
|
|
if c.x < 0:
|
|
c = screen.target_center(ref)
|
|
return c
|
|
|
|
|
|
func popup(text: String, color: Color, at: Vector2, delay: float) -> void:
|
|
if not enabled or at.x < 0:
|
|
return
|
|
var l := Label.new()
|
|
l.text = text
|
|
l.add_theme_font_size_override("font_size", 22)
|
|
l.add_theme_font_override("font", Style.FONT_BOLD)
|
|
l.add_theme_color_override("font_color", color)
|
|
l.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.9))
|
|
l.add_theme_constant_override("outline_size", 6)
|
|
l.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
screen.fx_layer.add_child(l)
|
|
l.reset_size()
|
|
l.position = at - l.size / 2
|
|
l.modulate.a = 0.0
|
|
var tw: Tween = screen.create_tween()
|
|
tw.tween_interval(delay)
|
|
tw.tween_property(l, "modulate:a", 1.0, 0.08)
|
|
tw.tween_property(l, "position:y", l.position.y - 44, 0.8).set_ease(Tween.EASE_OUT)
|
|
tw.parallel().tween_property(l, "modulate:a", 0.0, 0.5).set_delay(0.35)
|
|
tw.tween_callback(l.queue_free)
|
|
|
|
|
|
func lunge(tile: CreatureView, target: Vector2) -> void:
|
|
if tile == null or not enabled or target.x < 0:
|
|
return
|
|
tile.busy = true
|
|
var start := tile.slot
|
|
var dest := start + (target - (start + tile.size / 2)) * 0.35
|
|
var tw := tile.create_tween()
|
|
tw.tween_property(tile, "position", dest, 0.12).set_ease(Tween.EASE_OUT)
|
|
tw.tween_property(tile, "position", start, 0.15).set_ease(Tween.EASE_IN)
|
|
tw.tween_callback(func():
|
|
tile.busy = false
|
|
tile.position = tile.slot)
|
|
|
|
|
|
func flash_target(ref: Dictionary) -> void:
|
|
if not enabled:
|
|
return
|
|
if ref.kind == "creature":
|
|
var tile: CreatureView = screen.tiles.get(ref.uid)
|
|
if tile == null:
|
|
return
|
|
tile.flash = true
|
|
tile.queue_redraw()
|
|
var tw := tile.create_tween()
|
|
tw.tween_interval(0.35)
|
|
tw.tween_callback(func():
|
|
tile.flash = false
|
|
tile.queue_redraw())
|
|
else:
|
|
screen.flash_until[int(ref.index)] = Time.get_ticks_msec() + 350
|
|
var tw: Tween = screen.create_tween()
|
|
tw.tween_interval(0.37)
|
|
tw.tween_callback(screen.refresh)
|
|
|
|
|
|
func pop_in(tile: CreatureView) -> void:
|
|
if not enabled:
|
|
return
|
|
tile.scale = Vector2(0.2, 0.2)
|
|
tile.modulate.a = 0.0
|
|
var tw := tile.create_tween().set_parallel(true)
|
|
tw.tween_property(tile, "scale", Vector2.ONE, 0.25).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
|
|
tw.tween_property(tile, "modulate:a", 1.0, 0.15)
|
|
|
|
|
|
func fade_out(tile: CreatureView) -> void:
|
|
tile.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
if not enabled:
|
|
tile.queue_free()
|
|
return
|
|
var tw := tile.create_tween().set_parallel(true)
|
|
tw.tween_property(tile, "modulate:a", 0.0, 0.3)
|
|
tw.tween_property(tile, "scale", Vector2(0.6, 0.6), 0.3)
|
|
tw.chain().tween_callback(tile.queue_free)
|
|
|
|
|
|
func move_to(tile: CreatureView, pos: Vector2) -> void:
|
|
if enabled:
|
|
screen.create_tween().tween_property(tile, "position", pos, 0.2)
|
|
else:
|
|
tile.position = pos
|