Top windet sich über das Hochland, Mid quert die Kluft über die Mittelplattform, Bottom führt durch das Tiefland über die Deltabrücke; Top und Bottom sind exakt gleich lang, alles bleibt spiegelsymmetrisch. LuneTerrain liefert ein exakt gespiegeltes Höhenfeld, auf dem Figuren stehen. Bossgrotte als Terrasse auf der Achse zwischen Top und Mid, Aufmarschbereiche je Lane, perspektivische Kamera mit Sprungtasten (F2–F4, B, G, H, O, R, Q/E). Bestandsassets wiederverwendet, neue Formen als Blockout für G2/G4. Geprüft: verify_g1 443/443, Regeln 91/91, M2 73/73, verify_m1 38/38, verify_m2 14/14, Netz 43/43, Loopback 12/12, Balance 40 Partien (Seitentausch 20/20), Legacy 209/463. Vorher/Nachher-Aufnahmen und Messungen in G1-KARTE.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
484 lines
22 KiB
GDScript
484 lines
22 KiB
GDScript
extends Control
|
||
## Champions of Luneraria HUD. Presentation only: it reads LuneMatch and sends the human
|
||
## player's commands through the owning LuneGame; UI state never creates ownership.
|
||
|
||
signal command_requested(command: Dictionary)
|
||
signal undo_requested
|
||
signal ready_requested
|
||
signal starter_requested
|
||
signal speed_requested(value: int)
|
||
signal pause_requested
|
||
signal lane_focus_requested(lane: String)
|
||
signal map_focus_requested(point: Vector3)
|
||
signal menu_requested
|
||
signal rematch_requested
|
||
signal settings_requested
|
||
|
||
const Minimap = preload("res://scripts/luneraria/ui/lune_minimap.gd")
|
||
const HeroPanel = preload("res://scripts/luneraria/ui/lune_hero_panel.gd")
|
||
const ReinforcePanel = preload("res://scripts/luneraria/ui/lune_reinforce_panel.gd")
|
||
const TEAM_COLORS: Array[Color] = [Color("5aa8ff"), Color("ff6a55")]
|
||
const TEAM_NAMES: Array[String] = ["Blau", "Rot"]
|
||
const ROLE_NAMES: Dictionary = {"front": "Front", "melee": "Nahkampf", "ranged": "Fernkampf", "splash": "Fläche", "support": "Unterstützung"}
|
||
|
||
var match_state: LuneMatch
|
||
var human_slot: int = 1
|
||
var minimap: Control
|
||
var _phase: Label
|
||
var _round: Label
|
||
var _core_bars: Array[ProgressBar] = []
|
||
var _core_labels: Array[Label] = []
|
||
var _gold: Label
|
||
var _income: Label
|
||
var _identity: Label
|
||
var _pause: Button
|
||
var _speed: Button
|
||
var _lane_rows: Dictionary = {}
|
||
var _team_list: Label
|
||
var _plan_panel: PanelContainer
|
||
var _shop: GridContainer
|
||
var _shop_buttons: Dictionary = {}
|
||
var _roster: VBoxContainer
|
||
var _roster_title: Label
|
||
var _ready_button: Button
|
||
var _starter: Button
|
||
var _undo: Button
|
||
var _combat_panel: PanelContainer
|
||
var _combat_info: Label
|
||
var _report: Label
|
||
var _toast: Label
|
||
var _toast_left: float = 0.0
|
||
var _over: Control
|
||
var _over_title: Label
|
||
var _over_body: Label
|
||
var _roster_signature: String = "@"
|
||
var hero_panel: VBoxContainer
|
||
var reinforce_panel: VBoxContainer
|
||
var _pages: Array[Control] = []
|
||
var _tabs: ChoiceGroup
|
||
var _boss_status: Label
|
||
|
||
func _ready() -> void:
|
||
theme = SaumwachtTheme.shared()
|
||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_top_bar()
|
||
_lane_panel()
|
||
_plan_side()
|
||
_bottom()
|
||
_game_over()
|
||
|
||
func _panel(variation: String = "CaptionPanel") -> PanelContainer:
|
||
var panel := PanelContainer.new()
|
||
panel.theme_type_variation = variation
|
||
panel.mouse_filter = Control.MOUSE_FILTER_STOP
|
||
add_child(panel)
|
||
return panel
|
||
|
||
func _label(text_value: String, variation: String, parent: Node, size: int = 0) -> Label:
|
||
var label := Label.new()
|
||
label.text = text_value
|
||
label.theme_type_variation = variation
|
||
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
if size > 0:
|
||
label.add_theme_font_size_override("font_size", size)
|
||
parent.add_child(label)
|
||
return label
|
||
|
||
func _button(text_value: String, parent: Node, action: Callable, variation: String = "Button") -> Button:
|
||
var button := Button.new()
|
||
button.text = text_value
|
||
button.theme_type_variation = variation
|
||
button.focus_mode = Control.FOCUS_ALL
|
||
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||
button.pressed.connect(action)
|
||
parent.add_child(button)
|
||
return button
|
||
|
||
func _bar(color: Color, parent: Node) -> ProgressBar:
|
||
var bar := ProgressBar.new()
|
||
bar.show_percentage = false
|
||
bar.custom_minimum_size = Vector2(190, 10)
|
||
var back := StyleBoxFlat.new()
|
||
back.bg_color = Color(0, 0, 0, .55)
|
||
back.border_color = Color("5c4a33")
|
||
back.set_border_width_all(1)
|
||
var fill := StyleBoxFlat.new()
|
||
fill.bg_color = color
|
||
bar.add_theme_stylebox_override("background", back)
|
||
bar.add_theme_stylebox_override("fill", fill)
|
||
parent.add_child(bar)
|
||
return bar
|
||
|
||
func _top_bar() -> void:
|
||
var top := _panel()
|
||
top.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE)
|
||
top.offset_left = 16
|
||
top.offset_right = -16
|
||
top.offset_top = 12
|
||
var row := HBoxContainer.new()
|
||
row.add_theme_constant_override("separation", 22)
|
||
top.add_child(row)
|
||
var title := VBoxContainer.new()
|
||
title.add_theme_constant_override("separation", 0)
|
||
row.add_child(title)
|
||
_label("CHAMPIONS OF LUNERARIA", "EyebrowLabel", title)
|
||
_phase = _label("PLANUNG", "SectionLabel", title, 20)
|
||
_round = _label("Welle 1", "MutedLabel", title)
|
||
for team: int in range(2):
|
||
var box := VBoxContainer.new()
|
||
box.add_theme_constant_override("separation", 3)
|
||
row.add_child(box)
|
||
_core_labels.append(_label("Ancient %s" % TEAM_NAMES[team], "MutedLabel", box, 15))
|
||
_core_bars.append(_bar(TEAM_COLORS[team], box))
|
||
var economy := VBoxContainer.new()
|
||
economy.add_theme_constant_override("separation", 0)
|
||
row.add_child(economy)
|
||
_gold = _label("0 Gold", "CardTitleLabel", economy, 24)
|
||
_income = _label("", "MutedLabel", economy, 15)
|
||
_identity = _label("", "MutedLabel", row, 15)
|
||
_identity.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_identity.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||
_pause = _button("Pause · P", row, func() -> void: pause_requested.emit())
|
||
_speed = _button("1×", row, func() -> void: speed_requested.emit(0))
|
||
_button("Optionen · F1", row, func() -> void: settings_requested.emit())
|
||
_button("Menü", row, func() -> void: menu_requested.emit(), "QuietButton")
|
||
|
||
func _lane_panel() -> void:
|
||
var panel := _panel()
|
||
panel.position = Vector2(16, 112)
|
||
panel.custom_minimum_size = Vector2(300, 0)
|
||
var column := VBoxContainer.new()
|
||
column.add_theme_constant_override("separation", 6)
|
||
panel.add_child(column)
|
||
_label("LANES · TÜRME", "SectionLabel", column)
|
||
for lane: String in LuneMap.LANES:
|
||
var button := _button("", column, func() -> void: lane_focus_requested.emit(lane), "ChoiceButton")
|
||
button.custom_minimum_size = Vector2(0, 52)
|
||
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||
button.tooltip_text = "Kamera auf %s" % LuneMap.LANE_NAMES[lane]
|
||
_lane_rows[lane] = button
|
||
_team_list = _label("", "MutedLabel", column, 14)
|
||
_boss_status = _label("", "Label", column, 15)
|
||
_boss_status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
_boss_status.custom_minimum_size.x = 264
|
||
|
||
func _plan_side() -> void:
|
||
_plan_panel = _panel("MenuPanel")
|
||
_plan_panel.anchor_left = 1.0
|
||
_plan_panel.anchor_right = 1.0
|
||
_plan_panel.anchor_bottom = 1.0
|
||
_plan_panel.offset_left = -500
|
||
_plan_panel.offset_right = -16
|
||
_plan_panel.offset_top = 112
|
||
_plan_panel.offset_bottom = -270
|
||
var outer := VBoxContainer.new()
|
||
outer.add_theme_constant_override("separation", 8)
|
||
_plan_panel.add_child(outer)
|
||
_tabs = ChoiceGroup.new(true)
|
||
_tabs.name = "PlanTabs"
|
||
_tabs.add_option("Truppen")
|
||
_tabs.add_option("Held")
|
||
_tabs.add_option("Verstärkung")
|
||
outer.add_child(_tabs)
|
||
_tabs.item_selected.connect(show_page)
|
||
var column := VBoxContainer.new()
|
||
column.add_theme_constant_override("separation", 8)
|
||
column.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
outer.add_child(column)
|
||
_pages.append(column)
|
||
_label("TRUPPEN KAUFEN · 1–5", "SectionLabel", column)
|
||
_shop = GridContainer.new()
|
||
_shop.columns = 5
|
||
_shop.add_theme_constant_override("h_separation", 6)
|
||
column.add_child(_shop)
|
||
_roster_title = _label("DEINE ARMEE", "SectionLabel", column)
|
||
var scroll := ScrollContainer.new()
|
||
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||
column.add_child(scroll)
|
||
_roster = VBoxContainer.new()
|
||
_roster.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_roster.add_theme_constant_override("separation", 4)
|
||
scroll.add_child(_roster)
|
||
for index: int in range(2):
|
||
var page := ScrollContainer.new()
|
||
page.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
page.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||
page.visible = false
|
||
outer.add_child(page)
|
||
_pages.append(page)
|
||
var panel: VBoxContainer = HeroPanel.new() if index == 0 else ReinforcePanel.new()
|
||
panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
page.add_child(panel)
|
||
panel.command_requested.connect(func(command: Dictionary) -> void: command_requested.emit(command))
|
||
if index == 0:
|
||
hero_panel = panel
|
||
else:
|
||
reinforce_panel = panel
|
||
_tabs.select(0)
|
||
var actions := HBoxContainer.new()
|
||
outer.add_child(actions)
|
||
_starter = _button("Startaufstellung", actions, func() -> void: starter_requested.emit())
|
||
_starter.tooltip_text = "Kauft Front, Nahkampf und Fernkampf deines Bundes für deine Lane."
|
||
_undo = _button("Rückgängig", actions, func() -> void: undo_requested.emit())
|
||
_ready_button = _button("BEREIT · KAMPF ›", outer, func() -> void: ready_requested.emit(), "PrimaryButton")
|
||
_ready_button.custom_minimum_size.y = 58
|
||
_ready_button.tooltip_text = "Leertaste · Befehle werden jetzt verbindlich übernommen."
|
||
_combat_panel = _panel()
|
||
_combat_panel.anchor_left = 1.0
|
||
_combat_panel.anchor_right = 1.0
|
||
_combat_panel.offset_left = -500
|
||
_combat_panel.offset_right = -16
|
||
_combat_panel.offset_top = 112
|
||
_combat_info = _label("", "Label", _combat_panel, 16)
|
||
|
||
func _bottom() -> void:
|
||
var report_panel := _panel()
|
||
report_panel.anchor_top = 1.0
|
||
report_panel.anchor_bottom = 1.0
|
||
report_panel.offset_left = 16
|
||
report_panel.offset_top = -120
|
||
report_panel.offset_bottom = -16
|
||
report_panel.offset_right = 760
|
||
var column := VBoxContainer.new()
|
||
report_panel.add_child(column)
|
||
_report = _label("Planung: Kaufe Truppen für deine Lane und drücke Bereit.", "Label", column, 16)
|
||
_report.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
_toast = _label("", "MutedLabel", column, 15)
|
||
var map_panel := _panel()
|
||
map_panel.anchor_left = 1.0
|
||
map_panel.anchor_right = 1.0
|
||
map_panel.anchor_top = 1.0
|
||
map_panel.anchor_bottom = 1.0
|
||
map_panel.offset_left = -270
|
||
map_panel.offset_right = -16
|
||
map_panel.offset_top = -258
|
||
map_panel.offset_bottom = -16
|
||
minimap = Minimap.new()
|
||
map_panel.add_child(minimap)
|
||
minimap.focus_requested.connect(func(point: Vector3) -> void: map_focus_requested.emit(point))
|
||
|
||
func _game_over() -> void:
|
||
_over = ColorRect.new()
|
||
(_over as ColorRect).color = Color(.02, .03, .04, .82)
|
||
_over.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
_over.mouse_filter = Control.MOUSE_FILTER_STOP
|
||
_over.visible = false
|
||
add_child(_over)
|
||
var center := CenterContainer.new()
|
||
center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
_over.add_child(center)
|
||
var panel := PanelContainer.new()
|
||
panel.theme_type_variation = "MenuPanel"
|
||
panel.custom_minimum_size = Vector2(620, 0)
|
||
center.add_child(panel)
|
||
var column := VBoxContainer.new()
|
||
column.add_theme_constant_override("separation", 14)
|
||
panel.add_child(column)
|
||
_over_title = _label("", "TitleLabel", column, 60)
|
||
_over_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
_over_body = _label("", "BodyLabel", column)
|
||
_over_body.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
_over_body.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
var row := HBoxContainer.new()
|
||
row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||
row.add_theme_constant_override("separation", 12)
|
||
column.add_child(row)
|
||
_button("NEUE PARTIE", row, func() -> void: rematch_requested.emit(), "PrimaryButton")
|
||
_button("Hauptmenü", row, func() -> void: menu_requested.emit())
|
||
|
||
# ---------------------------------------------------------------- refresh
|
||
func notify(message: String, error: bool = false) -> void:
|
||
_toast.text = message
|
||
_toast.add_theme_color_override("font_color", Color("f0a888") if error else Color("9fe0c8"))
|
||
_toast_left = 5.0
|
||
|
||
func _process(delta: float) -> void:
|
||
_toast_left -= delta
|
||
if _toast_left <= 0.0:
|
||
_toast.text = "WASD / mittlere Maustaste: Kamera · Mausrad: Zoom · F2–F4: Top/Mid/Bottom · B Basis · G Grotte · H Held · O Karte · R Zurück"
|
||
_toast.remove_theme_color_override("font_color")
|
||
if match_state != null:
|
||
refresh()
|
||
|
||
func show_page(index: int) -> void:
|
||
_tabs.select(index)
|
||
for page_index: int in range(_pages.size()):
|
||
_pages[page_index].visible = page_index == index
|
||
|
||
func refresh() -> void:
|
||
var player: Dictionary = match_state.players[human_slot]
|
||
if hero_panel.match_state != match_state:
|
||
hero_panel.match_state = match_state
|
||
hero_panel.slot = human_slot
|
||
reinforce_panel.match_state = match_state
|
||
reinforce_panel.slot = human_slot
|
||
_boss_status.text = _grotto_text()
|
||
var planning: bool = match_state.phase == LuneMatch.Phase.PLANNING
|
||
var combat: bool = match_state.phase == LuneMatch.Phase.COMBAT
|
||
var phase_name: String = "PLANUNG" if planning else ("KAMPF" if combat else "PARTIE BEENDET")
|
||
_phase.text = phase_name
|
||
_round.text = "Welle %d%s" % [match_state.round_number, (" · %02d:%02d" % [int(match_state.combat.elapsed) / 60, int(match_state.combat.elapsed) % 60]) if combat else ""]
|
||
for team: int in range(2):
|
||
var hp: float = match_state.core_hp(team)
|
||
if combat and match_state.combat.buildings.has("%s_ancient" % ("blau" if team == 0 else "rot")):
|
||
hp = (match_state.combat.buildings["%s_ancient" % ("blau" if team == 0 else "rot")] as LuneUnit).hp
|
||
_core_bars[team].max_value = match_state.balance.core_hp
|
||
_core_bars[team].value = hp
|
||
_core_labels[team].text = "Ancient %s · %d" % [TEAM_NAMES[team], ceili(hp)]
|
||
_gold.text = "%d Gold" % match_state.gold_of(human_slot)
|
||
_income.text = "+%d pro Runde · Armee %d / %d" % [match_state.balance.round_income(match_state.round_number), (player.draft.roster as Array).size() if planning else (player.roster as Array).size(), match_state.balance.supply_cap]
|
||
_identity.text = "%s · %s-Lane · %s" % [TEAM_NAMES[int(player.team)], LuneMap.LANE_NAMES[player.lane], DataCatalog.FACTION_NAMES[player.bund]]
|
||
_pause.visible = combat
|
||
_speed.visible = combat
|
||
for lane: String in LuneMap.LANES:
|
||
var button: Button = _lane_rows[lane]
|
||
var own: String = "●".repeat(match_state.towers_alive(0, lane, true)) + "○".repeat(2 - match_state.towers_alive(0, lane, true))
|
||
var rival: String = "●".repeat(match_state.towers_alive(1, lane, true)) + "○".repeat(2 - match_state.towers_alive(1, lane, true))
|
||
var blue_owner: String = match_state.players[match_state.slot_of(0, lane)].name
|
||
var red_owner: String = match_state.players[match_state.slot_of(1, lane)].name
|
||
button.text = "%s Blau %s Rot %s\n%s · %s" % [LuneMap.LANE_NAMES[lane].to_upper(), own, rival, blue_owner, red_owner]
|
||
var team_lines: PackedStringArray = []
|
||
for team: int in range(2):
|
||
var names: PackedStringArray = []
|
||
for slot: int in range(team * 3, team * 3 + 3):
|
||
names.append(match_state.players[slot].name)
|
||
team_lines.append("%s: %s · Basistürme %d/2" % [TEAM_NAMES[team], ", ".join(names), int(match_state.building_alive_now("%s_base_1" % ("blau" if team == 0 else "rot"))) + int(match_state.building_alive_now("%s_base_2" % ("blau" if team == 0 else "rot")))])
|
||
_team_list.text = "\n".join(team_lines)
|
||
_plan_panel.visible = planning
|
||
_combat_panel.visible = combat
|
||
if planning:
|
||
_refresh_plan(player)
|
||
elif combat:
|
||
var lines: PackedStringArray = ["KAMPF LÄUFT · automatisch"]
|
||
for lane: String in LuneMap.LANES:
|
||
var counts: Array[int] = [0, 0]
|
||
for unit: LuneUnit in match_state.combat.units:
|
||
if unit.alive and unit.is_mobile() and unit.lane == lane:
|
||
counts[unit.team] += 1
|
||
lines.append("%s Blau %d · Rot %d Einheiten" % [LuneMap.LANE_NAMES[lane], counts[0], counts[1]])
|
||
lines.append("Leertaste/P: Pause · Tempo 1×/2×/4×")
|
||
_combat_info.text = "\n".join(lines)
|
||
if combat:
|
||
_report.text = "Welle %d läuft. Armeen und Monsterwellen rücken über alle drei Lanes vor; Türme fallen in fester Reihenfolge." % match_state.round_number
|
||
elif planning and match_state.last_settlement.is_empty():
|
||
_report.text = "Planung: Kaufe Truppen für deine Lane, teile sie auf Top, Mid oder Bottom ein und drücke Bereit."
|
||
if not match_state.last_settlement.is_empty() and planning:
|
||
var s: Dictionary = match_state.last_settlement
|
||
var lost: PackedStringArray = []
|
||
for id: String in s.buildings_lost:
|
||
lost.append(_building_name(id))
|
||
_report.text = "Welle %d abgeschlossen (%s, %ds) · dein Ertrag +%d Gold (Kills %d, Einkommen %d)%s" % [s.round, _reason(s.end_reason), int(s.combat_time), int(s.gains[human_slot]) + int(s.income), int(s.gains[human_slot]), int(s.income), ("\nGefallen: " + ", ".join(lost)) if not lost.is_empty() else ""]
|
||
_over.visible = match_state.phase == LuneMatch.Phase.OVER
|
||
if _over.visible:
|
||
var own_team: int = int(player.team)
|
||
_over_title.text = "UNENTSCHIEDEN" if match_state.winner == LuneMatch.DRAW else ("SIEG" if match_state.winner == own_team else "NIEDERLAGE")
|
||
_over_body.text = "%s\nNach %d Wellen." % [match_state.end_note, match_state.round_number]
|
||
minimap.match_state = match_state
|
||
minimap.queue_redraw()
|
||
|
||
func _grotto_text() -> String:
|
||
var claim: Dictionary = match_state.boss.claim
|
||
if not claim.is_empty():
|
||
var team: String = TEAM_NAMES[int(claim.team)]
|
||
return "BOSSGROTTE · von %s erobert (Runde %d)\nDer Boss greift in Runde %d für %s über %s an · Grotte wieder ab Runde %d" % [team, int(claim.won_round), int(claim.deploy_round), team, LuneMap.LANE_NAMES[claim.lane], int(match_state.boss.next_available_round)]
|
||
if match_state.grotto_available():
|
||
return "BOSSGROTTE · verfügbar\nEinheiten mit „G“ entsenden; sie fehlen ihrer Lane die ganze Kampfphase."
|
||
return "BOSSGROTTE · gesperrt bis Runde %d" % int(match_state.boss.next_available_round)
|
||
|
||
func _reason(reason: String) -> String:
|
||
return {"cleared": "alle Einheiten gefallen", "timeout": "Zeitfenster abgelaufen", "core": "Ancient zerstört"}.get(reason, reason)
|
||
|
||
func _building_name(id: String) -> String:
|
||
var parts: PackedStringArray = id.split("_")
|
||
var team: String = "Blau" if parts[0] == "blau" else "Rot"
|
||
if parts[1] == "ancient":
|
||
return "Ancient " + team
|
||
if parts[1] == "base":
|
||
return "Basisturm %s %s" % [team, parts[2]]
|
||
return "%s %s-%s" % [team, LuneMap.LANE_NAMES[parts[1]], "Außenturm" if parts[2] == "outer" else "Innenturm"]
|
||
|
||
func _refresh_plan(player: Dictionary) -> void:
|
||
var draft: Dictionary = player.draft
|
||
var ids: Array[String] = match_state.base_ids(player.bund)
|
||
if _shop_buttons.is_empty() or not _shop_buttons.has(ids[0]):
|
||
for child: Node in _shop.get_children():
|
||
child.free()
|
||
_shop_buttons.clear()
|
||
for index: int in range(ids.size()):
|
||
var id: String = ids[index]
|
||
var data: Dictionary = DataCatalog.get_unit(id)
|
||
var button := Button.new()
|
||
button.icon = AssetLibrary.icon(id)
|
||
button.expand_icon = true
|
||
button.custom_minimum_size = Vector2(76, 96)
|
||
button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
button.vertical_icon_alignment = VERTICAL_ALIGNMENT_TOP
|
||
button.text = "%d G" % int(data.cost)
|
||
button.focus_mode = Control.FOCUS_ALL
|
||
button.tooltip_text = "%s [%d]\n%s · %d HP · %d Schaden/s · %.1f m\n%s" % [data.display_name, index + 1, ROLE_NAMES[match_state.role_of(id)], data.max_hp, data.damage, data.attack_range, data.ability_description]
|
||
button.pressed.connect(func() -> void: command_requested.emit({"type": "buy", "type_id": id}))
|
||
_shop.add_child(button)
|
||
_shop_buttons[id] = button
|
||
for id: String in _shop_buttons:
|
||
var cost: int = int(DataCatalog.get_unit(id).cost)
|
||
(_shop_buttons[id] as Button).disabled = int(draft.gold) < cost or (draft.roster as Array).size() >= match_state.balance.supply_cap
|
||
_undo.disabled = (player.undo as Array).is_empty()
|
||
_starter.disabled = not (draft.roster as Array).is_empty()
|
||
_ready_button.disabled = bool(player.ready)
|
||
_ready_button.text = "BEREIT · KAMPF ›" if not player.ready else "WARTE AUF ANDERE …"
|
||
var signature: String = JSON.stringify(draft)
|
||
if signature == _roster_signature:
|
||
return
|
||
_roster_signature = signature
|
||
for child: Node in _roster.get_children():
|
||
child.free()
|
||
_roster_title.text = "DEINE ARMEE · %d / %d" % [(draft.roster as Array).size(), match_state.balance.supply_cap]
|
||
if (draft.roster as Array).is_empty():
|
||
_label("Noch keine Truppen. Kaufe oben oder nutze die Startaufstellung.", "MutedLabel", _roster, 15).autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
for entry: Dictionary in draft.roster:
|
||
var data: Dictionary = DataCatalog.get_unit(entry.type_id)
|
||
var row := HBoxContainer.new()
|
||
row.add_theme_constant_override("separation", 6)
|
||
_roster.add_child(row)
|
||
var icon := TextureRect.new()
|
||
icon.texture = AssetLibrary.icon(entry.type_id)
|
||
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||
icon.custom_minimum_size = Vector2(40, 40)
|
||
row.add_child(icon)
|
||
var name_label := _label(str(data.display_name), "Label", row, 15)
|
||
name_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
name_label.clip_text = true
|
||
for lane: String in LuneMap.LANES + [LuneMatch.GROTTO]:
|
||
var lane_button := Button.new()
|
||
var grotto: bool = lane == LuneMatch.GROTTO
|
||
lane_button.text = "G" if grotto else LuneMap.LANE_NAMES[lane].left(1)
|
||
lane_button.toggle_mode = true
|
||
lane_button.button_pressed = entry.lane == lane
|
||
lane_button.theme_type_variation = "ChoiceButton"
|
||
lane_button.custom_minimum_size = Vector2(34, 34)
|
||
lane_button.disabled = grotto and not match_state.grotto_available()
|
||
lane_button.tooltip_text = "In die Bossgrotte entsenden (fehlt der Lane die ganze Kampfphase)" if grotto else "Auf %s einteilen" % LuneMap.LANE_NAMES[lane]
|
||
var uid: String = str(entry.uid)
|
||
lane_button.pressed.connect(func() -> void: command_requested.emit({"type": "assign", "uid": uid, "lane": lane}))
|
||
row.add_child(lane_button)
|
||
var upgrade_id: String = str(data.get("upgrade_id", ""))
|
||
var uid_value: String = str(entry.uid)
|
||
var up := Button.new()
|
||
up.text = "↑ %d" % int(DataCatalog.get_unit(upgrade_id).cost) if not upgrade_id.is_empty() else "max"
|
||
up.disabled = upgrade_id.is_empty() or int(draft.gold) < int(DataCatalog.get_unit(upgrade_id).get("cost", 0))
|
||
up.tooltip_text = ("Aufwerten zu %s" % DataCatalog.get_unit(upgrade_id).display_name) if not upgrade_id.is_empty() else "Vollständig aufgewertet"
|
||
up.pressed.connect(func() -> void: command_requested.emit({"type": "upgrade", "uid": uid_value}))
|
||
row.add_child(up)
|
||
var sell := Button.new()
|
||
var refund: int = int(entry.invested) if bool(entry.get("fresh", false)) else floori(float(entry.invested) * match_state.balance.sell_refund)
|
||
sell.text = "×"
|
||
sell.tooltip_text = "Zurückgeben für %d Gold" % refund
|
||
sell.pressed.connect(func() -> void: command_requested.emit({"type": "sell", "uid": uid_value}))
|
||
row.add_child(sell)
|
||
|
||
func set_speed_label(value: int, paused: bool) -> void:
|
||
_speed.text = "%d×" % value
|
||
_pause.text = "Weiter · P" if paused else "Pause · P"
|