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>
166 lines
7.4 KiB
GDScript
166 lines
7.4 KiB
GDScript
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"}
|