Hauptszene scenes/luneraria.tscn, Saumwacht bleibt über --legacy und die alten QA-Schalter erreichbar. Karte aus Bestandsassets plus neuem Turm, HUD mit Planung, Lanes und Minimap, Menü umbenannt, Einstellungen werden aus dem alten Benutzerordner übernommen. Simulation 6,6x schneller (Raster, Caches, faire Staffelung) und exakt spiegelsymmetrisch. Regeln 91/91, echte Anwendung 38/38, Legacy unverändert grün. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
424 lines
19 KiB
GDScript
424 lines
19 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 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 = "@"
|
||
|
||
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)
|
||
|
||
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 = -440
|
||
_plan_panel.offset_right = -16
|
||
_plan_panel.offset_top = 112
|
||
_plan_panel.offset_bottom = -270
|
||
var column := VBoxContainer.new()
|
||
column.add_theme_constant_override("separation", 8)
|
||
_plan_panel.add_child(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)
|
||
var actions := HBoxContainer.new()
|
||
column.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 ›", column, 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 = -440
|
||
_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 · Tab: Lanes · Minimap klicken"
|
||
_toast.remove_theme_color_override("font_color")
|
||
if match_state != null:
|
||
refresh()
|
||
|
||
func refresh() -> void:
|
||
var player: Dictionary = match_state.players[human_slot]
|
||
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 _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:
|
||
var lane_button := Button.new()
|
||
lane_button.text = 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.tooltip_text = "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"
|