saumwacht-godot/scripts/luneraria/ui/components/boss_indicator.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

166 lines
7.4 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 LuneBossIndicator
extends PanelContainer
## The boss grotto and the Schlundkrone at a glance: BOSS VERFÜGBAR, BOSS UMKÄMPFT, BOSS
## EROBERT, EINSATZ NÄCHSTE WELLE, BOSS GREIFT AN, COOLDOWN – X RUNDEN. When a team owns the
## boss the card names target lane, deployment wave and the bounty the defenders earn; the
## player who steers the boss chooses its lane here. A click on the card moves the camera.
signal focus_requested
signal lane_chosen(lane: String)
var data: Dictionary = {}
var portrait: LunePortraitRect
var _badge: LuneStatusBadge
var _line: Label
var _risk: Label
var _bar: LuneHpBar
var _choice: HBoxContainer
var _lane_buttons: Dictionary = {}
var _owner: LuneGlyph
var _signature: String = ""
var compact: bool = false
var _own_team: int = 0
var _round: int = 0
var _planning: bool = true
func _init() -> void:
theme_type_variation = "HudPanelTight"
mouse_filter = Control.MOUSE_FILTER_STOP
mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
var row := HBoxContainer.new()
row.add_theme_constant_override("separation", 10)
row.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(row)
portrait = LunePortraitRect.new()
portrait.custom_minimum_size = Vector2(64, 64)
portrait.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
row.add_child(portrait)
_owner = LuneGlyph.make("emblem_2", LuneUiTheme.TEAM[2], 18.0, portrait)
_owner.position = Vector2(2, 44)
var column := VBoxContainer.new()
column.add_theme_constant_override("separation", 3)
column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
column.mouse_filter = Control.MOUSE_FILTER_IGNORE
row.add_child(column)
var head := HBoxContainer.new()
head.add_theme_constant_override("separation", 6)
head.mouse_filter = Control.MOUSE_FILTER_IGNORE
column.add_child(head)
LuneGlyph.make("grotto", LuneUiTheme.TEAM[2], 15.0, head)
LuneUiTheme.label("BOSSGROTTE · G", "HudCaps", head)
_badge = LuneStatusBadge.new()
column.add_child(_badge)
_badge.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
_line = LuneUiTheme.label("", "HudText", column)
_line.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_risk = LuneUiTheme.label("", "HudMuted", column)
_risk.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_bar = LuneHpBar.new()
_bar.custom_minimum_size = Vector2(0, 5)
_bar.visible = false
column.add_child(_bar)
_choice = HBoxContainer.new()
_choice.add_theme_constant_override("separation", 4)
_choice.visible = false
column.add_child(_choice)
for lane: String in LuneMap.LANES:
var button := LuneUiTheme.button(LuneMap.LANE_NAMES[lane].to_upper(), "HudToggle", _choice)
button.toggle_mode = true
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
button.pressed.connect(func() -> void: lane_chosen.emit(lane))
_lane_buttons[lane] = button
LuneTooltip.attach(self, _tip)
## Short form while the right column shows a selected unit: badge only.
func set_compact(value: bool) -> void:
if value == compact:
return
compact = value
portrait.custom_minimum_size = Vector2(40, 40) if value else Vector2(64, 64)
_signature = ""
if not data.is_empty():
apply(data, _own_team, _round, _planning)
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
focus_requested.emit()
accept_event()
func apply(boss: Dictionary, own_team: int, round_number: int, planning: bool) -> void:
var signature: String = "%s|%d|%d|%s" % [str(boss), own_team, round_number, planning]
if signature == _signature:
return
_signature = signature
data = boss
_own_team = own_team
_round = round_number
_planning = planning
portrait.show_model("schlundkrone")
var owner: int = int(boss.team)
var own: bool = owner == own_team
var lane_text: String = LuneMap.LANE_NAMES.get(str(boss.lane), "?").to_upper()
var side: String = LuneUiTheme.team_name(owner)
_owner.visible = owner >= 0
_owner.glyph = "emblem_%d" % clampi(owner, 0, 2)
_owner.color = LuneUiTheme.team_color(owner)
_bar.visible = false
_choice.visible = false
_risk.text = ""
match str(boss.state):
"available":
_badge.set_state("BOSS VERFÜGBAR", LuneUiTheme.TEAM[2], "crown")
_line.text = "Einheiten mit „Grotte“ entsenden. Wer den Boss besiegt, schickt ihn in der Folgewelle gegen den Gegner."
"contested":
_badge.set_state("BOSS UMKÄMPFT", LuneUiTheme.WARN, "melee")
_line.text = "Expeditionen kämpfen in der Grotte; sie fehlen ihren Lanes bis Kampfende."
"conquered":
_badge.set_state("BOSS EROBERT · %s" % side.to_upper(), LuneUiTheme.team_color(owner), "crown", true)
_line.text = "Einsatz in Welle %d für %s." % [int(boss.deploy_round), "dein Team" if own else side]
"failed":
_badge.set_state("EXPEDITION GESCHEITERT", LuneUiTheme.MUTED, "close")
_line.text = "Der Boss hat überlebt; niemand erhält ihn."
"deploy_next":
_badge.set_state("EINSATZ NÄCHSTE WELLE", LuneUiTheme.team_color(owner) if own else LuneUiTheme.DANGER, "crown", true)
if own:
_line.text = "Deine Schlundkrone greift in Welle %d auf %s an." % [int(boss.deploy_round), lane_text]
_risk.text = "Fällt sie, erhält %s %s Gold Kopfgeld." % [LuneUiTheme.team_name(1 - own_team), LuneUiTheme.number(boss.bounty)]
else:
_line.text = "%s: Schlundkrone greift in Welle %d auf %s an." % [side, int(boss.deploy_round), lane_text]
_risk.text = "Besiegt ihr sie, erhält dein Team %s Gold Kopfgeld." % LuneUiTheme.number(boss.bounty)
if bool(boss.chooser) and planning:
_choice.visible = true
for lane: String in _lane_buttons:
(_lane_buttons[lane] as Button).set_pressed_no_signal(lane == str(boss.lane))
_risk.text += " Du wählst die Lane:"
"deploying":
_badge.set_state("BOSS GREIFT AN · %s" % lane_text, LuneUiTheme.team_color(owner) if own else LuneUiTheme.DANGER, "warning", true)
_line.text = ("Deine Schlundkrone greift %s an." if own else "Gegnerische Schlundkrone auf %s!") % lane_text
_risk.text = "Kopfgeld für die Verteidiger: %s Gold." % LuneUiTheme.number(boss.bounty)
if boss.has("hp"):
_bar.visible = true
_bar.fraction = float(boss.hp) / maxf(1.0, float(boss.max_hp))
_bar.fill = LuneUiTheme.team_color(owner)
"defeated":
_badge.set_state("SCHLUNDKRONE BESIEGT", LuneUiTheme.GOOD if not own else LuneUiTheme.MUTED, "check")
_line.text = "Das Kopfgeld von %s Gold geht an %s." % [LuneUiTheme.number(boss.bounty), "den Gegner" if own else "dein Team"]
_:
var left: int = int(boss.rounds_left)
_badge.set_state("COOLDOWN – %d %s" % [left, "RUNDE" if left == 1 else "RUNDEN"], LuneUiTheme.MUTED, "clock")
_line.text = "Grotte wieder offen ab Welle %d." % int(boss.next_round)
_risk.visible = not _risk.text.is_empty() and not compact
_line.visible = not compact
_owner.visible = _owner.visible and not compact
if compact:
_choice.visible = false
if LuneTooltip.current != null:
LuneTooltip.current.refresh(self)
## Live boss HP during its attack, without rebuilding the texts.
func set_boss_hp(fraction: float) -> void:
if _bar.visible:
_bar.fraction = fraction
func _tip() -> Dictionary:
return {"title": "Bossgrotte · Schlundkrone", "glyph": "crown", "glyph_color": LuneUiTheme.TEAM[2],
"text": "Expeditionen kämpfen zuerst gegeneinander; der Boss erwacht, wenn höchstens ein Team übrig ist. Sieg: Der Boss greift in der Folgewelle für die Sieger an, die Verteidiger erhalten bei seinem Fall ein hohes Kopfgeld. Danach ist die Grotte %d Runden gesperrt." % LuneContent.BOSS_LOCK_ROUNDS,
"note": "Klick oder G: Kamera zur Grotte"}