Files
cards/scripts/ui/battle_fx.gd
team3 f2c609a08b
Some checks are pending
Tests / headless (push) Waiting to run
Tests / fenster-schnell (push) Waiting to run
Tests / fenster-voll (push) Waiting to run
Tests / balance (push) Waiting to run
update
2026-10-13 15:39:55 +02:00

252 lines
8.9 KiB
GDScript

class_name BattleFx
extends RefCounted
## Animationen und Töne des Kampfes aus den Spielereignissen.
var screen # BattleScreen
var enabled := true
var banners: Array = [] # wartende Ansagen [Text, Farbe]
var banner_running := false
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)
shake()
delay = 0.12
"damage":
sfx("damage")
popup("-%d" % ev.amount, Style.RED, 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":
sfx("combo")
popup(tr("Aufgedeckt!"), Style.GOLD, screen.target_center({"kind": "creature", "uid": ev.uid}), 0.0)
"fuse":
sfx("combo")
popup("Verschmolzen!", Style.PURPLE, screen.target_center({"kind": "player", "index": ev.player}), 0.0)
"draw":
sfx("click")
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
## Mehrere Meldungen an derselben Stelle rücken nach unten, sonst liegen sie übereinander.
func _free_spot(at: Vector2) -> Vector2:
for step in 6:
var spot := at + Vector2(0, step * 26)
var taken := false
for l in screen.fx_layer.get_children():
if l is Label and l.get_meta("spot", Vector2(-999, -999)).distance_to(spot) < 20:
taken = true
break
if not taken:
return spot
return at
func popup(text: String, color: Color, at: Vector2, delay: float) -> void:
if not enabled or at.x < 0:
return
at = _free_spot(at)
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
l.set_meta("spot", at)
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:
fade_view(tile)
## Blendet eine Ansicht aus und entfernt sie (Wesen, die sterben; Handkarten, die verschmelzen).
func fade_view(view: Control) -> void:
view.mouse_filter = Control.MOUSE_FILTER_IGNORE
if not enabled:
view.queue_free()
return
var tw := view.create_tween().set_parallel(true)
tw.tween_property(view, "modulate:a", 0.0, 0.3)
tw.tween_property(view, "scale", Vector2(0.6, 0.6), 0.3)
tw.chain().tween_callback(view.queue_free)
## Eine Ansicht gleitet von from an ihren Platz (die neue Handkarte nach dem Verschmelzen).
func glide(view: Control, from: Rect2) -> void:
if not enabled:
return
var to := view.position
view.position = from.position
view.scale = from.size / view.size
var tw := view.create_tween().set_parallel(true)
tw.tween_property(view, "position", to, 0.25).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
tw.tween_property(view, "scale", Vector2.ONE, 0.25).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
func move_to(tile: CreatureView, pos: Vector2) -> void:
if enabled:
screen.create_tween().tween_property(tile, "position", pos, 0.2)
else:
tile.position = pos
## Ein Wesen fliegt von der Handkachel auf seinen Platz.
func fly_in(tile: CreatureView, from: Rect2) -> void:
if not enabled:
return
tile.busy = true
tile.position = from.position
tile.scale = from.size / tile.size
var tw := tile.create_tween().set_parallel(true)
tw.tween_property(tile, "position", tile.slot, 0.22).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
tw.tween_property(tile, "scale", Vector2.ONE, 0.22).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
tw.chain().tween_callback(func():
tile.busy = false
tile.position = tile.slot)
## Kurzes Zittern des Bretts beim Treffer.
func shake() -> void:
if not enabled:
return
var layer: Control = screen.tile_layer
var tw: Tween = screen.create_tween()
for i in 3:
tw.tween_property(layer, "position", Vector2(3 if i % 2 == 0 else -3, 0), 0.03)
tw.tween_property(layer, "position", Vector2.ZERO, 0.03)
## Ansage quer über die Mitte: Zugbeginn, Feldwechsel. Mehrere folgen nacheinander.
func banner(text: String, color: Color) -> void:
if not enabled:
return
banners.append([text, color])
if not banner_running:
_next_banner()
func _next_banner() -> void:
if banners.is_empty() or not is_instance_valid(screen) or not screen.is_inside_tree():
banner_running = false
return
banner_running = true
var next: Array = banners.pop_front()
var text: String = next[0]
var color: Color = next[1]
var delay := 0.0
var band := ColorRect.new()
band.color = Color(0, 0, 0, 0.55)
band.position = Vector2(0, 355)
band.size = Vector2(1280, 90)
band.mouse_filter = Control.MOUSE_FILTER_IGNORE
band.scale.y = 0.0
band.pivot_offset = Vector2(640, 45)
screen.fx_layer.add_child(band)
var l := Label.new()
l.text = text
l.add_theme_font_size_override("font_size", 40)
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", 8)
l.mouse_filter = Control.MOUSE_FILTER_IGNORE
screen.fx_layer.add_child(l)
l.reset_size()
l.position = Vector2(640, 400) - l.size / 2
l.pivot_offset = l.size / 2
l.scale = Vector2(0.6, 0.6)
l.modulate.a = 0.0
var tw: Tween = screen.create_tween().set_parallel(true)
tw.tween_property(band, "scale:y", 1.0, 0.15).set_delay(delay).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
tw.tween_property(l, "scale", Vector2.ONE, 0.25).set_delay(delay).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
tw.tween_property(l, "modulate:a", 1.0, 0.12).set_delay(delay)
tw.tween_property(l, "modulate:a", 0.0, 0.3).set_delay(delay + 0.8)
tw.tween_property(band, "scale:y", 0.0, 0.2).set_delay(delay + 0.9)
tw.chain().tween_callback(func():
l.queue_free()
band.queue_free()
_next_banner())