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>
119 lines
6.1 KiB
GDScript
119 lines
6.1 KiB
GDScript
class_name LuneChampionPanel
|
||
extends HBoxContainer
|
||
## Attack champions: target lane, the three champion cards (copper, hatched, "1 WELLE") and
|
||
## the own bookings ("Einsatz in Welle 15 – MID"). Champions are bought in planning n, fight
|
||
## once in wave n+1 and then vanish; they never join the permanent army.
|
||
|
||
signal command_requested(command: Dictionary)
|
||
|
||
var target_lane: String = "mid"
|
||
var cards: Dictionary = {}
|
||
var _lane_buttons: Dictionary = {}
|
||
var _bookings: VBoxContainer
|
||
var _count: Label
|
||
var _signature: String = ""
|
||
|
||
func _init() -> void:
|
||
add_theme_constant_override("separation", 10)
|
||
var lanes := VBoxContainer.new()
|
||
lanes.add_theme_constant_override("separation", 4)
|
||
add_child(lanes)
|
||
LuneUiTheme.label("ZIEL", "HudCaps", lanes)
|
||
for lane: String in LuneMap.LANES:
|
||
var button := LuneUiTheme.button(LuneMap.LANE_NAMES[lane].to_upper(), "HudToggle", lanes)
|
||
button.toggle_mode = true
|
||
button.custom_minimum_size = Vector2(86, 0)
|
||
button.set_pressed_no_signal(lane == target_lane)
|
||
button.pressed.connect(func() -> void:
|
||
target_lane = lane
|
||
_signature = ""
|
||
for other: String in _lane_buttons:
|
||
(_lane_buttons[other] as Button).set_pressed_no_signal(other == lane))
|
||
_lane_buttons[lane] = button
|
||
LuneTooltip.attach(button, func() -> Dictionary: return {"title": "Ziel-Lane %s" % LuneMap.LANE_NAMES[lane], "text": "Gebuchte Champions greifen in der nächsten Welle auf dieser Lane an."})
|
||
var row := HBoxContainer.new()
|
||
row.add_theme_constant_override("separation", 6)
|
||
add_child(row)
|
||
for champion_id: String in LuneContent.CHAMPIONS:
|
||
var spec: Dictionary = LuneContent.CHAMPIONS[champion_id]
|
||
var card := LuneUnitCard.new()
|
||
row.add_child(card)
|
||
var role_key: String = {"ch_schutz": "schutz", "ch_druck": "druck", "ch_belagerung": "siege"}.get(champion_id, "melee")
|
||
card.setup(champion_id, str(spec.model), str(spec.name), role_key, int(spec.cost), "", true)
|
||
card.pressed.connect(func() -> void: command_requested.emit({"type": "champion_book", "champion_id": champion_id, "lane": target_lane}))
|
||
cards[champion_id] = card
|
||
LuneTooltip.attach(card, _tip.bind(champion_id))
|
||
var right := VBoxContainer.new()
|
||
right.add_theme_constant_override("separation", 4)
|
||
right.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
right.custom_minimum_size.x = 200
|
||
add_child(right)
|
||
var head := HBoxContainer.new()
|
||
right.add_child(head)
|
||
LuneUiTheme.label("EINSÄTZE", "HudCaps", head).size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_count = LuneUiTheme.label("", "HudMuted", head)
|
||
_bookings = VBoxContainer.new()
|
||
_bookings.add_theme_constant_override("separation", 3)
|
||
right.add_child(_bookings)
|
||
|
||
var _model: LuneHudModel
|
||
|
||
func apply(model: LuneHudModel) -> void:
|
||
_model = model
|
||
var bookings: Array = model.own_bookings()
|
||
var fresh: int = (model.me().draft.new_bookings as Array).size() if model.planning() else 0
|
||
var gold: int = model.own_gold()
|
||
var signature: String = "%s|%d|%s|%s|%s|%d" % [str(bookings), gold, model.planning(), model.locked(), target_lane, model.state.round_number]
|
||
if signature == _signature:
|
||
return
|
||
_signature = signature
|
||
var can_act: bool = model.planning() and not model.locked()
|
||
for champion_id: String in cards:
|
||
var card: LuneUnitCard = cards[champion_id]
|
||
var cost: int = int(LuneContent.CHAMPIONS[champion_id].cost)
|
||
var why: String = ""
|
||
if not model.planning():
|
||
why = "Buchungen nur in der Planung."
|
||
elif model.locked():
|
||
why = "Bereit gemeldet – die Planung ist verbindlich."
|
||
elif fresh >= LuneContent.MAX_BOOKINGS:
|
||
why = "Höchstens %d Champions pro Runde." % LuneContent.MAX_BOOKINGS
|
||
elif gold < cost:
|
||
why = "Es fehlen %d Gold." % (cost - gold)
|
||
card.set_available(why.is_empty(), why, not can_act, gold < cost)
|
||
_count.text = "%d/%d diese Runde" % [fresh, LuneContent.MAX_BOOKINGS]
|
||
for child: Node in _bookings.get_children():
|
||
child.free()
|
||
if bookings.is_empty():
|
||
var hint := LuneUiTheme.label("Keine Champions gebucht. Kauf jetzt – Einsatz in Welle %d." % (model.state.round_number + (1 if model.planning() else 2)), "HudMuted", _bookings)
|
||
hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
return
|
||
for booking: Dictionary in bookings:
|
||
var line := HBoxContainer.new()
|
||
line.add_theme_constant_override("separation", 6)
|
||
_bookings.add_child(line)
|
||
LuneGlyph.make("hourglass", LuneUiTheme.CHAMPION, 14.0, line)
|
||
var running: bool = int(booking.deploy_round) == model.state.round_number
|
||
var texts := VBoxContainer.new()
|
||
texts.add_theme_constant_override("separation", -2)
|
||
texts.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
line.add_child(texts)
|
||
LuneUiTheme.label(str(LuneContent.CHAMPIONS[booking.champion_id].name), "HudText", texts)
|
||
LuneUiTheme.label("Einsatz in Welle %d – %s%s" % [int(booking.deploy_round), LuneMap.LANE_NAMES[booking.lane].to_upper(), " (jetzt)" if running else ""], "HudMuted", texts)
|
||
if bool(booking.get("fresh", false)):
|
||
var cancel := LuneUiTheme.button("×", "HudIconButton", line)
|
||
cancel.disabled = not can_act
|
||
var uid: String = str(booking.uid)
|
||
cancel.pressed.connect(func() -> void: command_requested.emit({"type": "champion_cancel", "uid": uid}))
|
||
LuneTooltip.attach(cancel, func() -> Dictionary: return {"title": "Buchung zurücknehmen", "text": "Volle Erstattung, solange die Planung läuft."})
|
||
|
||
func _tip(champion_id: String) -> Dictionary:
|
||
var spec: Dictionary = LuneContent.CHAMPIONS[champion_id]
|
||
var round_number: int = _model.state.round_number + 1 if _model != null else 2
|
||
var definition: Dictionary = LuneContent.champion_definition(champion_id, round_number, _model.state.balance if _model != null else LuneBalance.default_balance())
|
||
var card: LuneUnitCard = cards[champion_id]
|
||
return {"title": "%s · Angriffschampion" % spec.name, "glyph": "champion", "glyph_color": LuneUiTheme.CHAMPION,
|
||
"subtitle": "%s · %d Gold · Einsatz in Welle %d – %s" % [spec.role, int(spec.cost), round_number, LuneMap.LANE_NAMES[target_lane].to_upper()],
|
||
"stats": LuneStats.rows(definition), "text": str(spec.text),
|
||
"note": "Einmaliger Einsatz: kämpft eine Welle lang und verschwindet danach. Die Ankündigung ist für alle sichtbar; sein Tod bringt dem Gegner %d Gold." % int(definition.bounty),
|
||
"warn": card.reason}
|