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) "blocked": popup("Schild!", Style.BLUE, center_of(ev.target, centers), delay) "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) "upgrade": sfx("heal") if ev.uid >= 0: popup("+" + tr(ev.keyword), Style.GREEN, 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: 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 ## 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) ## 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: 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, 300) 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, 345) - 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())