saumwacht-godot/scripts/luneraria/ui/components/next_wave_panel.gd
Martin Stickelbroeck 1b540f2a55 G3 (Zwischenstand): neues Ingame-HUD; Projekt wechselt nach Unity
Nicht abgenommen: Nach dem ersten Spieltest wurde entschieden, Champions of
Luneraria nach Unity 6 zu portieren. Dieser Stand ist die Referenz für das
HUD im Unity-Projekt (../luneraria-unity).

- HUD neu aus Komponenten mit eigenem Theme, Porträts aus den echten Modellen
  (einmal gerendert, zwischengespeichert), Lane-Übersicht mit deterministischer
  Bedrohung aus öffentlichem Zustand, Bossgrotte, nächste Welle, Ereignisse,
  Minimap mit zusammengefassten Armeen, Held/Items/Talente, Einheitendetails,
  Angriffschampions, Bereit-Sperre, UI-Größe, Team-Formen statt nur Farben
- Privatsphäre über LuneHudModel: fremdes Gold und fremde Entwürfe erscheinen nie
- Keine Regeländerung: Regeln 91/91, M2 73/73, Netz 43/43; verify_g3 715/715;
  verify_m1 38/38 und verify_m2 14/14 mit angepassten UI-Treibern
- Spieltest bis Welle 8 über die echte Oberfläche, Befunde in G3-HUD.md
- HELDEN-KONZEPT.md: vom Projektinhaber vorgegebene Helden (nicht umgesetzt)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 19:59:55 +02:00

123 lines
5.7 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class_name LuneNextWave
extends PanelContainer
## What the coming wave brings: monster types with counts (identical for both sides and every
## lane), new types, announced attack champions and an announced boss. Dangers for the own
## team are highlighted. In combat the panel shrinks to one line about the following wave.
var _title: Label
var _round: Label
var _rows: VBoxContainer
var _extra: VBoxContainer
var _note: Label
var _signature: String = ""
var compact: bool = false
func _init() -> void:
theme_type_variation = "HudPanelTight"
mouse_filter = Control.MOUSE_FILTER_STOP
var column := VBoxContainer.new()
column.add_theme_constant_override("separation", 4)
column.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(column)
var head := HBoxContainer.new()
head.mouse_filter = Control.MOUSE_FILTER_IGNORE
column.add_child(head)
_title = LuneUiTheme.label("NÄCHSTE WELLE", "HudCaps", head)
_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_round = LuneUiTheme.label("", "HudValue", head)
_rows = VBoxContainer.new()
_rows.add_theme_constant_override("separation", 2)
_rows.mouse_filter = Control.MOUSE_FILTER_IGNORE
column.add_child(_rows)
_note = LuneUiTheme.label("", "HudMuted", column)
_note.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_extra = VBoxContainer.new()
_extra.add_theme_constant_override("separation", 3)
_extra.mouse_filter = Control.MOUSE_FILTER_IGNORE
column.add_child(_extra)
func apply(wave: Dictionary, own_team: int, fighting: bool) -> void:
var signature: String = "%s|%d|%s" % [str(wave), own_team, fighting]
if signature == _signature:
return
_signature = signature
compact = fighting
_title.text = "DANACH" if fighting else "NÄCHSTE WELLE"
_round.text = "WELLE %d" % int(wave.round)
for child: Node in _rows.get_children():
child.free()
for child: Node in _extra.get_children():
child.free()
var monsters: Array = wave.monsters
if fighting:
var parts: PackedStringArray = []
for monster: Dictionary in monsters:
parts.append("%d× %s%s" % [int(monster.count), monster.name, " (neu)" if monster.new else ""])
_note.text = ", ".join(parts) + " je Lane"
return
for monster: Dictionary in monsters:
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 7)
row.mouse_filter = Control.MOUSE_FILTER_PASS
_rows.add_child(row)
var portrait := LunePortraitRect.new()
portrait.custom_minimum_size = Vector2(30, 30)
row.add_child(portrait)
portrait.show_model(str(monster.id))
var count := LuneUiTheme.label("%d×" % int(monster.count), "HudValue", row)
count.custom_minimum_size.x = 30
var title := LuneUiTheme.label(str(monster.name), "HudText", row)
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
title.clip_text = true
title.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
LuneGlyph.make("armor", LuneUiTheme.MUTED, 13.0, row)
LuneUiTheme.label(DataCatalog.ARMOR_NAMES[int(monster.armor)], "HudMuted", row)
if monster.new:
var badge := LuneStatusBadge.new()
badge.set_state("NEU", LuneUiTheme.WARN)
row.add_child(badge)
var data: Dictionary = monster
LuneTooltip.attach(row, func() -> Dictionary: return {"title": str(data.name), "subtitle": "Monster der Welle %d" % int(wave.round),
"stats": [["heart", "Leben", LuneUiTheme.number(data.hp)], ["sword", "Schaden/Treffer", LuneUiTheme.decimal(data.damage)],
["range", "Reichweite", "%s m" % LuneUiTheme.decimal(data.range)], ["armor", "Rüstung", DataCatalog.ARMOR_NAMES[int(data.armor)]]],
"note": "Beide Teams erhalten auf jeder Lane dieselbe Welle."})
# Counter hint as in Legion TD 2: the armour most of the wave wears and the damage type
# that hits it for 130 % (LuneUnit.DAMAGE_MATRIX).
var armour_count: Array[int] = [0, 0, 0]
for monster: Dictionary in monsters:
armour_count[int(monster.armor)] += int(monster.count)
var main_armour: int = armour_count.find(armour_count.max())
var counter: int = 0
for attack: int in range(3):
if is_equal_approx(float(LuneUnit.DAMAGE_MATRIX[attack][main_armour]), 1.30):
counter = attack
_note.text = "%d Monster je Lane · beide Seiten gleich\nMeist %s: %s trifft stark" % [int(wave.count), DataCatalog.ARMOR_NAMES[main_armour], DataCatalog.DAMAGE_NAMES[counter]]
var champions: Array = wave.champions
var boss: Dictionary = wave.boss
if champions.is_empty() and boss.is_empty():
return
LuneUiTheme.label("ANGEKÜNDIGT", "HudCaps", _extra)
for entry: Dictionary in champions:
var hostile: bool = not bool(entry.own)
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 6)
row.mouse_filter = Control.MOUSE_FILTER_IGNORE
_extra.add_child(row)
LuneGlyph.make("champion", LuneUiTheme.DANGER if hostile else LuneUiTheme.CHAMPION, 15.0, row)
var text := LuneUiTheme.label("%s → %s" % [entry.name, LuneMap.LANE_NAMES[entry.lane].to_upper()], "HudText", row)
text.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var badge := LuneStatusBadge.new()
badge.set_state("GEGEN EUCH" if hostile else "FÜR EUCH", LuneUiTheme.DANGER if hostile else LuneUiTheme.GOOD)
row.add_child(badge)
if not boss.is_empty():
var hostile_boss: bool = not bool(boss.own)
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 6)
row.mouse_filter = Control.MOUSE_FILTER_IGNORE
_extra.add_child(row)
LuneGlyph.make("crown", LuneUiTheme.DANGER if hostile_boss else LuneUiTheme.team_color(own_team), 16.0, row)
var text := LuneUiTheme.label("Schlundkrone → %s" % LuneMap.LANE_NAMES[boss.lane].to_upper(), "HudText", row)
text.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var badge := LuneStatusBadge.new()
badge.set_state("GEGEN EUCH" if hostile_boss else "FÜR EUCH", LuneUiTheme.DANGER if hostile_boss else LuneUiTheme.GOOD, "crown")
row.add_child(badge)