135 lines
4.9 KiB
GDScript
135 lines
4.9 KiB
GDScript
class_name HudOverlay
|
|
extends Control
|
|
|
|
## Zeichnet Lebens- und Manabalken, Rolle, Sterne und Schadenszahlen
|
|
## über die 3D-Einheiten. Spielt außerdem die Kampfgeräusche ab.
|
|
|
|
const FLOAT_TIME := 0.9
|
|
const FLOAT_RISE := 42.0
|
|
|
|
var board: BoardView3D = null
|
|
var sfx: Sfx = null
|
|
## Tooltip zu einer Einheit, gesetzt von Main (Maus über Einheit oder Shop-Karte).
|
|
var tooltip_avatar: Avatar = null
|
|
var tooltip_pos := Vector2.ZERO
|
|
var tooltip_note := ""
|
|
var _font: Font
|
|
var _floaters: Array = [] ## {"world": Vector3, "text": String, "col": Color, "age": float}
|
|
var _combat_seen: Combat = null
|
|
|
|
|
|
func _ready() -> void:
|
|
_font = ThemeDB.fallback_font
|
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
set_process(true)
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
_pull_hits()
|
|
for f in _floaters:
|
|
f["age"] += delta
|
|
_floaters = _floaters.filter(func(f): return f["age"] < FLOAT_TIME)
|
|
queue_redraw()
|
|
|
|
|
|
## Holt neue Treffer aus dem laufenden Spielerkampf und macht Zahlen und Töne daraus.
|
|
func _pull_hits() -> void:
|
|
if board == null or board.game == null:
|
|
return
|
|
var c: Combat = board.game.player_combat
|
|
if c == null or not c.record_hits:
|
|
_combat_seen = null
|
|
return
|
|
if c != _combat_seen:
|
|
_combat_seen = c
|
|
_floaters.clear()
|
|
if c.hits.is_empty():
|
|
return
|
|
var my_team := board.game.human_team_in_combat()
|
|
for h in c.hits:
|
|
var world := board.world_pos(board.mirror_for_view(h["pos"], my_team))
|
|
var mine: bool = h["team"] == my_team
|
|
match h["kind"]:
|
|
"dmg":
|
|
_floaters.append({"world": world, "text": str(h["amount"]),
|
|
"col": Color(1.0, 0.55, 0.45) if mine else Color(1.0, 0.95, 0.85), "age": 0.0})
|
|
if sfx: sfx.play("treffer")
|
|
"crit":
|
|
_floaters.append({"world": world, "text": str(h["amount"]) + "!",
|
|
"col": Color(1.0, 0.85, 0.3), "age": 0.0})
|
|
if sfx: sfx.play("krit")
|
|
"counter":
|
|
# Konter-Treffer: Element oder Rolle war stark gegen das Ziel.
|
|
_floaters.append({"world": world, "text": str(h["amount"]) + " ▲",
|
|
"col": Color(1.0, 0.6, 0.9), "age": 0.0})
|
|
if sfx: sfx.play("krit")
|
|
"heal":
|
|
_floaters.append({"world": world, "text": "+" + str(h["amount"]),
|
|
"col": Color(0.55, 1.0, 0.6), "age": 0.0})
|
|
if sfx: sfx.play("heilung")
|
|
"cast":
|
|
if sfx: sfx.play("zauber")
|
|
var tgt := board.world_pos(board.mirror_for_view(h["target"], my_team))
|
|
board.spawn_effect(world, tgt, h["role"], Data.ELEMENT_COLORS[h["element"]])
|
|
"attack":
|
|
var from := board.world_pos(board.mirror_for_view(h["from"], my_team))
|
|
board.spawn_attack(from, world, h["ranged"], Data.ELEMENT_COLORS[h["element"]])
|
|
c.hits.clear()
|
|
|
|
|
|
func _draw() -> void:
|
|
if board == null or board.camera == null:
|
|
return
|
|
var cam := board.camera
|
|
for d in board.draw_units:
|
|
var world: Vector3 = d["pos"]
|
|
if cam.is_position_behind(world):
|
|
continue
|
|
var head := cam.unproject_position(world + Vector3(0.0, 1.35, 0.0))
|
|
var foot := cam.unproject_position(world + Vector3(0.0, 0.05, 0.0))
|
|
var a: Avatar = d["avatar"]
|
|
var side: int = d["side"]
|
|
|
|
var bw: float = clampf(absf(foot.y - head.y) * 0.85, 26.0, 70.0)
|
|
var bx := head.x - bw * 0.5
|
|
var by := head.y - 4.0
|
|
draw_rect(Rect2(bx - 1.0, by - 1.0, bw + 2.0, 8.0), Color(0.05, 0.05, 0.07, 0.85))
|
|
draw_rect(Rect2(bx, by, bw * clampf(d["hp"], 0.0, 1.0), 6.0),
|
|
Color(0.35, 0.85, 0.42) if side == 0 else Color(0.92, 0.38, 0.38))
|
|
if d["mana"] > 0.0:
|
|
draw_rect(Rect2(bx, by + 7.0, bw, 4.0), Color(0.05, 0.05, 0.07, 0.85))
|
|
draw_rect(Rect2(bx, by + 7.0, bw * clampf(d["mana"], 0.0, 1.0), 4.0),
|
|
Color(0.35, 0.62, 0.95))
|
|
# Schild als heller Streifen über dem Lebensbalken.
|
|
if d.get("shield", 0.0) > 0.0:
|
|
draw_rect(Rect2(bx, by - 4.0, bw * clampf(d["shield"], 0.0, 1.0), 3.0), Color(0.85, 0.9, 1.0))
|
|
|
|
for s in a.star:
|
|
draw_circle(Vector2(bx + bw * 0.5 - 7.0 + 7.0 * float(s), by - 9.0), 2.6,
|
|
Color(1.0, 0.85, 0.25))
|
|
draw_string(_font, Vector2(bx + bw + 4.0, by + 7.0), Data.ROLE_SHORT[a.role],
|
|
HORIZONTAL_ALIGNMENT_LEFT, -1, 13, Color(0.9, 0.9, 0.95, 0.9))
|
|
if d["cast"]:
|
|
draw_arc(foot, bw * 0.55, 0.0, TAU, 24, Color(1.0, 0.95, 0.5, 0.9), 2.5)
|
|
# Zustände als kleine Marken neben dem Balken.
|
|
var marks := []
|
|
if d.get("burn", false): marks.append(["B", Color(1.0, 0.5, 0.2)])
|
|
if d.get("slow", false): marks.append(["S", Color(0.6, 0.85, 1.0)])
|
|
if d.get("stun", false): marks.append(["Z", Color(1.0, 0.9, 0.3)])
|
|
for i in marks.size():
|
|
draw_string(_font, Vector2(bx - 12.0 - 11.0 * float(i), by + 7.0), marks[i][0],
|
|
HORIZONTAL_ALIGNMENT_LEFT, -1, 12, marks[i][1])
|
|
|
|
# Schwebende Zahlen
|
|
for f in _floaters:
|
|
var t: float = f["age"] / FLOAT_TIME
|
|
var pos := cam.unproject_position(f["world"] + Vector3(0.0, 1.5, 0.0))
|
|
pos.y -= t * FLOAT_RISE
|
|
var col: Color = f["col"]
|
|
col.a = 1.0 - t * t
|
|
var size := 15 if f["text"].ends_with("!") else 13
|
|
draw_string(_font, pos + Vector2(1, 1), f["text"], HORIZONTAL_ALIGNMENT_CENTER, -1, size,
|
|
Color(0, 0, 0, col.a * 0.8))
|
|
draw_string(_font, pos, f["text"], HORIZONTAL_ALIGNMENT_CENTER, -1, size, col)
|
|
|