Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore unversioniert; letztere sind unter ../backups gesichert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
894 lines
38 KiB
GDScript
894 lines
38 KiB
GDScript
extends Control
|
||
## Presentation only: emits player commands and reads snapshots.
|
||
|
||
signal command_requested(command: Dictionary)
|
||
signal build_selected(type_id: String)
|
||
signal move_requested
|
||
signal base_requested
|
||
signal focus_requested
|
||
signal menu_requested
|
||
signal new_match_requested
|
||
signal starter_requested
|
||
signal cancel_requested
|
||
signal ready_requested
|
||
|
||
const INK := Color("#0b1016")
|
||
const PANEL := Color("#101a22")
|
||
const TEXT := Color("#f2f6f4")
|
||
const MUTED := Color("#9bb0aa")
|
||
const GOLD := Color("#f0d090")
|
||
const GOLD_DEEP := Color("#c9a05a")
|
||
const TEAL := Color("#7fe8c8")
|
||
const TEAL_DEEP := Color("#2a8f7a")
|
||
const CORAL := Color("#f0a888")
|
||
const VIOLET := Color("#b8a0e8")
|
||
|
||
var selected_uid: int = -1
|
||
var build_type: String = ""
|
||
var viewed_team: int = 0
|
||
var model_inspector: ColorRect
|
||
var _inspect_model: Button
|
||
var settings_panel: ColorRect
|
||
var _settings_paused: bool = false
|
||
var _settings_focus: Control
|
||
var _volume_slider: HSlider
|
||
var _mute_check: CheckButton
|
||
var _follow_check: CheckButton
|
||
var _fullscreen_check: CheckButton
|
||
var _camera_status: Label
|
||
var _boss_bar: ProgressBar
|
||
var _boss_label: Label
|
||
var _phase: Label
|
||
var _timer: Label
|
||
var _gold: Label
|
||
var _sparks: Label
|
||
var _income: Label
|
||
var _workers: Label
|
||
var _core_labels: Array = []
|
||
var _core_bars: Array = []
|
||
var _wave_title: Label
|
||
var _wave_body: Label
|
||
var _selection_icon: TextureRect
|
||
var _selection_types: HBoxContainer
|
||
var _selection_title: Label
|
||
var _selection_body: Label
|
||
var _upgrade: Button
|
||
var _sell: Button
|
||
var _move: Button
|
||
var _priority: OptionButton
|
||
var _worker_button: Button
|
||
var _ready_button: Button
|
||
var _pause_button: Button
|
||
var _speed_button: Button
|
||
var _front_button: Button
|
||
var _undo: Button
|
||
var _starter_button: Button
|
||
var _cards: HBoxContainer
|
||
var _shop_faction: String = ""
|
||
var _faction_label: Label
|
||
var _worker_info: Label
|
||
var _step_hint: Label
|
||
var _faction_pickers: Array = []
|
||
var _timed_picker: CheckButton
|
||
var _shop_buttons: Dictionary = {}
|
||
var _send_picker: OptionButton
|
||
var _send_button: Button
|
||
var _cancel_send: Button
|
||
var _send_info: Label
|
||
var _toast: Label
|
||
var _toast_left: float = 0.0
|
||
## Phase 20A: the title menu and match setup live in MainMenu. These names remain the
|
||
## entry points used by tools and tests; they point at OptionButton-compatible parts.
|
||
var main_menu: MainMenu
|
||
var _overlay: Control
|
||
var _overlay_title: Label
|
||
var _overlay_action: Button
|
||
var _ai_picker: ChoiceGroup
|
||
var _mode_picker: ChoiceGroup
|
||
var _reduced_check: CheckButton
|
||
var _summary: Label
|
||
var _queue_scroll: ScrollContainer
|
||
var _queue_row: HBoxContainer
|
||
var _queue_signature: String = "@"
|
||
var _refresh_left: float = 0.0
|
||
var _shop_panel: PanelContainer
|
||
var _wave_progress: ProgressBar
|
||
var _context_panel: PanelContainer
|
||
var _context_column: VBoxContainer
|
||
var _game_panels: Array[Control] = []
|
||
var _logo_label: Label
|
||
|
||
const SEND_SHORT_NAMES: Dictionary = {
|
||
"s01": "Späher", "s02": "Rost", "s03": "Dunst", "s04": "Keil", "s05": "Riss"}
|
||
const MATRIX_TOOLTIP: String = "Schadensmatrix (Zeile → Spalte):\nKlinge: 130 % gegen Leicht · 75 % gegen Schwer\nDurchschlag: 130 % gegen Schwer · 75 % gegen Ätherisch\nImpuls: 130 % gegen Ätherisch · 75 % gegen Leicht"
|
||
|
||
|
||
func _ready() -> void:
|
||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
theme = _make_theme()
|
||
_make_header()
|
||
_make_sidebar()
|
||
_make_footer()
|
||
_make_overlay()
|
||
_make_settings()
|
||
model_inspector = preload("res://scripts/ui/model_inspector.gd").new()
|
||
add_child(model_inspector)
|
||
GameState.phase_changed.connect(_phase_changed)
|
||
WaveManager.wave_settled.connect(_show_summary)
|
||
_phase_changed(GameState.phase)
|
||
|
||
|
||
func _style(color: Color, border: Color = Color.TRANSPARENT, width: int = 0) -> StyleBoxFlat:
|
||
var box := StyleBoxFlat.new()
|
||
box.bg_color = color
|
||
box.border_color = border
|
||
box.set_border_width_all(width)
|
||
box.set_corner_radius_all(14)
|
||
box.anti_aliasing = true
|
||
box.content_margin_left = 16
|
||
box.content_margin_right = 16
|
||
box.content_margin_top = 10
|
||
box.content_margin_bottom = 10
|
||
return box
|
||
|
||
|
||
func _glass(bg: Color = Color(.05, .09, .12, .88), border: Color = Color(.45, .85, .75, .22)) -> StyleBoxFlat:
|
||
var box := _style(bg, border, 1)
|
||
box.set_corner_radius_all(16)
|
||
box.shadow_color = Color(0, 0, 0, .45)
|
||
box.shadow_size = 14
|
||
box.shadow_offset = Vector2(0, 6)
|
||
box.content_margin_left = 18
|
||
box.content_margin_right = 18
|
||
box.content_margin_top = 12
|
||
box.content_margin_bottom = 12
|
||
return box
|
||
|
||
|
||
func _gold_button() -> StyleBoxFlat:
|
||
var box := _style(Color("#e8c57a"), Color("#ffe2a8"), 1)
|
||
box.set_corner_radius_all(12)
|
||
box.shadow_color = Color(.95, .75, .35, .35)
|
||
box.shadow_size = 16
|
||
box.shadow_offset = Vector2(0, 2)
|
||
box.content_margin_top = 12
|
||
box.content_margin_bottom = 12
|
||
return box
|
||
|
||
|
||
func _make_theme() -> Theme:
|
||
var result := Theme.new()
|
||
result.default_font_size = 15
|
||
var modern_font := FontVariation.new()
|
||
modern_font.base_font = preload("res://assets/fonts/Cantarell-VF.otf")
|
||
modern_font.variation_opentype = {"wght": 550.0}
|
||
result.default_font = modern_font
|
||
result.set_color("font_color", "Label", TEXT)
|
||
result.set_color("font_color", "Button", TEXT)
|
||
result.set_color("font_hover_color", "Button", Color.WHITE)
|
||
result.set_color("font_disabled_color", "Button", Color("#6a807c"))
|
||
result.set_stylebox("normal", "Button", _style(Color("#1c2c36"), Color("#3d5a58"), 1))
|
||
result.set_stylebox("hover", "Button", _style(Color("#284852"), TEAL, 1))
|
||
result.set_stylebox("pressed", "Button", _style(Color("#1f3c40"), TEAL_DEEP, 2))
|
||
result.set_stylebox("focus", "Button", _style(Color(0, 0, 0, 0), GOLD, 2))
|
||
result.set_stylebox("disabled", "Button", _style(Color("#152028")))
|
||
result.set_stylebox("normal", "PanelContainer", _glass())
|
||
result.set_constant("separation", "VBoxContainer", 10)
|
||
result.set_constant("separation", "HBoxContainer", 12)
|
||
result.set_color("font_color", "OptionButton", TEXT)
|
||
result.set_stylebox("normal", "OptionButton", _style(Color("#1c2c36"), Color("#3d5a58"), 1))
|
||
result.set_stylebox("hover", "OptionButton", _style(Color("#284852"), TEAL, 1))
|
||
result.set_stylebox("focus", "OptionButton", _style(Color("#284852"), GOLD, 1))
|
||
var popup := _glass(Color(.06, .10, .14, .97), Color(.4, .7, .65, .3))
|
||
result.set_stylebox("panel", "PopupMenu", popup)
|
||
result.set_color("font_color", "PopupMenu", TEXT)
|
||
result.set_color("font_hover_color", "PopupMenu", GOLD)
|
||
return result
|
||
|
||
|
||
func _label(text_value: String, size: int, parent: Node, color: Color = TEXT) -> Label:
|
||
var label := Label.new()
|
||
label.text = text_value
|
||
label.add_theme_font_size_override("font_size", size)
|
||
label.add_theme_color_override("font_color", color)
|
||
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
parent.add_child(label)
|
||
return label
|
||
|
||
|
||
func _button(text_value: String, action: Callable, parent: Node) -> Button:
|
||
var button := Button.new()
|
||
button.text = text_value
|
||
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||
button.pressed.connect(action)
|
||
parent.add_child(button)
|
||
return button
|
||
|
||
|
||
func _panel(parent: Node) -> PanelContainer:
|
||
var panel := PanelContainer.new()
|
||
parent.add_child(panel)
|
||
return panel
|
||
|
||
|
||
func _make_header() -> void:
|
||
var header := _panel(self)
|
||
_game_panels.append(header)
|
||
header.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE)
|
||
header.offset_left = 18
|
||
header.offset_top = 16
|
||
header.offset_right = -18
|
||
header.offset_bottom = 92
|
||
var row := HBoxContainer.new()
|
||
row.add_theme_constant_override("separation", 18)
|
||
header.add_child(row)
|
||
var title := VBoxContainer.new()
|
||
title.custom_minimum_size.x = 185
|
||
title.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||
row.add_child(title)
|
||
var logo := _label("S A U M W A C H T", 22, title, Color("#f6e6b8"))
|
||
_logo_label = logo
|
||
_faction_label = _label("", 11, title, TEAL)
|
||
var phase_box := VBoxContainer.new()
|
||
phase_box.custom_minimum_size.x = 164
|
||
phase_box.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||
row.add_child(phase_box)
|
||
_phase = _label("VORBEREITUNG", 11, phase_box, TEAL)
|
||
_timer = _label("", 22, phase_box)
|
||
_wave_progress = ProgressBar.new()
|
||
_wave_progress.custom_minimum_size.y = 3
|
||
_wave_progress.show_percentage = false
|
||
var progress_fill := _style(TEAL)
|
||
var progress_back := _style(Color("2c403e"))
|
||
for style: StyleBoxFlat in [progress_fill, progress_back]:
|
||
style.content_margin_top = 0
|
||
style.content_margin_bottom = 0
|
||
_wave_progress.add_theme_stylebox_override("fill", progress_fill)
|
||
_wave_progress.add_theme_stylebox_override("background", progress_back)
|
||
phase_box.add_child(_wave_progress)
|
||
_gold = _metric(row, "GOLD", GOLD, "res_gold")
|
||
_sparks = _metric(row, "FUNKEN", TEAL, "res_sparks")
|
||
_income = _metric(row, "PRO WELLE", GOLD, "res_income")
|
||
_workers = _metric(row, "ARBEITER", TEXT, "worker")
|
||
var spacer := Control.new()
|
||
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
row.add_child(spacer)
|
||
var controls := VBoxContainer.new()
|
||
controls.add_theme_constant_override("separation", 3)
|
||
row.add_child(controls)
|
||
var buttons := HBoxContainer.new()
|
||
buttons.add_theme_constant_override("separation", 6)
|
||
controls.add_child(buttons)
|
||
_pause_button = _button("Pause [P]", toggle_pause, buttons)
|
||
_speed_button = _button("1×", _toggle_speed, buttons)
|
||
_button("Optionen", toggle_settings, buttons).tooltip_text = "Einstellungen & Steuerung [F1]"
|
||
_front_button = _button("Rivalen beobachten [Tab]", func(): focus_requested.emit(), controls)
|
||
for button: Node in buttons.get_children():
|
||
button.add_theme_font_size_override("font_size", 12)
|
||
_front_button.add_theme_font_size_override("font_size", 12)
|
||
|
||
|
||
func _metric(parent: Node, caption: String, color: Color, icon_id: String) -> Label:
|
||
var panel := PanelContainer.new()
|
||
panel.add_theme_stylebox_override("panel", _glass(Color(.08, .13, .16, .55), Color(.4, .7, .65, .14)))
|
||
panel.custom_minimum_size.x = 118
|
||
parent.add_child(panel)
|
||
var row := HBoxContainer.new()
|
||
row.add_theme_constant_override("separation", 9)
|
||
panel.add_child(row)
|
||
_icon(icon_id, row, Vector2(24,24))
|
||
var column := VBoxContainer.new()
|
||
column.add_theme_constant_override("separation", 0)
|
||
row.add_child(column)
|
||
_label(caption, 10, column, MUTED)
|
||
return _label("—", 24, column, color)
|
||
|
||
|
||
func _make_sidebar() -> void:
|
||
var guide := _panel(self)
|
||
guide.position = Vector2(22, 110)
|
||
guide.add_theme_stylebox_override("panel", _style(Color(.05,.08,.09,.86)))
|
||
var location := VBoxContainer.new()
|
||
guide.add_child(location)
|
||
_game_panels.append(guide)
|
||
_label("DIE GRÜNE KLUFT / DEINE FRONT", 11, location, TEAL)
|
||
_step_hint = _label("", 16, location)
|
||
_camera_status = _label("Q / E Drehen · R Zurücksetzen · F1 Hilfe", 12, location, MUTED)
|
||
_boss_label = _label("", 17, location, CORAL)
|
||
_boss_bar = ProgressBar.new()
|
||
_boss_bar.custom_minimum_size = Vector2(285, 13)
|
||
_boss_bar.show_percentage = false
|
||
_boss_bar.add_theme_stylebox_override("background", _style(Color("#101d25")))
|
||
_boss_bar.add_theme_stylebox_override("fill", _style(CORAL))
|
||
location.add_child(_boss_bar)
|
||
_boss_bar.visible = false
|
||
var sidebar := _panel(self)
|
||
_context_panel = sidebar
|
||
_game_panels.append(sidebar)
|
||
sidebar.anchor_left = 1.0
|
||
sidebar.anchor_right = 1.0
|
||
sidebar.anchor_bottom = 0.0
|
||
sidebar.offset_left = -348
|
||
sidebar.offset_right = -20
|
||
sidebar.offset_top = 108
|
||
sidebar.offset_bottom = 504
|
||
var scroll := ScrollContainer.new()
|
||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||
sidebar.add_child(scroll)
|
||
var column := VBoxContainer.new()
|
||
column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
scroll.add_child(column)
|
||
_context_column = column
|
||
column.add_theme_constant_override("separation", 6)
|
||
_ready_button = _button("KAMPF STARTEN [Leertaste]", request_wave_start, column)
|
||
_ready_button.custom_minimum_size.y = 52
|
||
_ready_button.add_theme_font_size_override("font_size", 15)
|
||
_ready_button.add_theme_stylebox_override("normal", _gold_button())
|
||
_ready_button.add_theme_stylebox_override("hover", _gold_button())
|
||
_ready_button.add_theme_stylebox_override("pressed", _gold_button())
|
||
_ready_button.add_theme_color_override("font_color", INK)
|
||
_ready_button.add_theme_color_override("font_hover_color", INK)
|
||
for team in range(2):
|
||
var color := TEAL if team == 0 else CORAL
|
||
var caption := _label("DEIN KERN" if team == 0 else "RIVALENKERN", 13, column, color)
|
||
_core_labels.append(caption)
|
||
var bar := ProgressBar.new()
|
||
bar.max_value = 3000
|
||
bar.value = 3000
|
||
bar.show_percentage = false
|
||
bar.custom_minimum_size.y = 7
|
||
var background := _style(Color("#101d25"))
|
||
var fill := _style(color)
|
||
for style: StyleBoxFlat in [background, fill]:
|
||
style.content_margin_top = 0
|
||
style.content_margin_bottom = 0
|
||
bar.add_theme_stylebox_override("background", background)
|
||
bar.add_theme_stylebox_override("fill", fill)
|
||
column.add_child(bar)
|
||
_core_bars.append(bar)
|
||
column.add_child(HSeparator.new())
|
||
_wave_title = _label("NÄCHSTE WELLE", 13, column, GOLD)
|
||
_wave_title.mouse_filter = Control.MOUSE_FILTER_STOP
|
||
_wave_body = _label("", 14, column, MUTED)
|
||
_wave_body.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
_wave_body.custom_minimum_size.x = 278
|
||
column.add_child(HSeparator.new())
|
||
var selected_row := HBoxContainer.new()
|
||
column.add_child(selected_row)
|
||
_selection_icon = _icon("", selected_row, Vector2(64,64))
|
||
_selection_icon.visible = false
|
||
_selection_title = _label("DEINE FORMATION", 18, selected_row)
|
||
_selection_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_selection_types = HBoxContainer.new()
|
||
column.add_child(_selection_types)
|
||
_selection_title.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
_selection_body = _label("", 14, column, MUTED)
|
||
_selection_body.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
_selection_body.custom_minimum_size = Vector2(278, 45)
|
||
_upgrade = _button("Aufwerten", _upgrade_selected, column)
|
||
var actions := HBoxContainer.new()
|
||
column.add_child(actions)
|
||
_move = _button("Versetzen", func(): move_requested.emit(), actions)
|
||
_sell = _button("Verkaufen", func(): _emit_selected("sell"), actions)
|
||
_priority = OptionButton.new()
|
||
for title in ["Nächstes Ziel", "Niedrigste HP", "Höchste maximale HP"]:
|
||
_priority.add_item(title)
|
||
_priority.item_selected.connect(func(index: int):
|
||
command_requested.emit({"type": "target", "uid": selected_uid, "policy": index}))
|
||
column.add_child(_priority)
|
||
_inspect_model = _button("3D-Modell ansehen [V]", inspect_selected_model, column)
|
||
_summary = _label("", 13, column, TEAL)
|
||
_summary.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
|
||
|
||
func _make_footer() -> void:
|
||
var footer := _panel(self)
|
||
_shop_panel = footer
|
||
_game_panels.append(footer)
|
||
footer.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_WIDE)
|
||
footer.offset_left = 16
|
||
footer.offset_right = -364
|
||
footer.offset_top = -212
|
||
footer.offset_bottom = -18
|
||
var shop := VBoxContainer.new()
|
||
shop.add_theme_constant_override("separation", 7)
|
||
footer.add_child(shop)
|
||
var heading := HBoxContainer.new()
|
||
shop.add_child(heading)
|
||
var title := _label("WÄCHTER / WÄHLEN & PLATZIEREN", 12, heading, TEAL)
|
||
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_starter_button = _button("Startformation", func(): starter_requested.emit(), heading)
|
||
_starter_button.tooltip_text = "Stellt drei bezahlte Wächter auf: Tank und Nahkämpfer vorn, Schütze dahinter."
|
||
_undo = _button("↶ Rückgängig", func(): command_requested.emit({"type": "undo"}), heading)
|
||
_cards = HBoxContainer.new()
|
||
_cards.add_theme_constant_override("separation", 8)
|
||
shop.add_child(_cards)
|
||
_rebuild_shop()
|
||
_toast = _label("", 13, shop, MUTED)
|
||
_toast.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
var economy := _panel(self)
|
||
_game_panels.append(economy)
|
||
economy.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_RIGHT)
|
||
economy.offset_left = -348
|
||
economy.offset_right = -20
|
||
economy.offset_top = -300
|
||
economy.offset_bottom = -18
|
||
var column := VBoxContainer.new()
|
||
column.add_theme_constant_override("separation", 5)
|
||
economy.add_child(column)
|
||
var row := HBoxContainer.new()
|
||
column.add_child(row)
|
||
_label("BASIS & NACHschub".to_upper(), 12, row, TEAL).size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_button("Ansehen [C]", func(): base_requested.emit(), row)
|
||
_worker_button = _button("Arbeiter ausbilden", func(): command_requested.emit({"type": "worker"}), column)
|
||
_worker_button.icon = AssetLibrary.icon("worker")
|
||
_worker_button.add_theme_constant_override("icon_max_width", 25)
|
||
_worker_info = _label("", 12, column, MUTED)
|
||
_worker_info.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
column.add_child(HSeparator.new())
|
||
_send_picker = OptionButton.new()
|
||
for send_id in ["s01", "s02", "s03", "s04", "s05"]:
|
||
var data: Dictionary = DataCatalog.get_send(send_id, 1)
|
||
_send_picker.add_icon_item(AssetLibrary.icon(send_id), "%s · %d F · +%d G/Welle" % [data.display_name, data.cost, data.income_gain])
|
||
_send_picker.add_theme_constant_override("icon_max_width", 22)
|
||
_send_picker.get_popup().add_theme_constant_override("icon_max_width", 22)
|
||
column.add_child(_send_picker)
|
||
var send_row := HBoxContainer.new()
|
||
column.add_child(send_row)
|
||
_send_button = _button("Zum Rivalen senden", _queue_send, send_row)
|
||
_cancel_send = _button("↶", _cancel_last_send, send_row)
|
||
_cancel_send.tooltip_text = "Letzten Söldner entfernen; Funken zurückgeben"
|
||
_send_info = _label("", 12, column, MUTED)
|
||
_queue_scroll = ScrollContainer.new()
|
||
_queue_scroll.custom_minimum_size.y = 36
|
||
_queue_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||
column.add_child(_queue_scroll)
|
||
_queue_row = HBoxContainer.new()
|
||
_queue_row.add_theme_constant_override("separation", 4)
|
||
_queue_scroll.add_child(_queue_row)
|
||
|
||
func _rebuild_shop() -> void:
|
||
if _shop_faction == GameState.factions[0]:
|
||
return
|
||
_shop_faction = GameState.factions[0]
|
||
_shop_buttons.clear()
|
||
for child: Node in _cards.get_children():
|
||
child.free()
|
||
var index: int = 0
|
||
for type_id: String in DataCatalog.base_ids():
|
||
var data: Dictionary = DataCatalog.get_unit(type_id)
|
||
var button := _button("", func(): build_selected.emit(type_id), _cards)
|
||
button.custom_minimum_size = Vector2(0, 121)
|
||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
var content := VBoxContainer.new()
|
||
content.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
content.add_theme_constant_override("separation", 1)
|
||
content.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
content.offset_left = 10
|
||
content.offset_right = -10
|
||
content.offset_top = 4
|
||
content.offset_bottom = -4
|
||
button.add_child(content)
|
||
var top := HBoxContainer.new()
|
||
top.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
content.add_child(top)
|
||
_icon(type_id, top, Vector2(68, 68))
|
||
var price := VBoxContainer.new()
|
||
price.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
top.add_child(price)
|
||
_label("%d G" % data.cost, 20, price, GOLD)
|
||
_label("[ %d ]" % (index+1), 12, price, MUTED)
|
||
_label(data.display_name, 15, content).horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
_label(DataCatalog.ROLES[index], 12, content, TEAL).horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
button.tooltip_text = "%s · %s
|
||
%d HP · %d Schaden/s · %.1f m
|
||
%s" % [DataCatalog.DAMAGE_NAMES[data.attack_type], DataCatalog.ARMOR_NAMES[data.armor_type], data.max_hp, data.damage, data.attack_range, data.ability_description]
|
||
_shop_buttons[type_id] = button
|
||
index += 1
|
||
|
||
|
||
func _make_overlay() -> void:
|
||
main_menu = MainMenu.new()
|
||
add_child(main_menu)
|
||
_overlay = main_menu
|
||
_overlay_action = main_menu.start_button
|
||
_overlay_title = main_menu.length_summary
|
||
_faction_pickers = [main_menu.faction_choice, main_menu.rival_choice]
|
||
_mode_picker = main_menu.mode_choice
|
||
_ai_picker = main_menu.ai_choice
|
||
_timed_picker = main_menu.timed_toggle
|
||
main_menu.start_requested.connect(func() -> void: new_match_requested.emit())
|
||
main_menu.settings_requested.connect(toggle_settings)
|
||
main_menu.models_requested.connect(inspect_selected_model)
|
||
main_menu.quit_requested.connect(func() -> void: Soundscape.quit_game())
|
||
main_menu.selection_changed.connect(_rebuild_shop)
|
||
|
||
|
||
func _process(delta: float) -> void:
|
||
_toast_left -= delta
|
||
_refresh_left -= delta
|
||
if _refresh_left <= 0:
|
||
_refresh_left = 0.12
|
||
_refresh()
|
||
|
||
|
||
func _refresh() -> void:
|
||
var team: Dictionary = EconomyManager.get_team(0)
|
||
if team.is_empty():
|
||
return
|
||
_rebuild_shop()
|
||
if GameState.phase in [GameState.Phase.MENU, GameState.Phase.GAME_OVER] and is_instance_valid(main_menu):
|
||
# Match options can also change outside the menu (tools, tests); keep the display honest.
|
||
main_menu.sync()
|
||
_faction_label.text = DataCatalog.FACTION_NAMES[GameState.factions[0]].to_upper() + " / " + ("DEINE FRONT" if viewed_team == 0 else "RIVALENFRONT")
|
||
var building := GameState.phase == GameState.Phase.BUILD
|
||
_wave_progress.max_value = DataCatalog.wave_count()
|
||
_wave_progress.value = maxi(0, GameState.wave - 1)
|
||
_phase.text = GameState.phase_name() + (" · PAUSIERT" if GameState.paused else "")
|
||
_timer.text = "%02d / %02d · %02d s" % [maxi(1, GameState.wave), DataCatalog.wave_count(), ceili(WaveManager.remaining)]
|
||
if not GameState.timed_build and GameState.phase in [GameState.Phase.BUILD, GameState.Phase.RESULTS]:
|
||
_timer.text = "Welle %02d / %02d" % [maxi(1, GameState.wave), DataCatalog.wave_count()]
|
||
_gold.text = str(team.gold)
|
||
_sparks.text = "%d" % (int(team.sparks) - EconomyManager.reserved_sparks(0))
|
||
_sparks.tooltip_text = "Freie Funken. Vorgemerkte Söldner sind bereits abgezogen."
|
||
_income.text = "+%d" % team.income
|
||
_workers.text = "%d%s" % [team.workers, (" +%d" % team.pending_workers) if team.pending_workers else ""]
|
||
_workers.tooltip_text = "Aktive Arbeiter + bestellte Arbeiter. Je 10 Funken zu Beginn der nächsten Bauphase."
|
||
for side in range(2):
|
||
_core_labels[side].text = "%s %d / 3000" % [
|
||
"DEIN KERN" if side == 0 else "RIVALENKERN", GameState.core_hp[side]]
|
||
_core_bars[side].value = GameState.core_hp[side]
|
||
_pause_button.text = "Fortsetzen [P]" if GameState.paused else "Pause [P]"
|
||
_speed_button.text = "%d×" % GameState.speed
|
||
_front_button.text = "Eigene Front [Tab]" if viewed_team else "Rivalen beobachten [Tab]"
|
||
var w: int = maxi(1, GameState.wave)
|
||
var wave_data: Dictionary = DataCatalog.get_wave(w)
|
||
_wave_title.text = "%02d · %s" % [w, str(wave_data.get("title", "Welle"))]
|
||
_wave_body.text = str(wave_data.get("description", "")) + "\n\n" + DataCatalog.wave_hint(w)
|
||
_wave_title.tooltip_text = _wave_body.text
|
||
for type_id in _shop_buttons:
|
||
var data: Dictionary = DataCatalog.get_unit(type_id)
|
||
var button: Button = _shop_buttons[type_id]
|
||
button.disabled = not building or team.gold < data.cost
|
||
button.modulate = Color.WHITE
|
||
if build_type == type_id:
|
||
button.add_theme_stylebox_override("normal", _style(Color("2a5048"), TEAL, 2))
|
||
else:
|
||
var card := _style(Color("1a2c34"), Color("3a5558"), 1)
|
||
card.set_corner_radius_all(16)
|
||
card.shadow_color = Color(0, 0, 0, .3)
|
||
card.shadow_size = 6
|
||
button.add_theme_stylebox_override("normal", card)
|
||
_starter_button.disabled = not building or not team.roster.is_empty() or team.gold < DataCatalog.starter_cost()
|
||
_starter_button.text = "Startformation · %d G" % DataCatalog.starter_cost()
|
||
_undo.disabled = not building
|
||
_worker_button.text = "Arbeiter ausbilden · %d Gold" % EconomyManager.worker_cost(0)
|
||
_worker_button.disabled = not building or w >= DataCatalog.wave_count() or team.workers + team.pending_workers >= 8 or team.gold < EconomyManager.worker_cost(0)
|
||
_worker_info.text = "%d / 8 aktiv%s · +%d Funken / Welle\nNeue Arbeiter produzieren ab Welle %d." % [team.workers, " (+%d bestellt)" % team.pending_workers if team.pending_workers > 0 else "", int(team.workers) * 10, w+1]
|
||
if w >= DataCatalog.wave_count():
|
||
_worker_info.text = "Letzte Welle · Gold jetzt in Wächter investieren."
|
||
var send_cost: int = int(DataCatalog.SENDS["s%02d" % (_send_picker.selected + 1)].cost)
|
||
_send_button.disabled = not building or w < 3 or team.send_queue.size() >= 8 or mini(int(team.sparks), EconomyManager.send_budget(w)) - EconomyManager.reserved_sparks(0) < send_cost
|
||
_cancel_send.disabled = not building or team.send_queue.is_empty()
|
||
_send_picker.disabled = not building or w < 3
|
||
_send_info.text = "%d / 8 geplant · %d / %d F Budget" % [
|
||
team.send_queue.size(), EconomyManager.reserved_sparks(0), EconomyManager.send_budget(w)]
|
||
if w < 3:
|
||
_send_info.text = "Söldner verfügbar ab Welle 3"
|
||
_refresh_send_queue(team)
|
||
_ready_button.disabled = not building and GameState.phase != GameState.Phase.RESULTS
|
||
_ready_button.text = "NÄCHSTE WELLE [Leertaste]" if GameState.phase == GameState.Phase.RESULTS else ("KAMPF LÄUFT …" if GameState.phase == GameState.Phase.COMBAT else "KAMPF STARTEN [Leertaste]")
|
||
_step_hint.text = ("Erst Startformation setzen oder Wächter wählen." if team.roster.is_empty() else ("Platzieren → Aufwerten → Kampf starten" if build_type.is_empty() else "Freies Feld anklicken · Rechtsklick bricht ab")) if building else ("Welle beendet · Abrechnung rechts" if GameState.phase == GameState.Phase.RESULTS else "Deine Formation kämpft automatisch")
|
||
_refresh_selection(team, building)
|
||
if is_instance_valid(_context_column):
|
||
_context_panel.size.y = minf(_context_column.get_combined_minimum_size().y + 24, size.y - 422)
|
||
if _toast_left <= 0:
|
||
_toast.text = "1–5 Bauen · Linksklick Auswählen · Rechtsklick Abbrechen · Mausrad Zoom"
|
||
_toast.add_theme_color_override("font_color", MUTED)
|
||
|
||
|
||
func _refresh_selection(team: Dictionary, building: bool) -> void:
|
||
var selected: Dictionary = {}
|
||
for entry in team.roster:
|
||
if entry.uid == selected_uid:
|
||
selected = entry
|
||
break
|
||
var has_selection := not selected.is_empty()
|
||
_wave_title.visible = not has_selection
|
||
_summary.visible = not has_selection
|
||
_wave_body.visible = not has_selection
|
||
_inspect_model.visible = has_selection
|
||
_upgrade.visible = has_selection
|
||
_move.visible = has_selection
|
||
_sell.visible = has_selection
|
||
_priority.visible = has_selection
|
||
if not has_selection:
|
||
_selection_icon.visible = not build_type.is_empty()
|
||
_selection_icon.texture = AssetLibrary.icon(build_type)
|
||
_selection_types.visible = false
|
||
_selection_title.text = "DEINE FORMATION"
|
||
_selection_body.text = "%d / 48 Plätze · Tank vorn, Schützen dahinter.\nKlicke einen Wächter an, um ihn aufzuwerten." % team.roster.size()
|
||
if not build_type.is_empty():
|
||
var candidate: Dictionary = DataCatalog.get_unit(build_type)
|
||
_selection_title.text = candidate.display_name
|
||
_selection_body.text = "%d Gold · %d HP · %d Schaden/s\n%s\n\nLinksklick setzt einen Wächter. Shift setzt mehrere." % [candidate.cost, candidate.max_hp, candidate.damage, candidate.ability_description]
|
||
return
|
||
var data: Dictionary = DataCatalog.get_unit(selected.type_id)
|
||
_selection_icon.visible = true
|
||
_selection_icon.texture = AssetLibrary.icon(str(data.id))
|
||
_selection_types.visible = true
|
||
_refresh_type_icons(data)
|
||
_selection_title.text = data.display_name
|
||
_selection_body.text = "%d HP · %d Grund-DPS · %.1f m\n\n%s" % [
|
||
data.max_hp, data.damage, data.attack_range,
|
||
str(data.get("ability_description", ""))]
|
||
_selection_body.tooltip_text = "%s · %s" % [data.display_name, MATRIX_TOOLTIP]
|
||
var upgrade_id: String = str(data.get("upgrade_id", ""))
|
||
var upgraded: Dictionary = DataCatalog.get_unit(upgrade_id) if not upgrade_id.is_empty() else {}
|
||
_upgrade.disabled = not building or upgraded.is_empty() or team.gold < int(upgraded.get("cost", 0))
|
||
_upgrade.text = "Vollständig aufgewertet" if upgraded.is_empty() else "AUFWERTEN · %d GOLD\n%s" % [upgraded.cost, upgraded.display_name]
|
||
_upgrade.add_theme_font_size_override("font_size", 13)
|
||
_upgrade.tooltip_text = "" if upgraded.is_empty() else "%d → %d HP · %d → %d Schaden/s\n%s%s" % [data.max_hp, upgraded.max_hp, data.damage, upgraded.damage, upgraded.ability_description, "\nEs fehlen %d Gold." % (int(upgraded.cost)-int(team.gold)) if team.gold < int(upgraded.cost) else ""]
|
||
_sell.text = "Verkaufen · %d G" % floori(selected.invested * 0.75)
|
||
_sell.disabled = not building
|
||
_move.disabled = not building
|
||
_priority.disabled = not building
|
||
_priority.select(selected.target_policy)
|
||
|
||
|
||
func _refresh_send_queue(team: Dictionary) -> void:
|
||
var signature: String = ",".join(PackedStringArray(team.send_queue))
|
||
if signature == _queue_signature:
|
||
return
|
||
_queue_signature = signature
|
||
for child in _queue_row.get_children():
|
||
child.free()
|
||
var index := 0
|
||
for send_id: String in team.send_queue:
|
||
var chip := _button(SEND_SHORT_NAMES.get(send_id, send_id),
|
||
func(): command_requested.emit({"type": "cancel_send", "index": index}), _queue_row)
|
||
chip.add_theme_font_size_override("font_size", 11)
|
||
for state in ["normal", "hover", "pressed"]:
|
||
var compact := _style(Color("#26343b"), Color("#40544f") if state == "normal" else TEAL, 1)
|
||
compact.content_margin_left = 8
|
||
compact.content_margin_right = 8
|
||
compact.content_margin_top = 3
|
||
compact.content_margin_bottom = 3
|
||
chip.add_theme_stylebox_override(state, compact)
|
||
chip.tooltip_text = "%s entfernen · gibt die Funken vollständig frei" % DataCatalog.SENDS[send_id]["display_name"]
|
||
index += 1
|
||
_queue_scroll.visible = not team.send_queue.is_empty()
|
||
|
||
|
||
func _phase_changed(phase: int) -> void:
|
||
for panel: Control in _game_panels:
|
||
panel.visible = phase not in [GameState.Phase.MENU, GameState.Phase.GAME_OVER]
|
||
var fighting: bool = phase == GameState.Phase.COMBAT
|
||
_cards.visible = not fighting
|
||
_starter_button.visible = not fighting
|
||
_undo.visible = not fighting
|
||
_shop_panel.offset_top = -84 if fighting else -212
|
||
if phase in [GameState.Phase.MENU, GameState.Phase.GAME_OVER]:
|
||
main_menu.occluder = model_inspector
|
||
main_menu.enter(phase == GameState.Phase.GAME_OVER)
|
||
else:
|
||
main_menu.leave()
|
||
if phase == GameState.Phase.BUILD:
|
||
if GameState.wave == 1:
|
||
_summary.text = ""
|
||
_queue_signature = "@"
|
||
notify("Bauphase: neue Wächter, Aufwertungen und Arbeiter sind verfügbar.")
|
||
elif phase == GameState.Phase.COMBAT:
|
||
notify("Die Formation kämpft automatisch. Deine Kämpfer kehren zur nächsten Welle zurück.")
|
||
_refresh()
|
||
|
||
|
||
func _show_summary(reports: Array) -> void:
|
||
var report: Dictionary = reports[0]
|
||
_summary.text = "LETZTE ABRECHNUNG\n+%d Gold: %d Grundbetrag + %d Einkommen + %d Prämien\n%d Lecks · %d Kernschaden" % [
|
||
report.get("total_gold", 0), report.get("base_gold", 0), report.get("income", 0),
|
||
report.get("bounty", 0), report.get("leaks", 0), report.get("core_damage", 0)]
|
||
|
||
|
||
func notify(message: String, is_error: bool = false) -> void:
|
||
_toast.text = message
|
||
_toast.add_theme_color_override("font_color", CORAL if is_error else TEAL)
|
||
_toast_left = 6.0
|
||
|
||
|
||
func toggle_pause() -> void:
|
||
if GameState.phase in [GameState.Phase.MENU, GameState.Phase.GAME_OVER]:
|
||
return
|
||
GameState.paused = not GameState.paused
|
||
if GameState.paused:
|
||
Soundscape.stop_combat()
|
||
_refresh()
|
||
|
||
|
||
func _toggle_speed() -> void:
|
||
GameState.speed = 2 if GameState.speed == 1 else 1
|
||
_refresh()
|
||
|
||
|
||
func _emit_selected(type: String) -> void:
|
||
command_requested.emit({"type": type, "uid": selected_uid})
|
||
|
||
|
||
func _upgrade_selected() -> void:
|
||
_emit_selected("upgrade")
|
||
|
||
|
||
func _queue_send() -> void:
|
||
command_requested.emit({"type": "queue_send", "type_id": "s%02d" % (_send_picker.selected + 1)})
|
||
|
||
|
||
func _cancel_last_send() -> void:
|
||
var queue: Array = EconomyManager.get_team(0).send_queue
|
||
if not queue.is_empty():
|
||
command_requested.emit({"type": "cancel_send", "index": queue.size() - 1})
|
||
|
||
|
||
func _icon(id: String, parent: Node, minimum: Vector2) -> TextureRect:
|
||
var image := TextureRect.new()
|
||
image.texture = AssetLibrary.icon(id)
|
||
image.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||
image.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||
image.custom_minimum_size = minimum
|
||
image.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
parent.add_child(image)
|
||
return image
|
||
|
||
func _refresh_type_icons(data: Dictionary) -> void:
|
||
var key: String = "%s:%s" % [data.attack_type, data.armor_type]
|
||
if _selection_types.get_meta("key", "") == key:
|
||
return
|
||
_selection_types.set_meta("key", key)
|
||
for child: Node in _selection_types.get_children():
|
||
child.free()
|
||
_icon(["type_damage_blade","type_damage_pierce","type_damage_pulse"][int(data.attack_type)], _selection_types, Vector2(25,25))
|
||
_label(DataCatalog.DAMAGE_NAMES[data.attack_type],12,_selection_types,GOLD)
|
||
_icon(["type_armor_light","type_armor_heavy","type_armor_aether"][int(data.armor_type)], _selection_types, Vector2(25,25))
|
||
_label(DataCatalog.ARMOR_NAMES[data.armor_type],12,_selection_types,MUTED)
|
||
|
||
|
||
func _make_settings() -> void:
|
||
settings_panel = ColorRect.new()
|
||
settings_panel.color = Color(.02, .03, .04, .9)
|
||
settings_panel.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
# Reached from the title menu as well as in play: it shares the menu theme.
|
||
settings_panel.theme = SaumwachtTheme.shared()
|
||
add_child(settings_panel)
|
||
var center := CenterContainer.new()
|
||
center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
settings_panel.add_child(center)
|
||
var panel := PanelContainer.new()
|
||
panel.theme_type_variation = "MenuPanel"
|
||
panel.custom_minimum_size.x = 760
|
||
center.add_child(panel)
|
||
var column := VBoxContainer.new()
|
||
column.add_theme_constant_override("separation", 12)
|
||
panel.add_child(column)
|
||
var heading := _themed_label("Optionen & Steuerung", "HeadingLabel", column)
|
||
heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
_themed_label("Eine laufende Partie pausiert, solange dieses Fenster geöffnet ist.", "MutedLabel", column).horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
_themed_label("LAUTSTÄRKE", "SectionLabel", column)
|
||
_volume_slider = HSlider.new()
|
||
_volume_slider.max_value = 100
|
||
_volume_slider.step = 1
|
||
_volume_slider.custom_minimum_size.y = 28
|
||
column.add_child(_volume_slider)
|
||
_volume_slider.value_changed.connect(func(value: float):
|
||
Preferences.volume = value / 100.0
|
||
Preferences.commit())
|
||
_volume_slider.drag_ended.connect(func(_changed: bool): Soundscape.play("place"))
|
||
var toggles := GridContainer.new()
|
||
toggles.columns = 2
|
||
toggles.add_theme_constant_override("h_separation", 28)
|
||
toggles.add_theme_constant_override("v_separation", 4)
|
||
column.add_child(toggles)
|
||
_mute_check = CheckButton.new()
|
||
_mute_check.text = "Ton stumm [M]"
|
||
toggles.add_child(_mute_check)
|
||
_mute_check.toggled.connect(func(value: bool):
|
||
Preferences.muted = value
|
||
Preferences.commit())
|
||
_follow_check = CheckButton.new()
|
||
_follow_check.text = "Kamera folgt dem Kampf"
|
||
toggles.add_child(_follow_check)
|
||
_follow_check.toggled.connect(func(value: bool):
|
||
Preferences.follow_camera = value
|
||
Preferences.commit())
|
||
_fullscreen_check = CheckButton.new()
|
||
_fullscreen_check.text = "Vollbild [F11]"
|
||
toggles.add_child(_fullscreen_check)
|
||
_fullscreen_check.toggled.connect(func(value: bool):
|
||
Preferences.fullscreen = value
|
||
Preferences.commit())
|
||
_reduced_check = CheckButton.new()
|
||
_reduced_check.text = "Bewegung reduzieren"
|
||
_reduced_check.tooltip_text = "Menü ohne Kamerafahrten, Kameraatmen, Partikel und Überblendungen."
|
||
toggles.add_child(_reduced_check)
|
||
_reduced_check.toggled.connect(func(value: bool):
|
||
Preferences.reduced_motion = value
|
||
Preferences.commit())
|
||
column.add_child(HSeparator.new())
|
||
_themed_label("STEUERUNG", "SectionLabel", column)
|
||
_themed_label("1–5 Wächter wählen Linksklick Bauen / Auswählen\nRechtsklick / Esc Bauauftrag abbrechen\nLeertaste Kampf / nächste Welle P Pause\nTab Front wechseln C Arbeiter & Kristall\nMausrad Zoomen Shift + Klick Mehrfach bauen\nQ / E Kamera drehen Mittlere Maustaste ziehen Verschieben\nR Kamera zurücksetzen und dem Kampf wieder folgen\nV 3D-Modelle ansehen, drehen und Bewegungen abspielen", "Label", column)
|
||
_themed_label("Startformation: Tank, Nahkämpfer und Schütze.\nFunkenleser liefern ab der nächsten Welle Funken.\nSöldner schickst du ab Welle 3 zum Rivalen.", "MutedLabel", column)
|
||
var row := HBoxContainer.new()
|
||
row.add_theme_constant_override("separation", 12)
|
||
column.add_child(row)
|
||
var back := _button("ZURÜCK [Esc]", toggle_settings, row)
|
||
back.theme_type_variation = "PrimaryButton"
|
||
back.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_button("Neue Partie", func():
|
||
settings_panel.hide()
|
||
main_menu.pending_screen = MainMenu.Screen.SETUP
|
||
menu_requested.emit(), row).tooltip_text = "Beendet diese Partie und öffnet die Bundauswahl."
|
||
_button("Spiel beenden", func(): Soundscape.quit_game(), row)
|
||
settings_panel.visible = false
|
||
|
||
func _themed_label(text_value: String, variation: String, parent: Node) -> Label:
|
||
var label := Label.new()
|
||
label.text = text_value
|
||
label.theme_type_variation = variation
|
||
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
parent.add_child(label)
|
||
return label
|
||
|
||
func sync_settings() -> void:
|
||
_volume_slider.set_value_no_signal(Preferences.volume * 100.0)
|
||
_mute_check.set_pressed_no_signal(Preferences.muted)
|
||
_follow_check.set_pressed_no_signal(Preferences.follow_camera)
|
||
_fullscreen_check.set_pressed_no_signal(Preferences.fullscreen)
|
||
_reduced_check.set_pressed_no_signal(Preferences.reduced_motion)
|
||
|
||
func toggle_settings() -> void:
|
||
if settings_panel.visible:
|
||
settings_panel.hide()
|
||
GameState.paused = _settings_paused
|
||
if is_instance_valid(_settings_focus) and _settings_focus.is_visible_in_tree():
|
||
_settings_focus.grab_focus()
|
||
else:
|
||
_settings_focus = get_viewport().gui_get_focus_owner()
|
||
_settings_paused = GameState.paused
|
||
GameState.paused = true
|
||
Soundscape.stop_combat()
|
||
sync_settings()
|
||
settings_panel.show()
|
||
_volume_slider.grab_focus()
|
||
_refresh()
|
||
|
||
func update_battle_readout(units: Array, manual_camera: bool) -> void:
|
||
_camera_status.text = ("Freie Kamera · R Zurücksetzen" if manual_camera else "Q / E Drehen · Mausrad Zoom") + " · F1 Hilfe"
|
||
var boss = null
|
||
if GameState.phase == GameState.Phase.COMBAT:
|
||
for unit in units:
|
||
if unit.alive and unit.is_attacker and unit.team_id == viewed_team and unit._is_boss():
|
||
boss = unit
|
||
break
|
||
_boss_bar.visible = boss != null
|
||
_boss_label.visible = boss != null
|
||
if boss != null:
|
||
_boss_bar.max_value = boss.max_hp
|
||
_boss_bar.value = boss.hp
|
||
_boss_label.text = "%s · %d / %d HP" % [boss.data.display_name, ceili(boss.hp), ceili(boss.max_hp)]
|
||
|
||
func inspect_selected_model() -> void:
|
||
var id: String = build_type
|
||
for entry: Dictionary in EconomyManager.get_team(0).roster:
|
||
if entry.uid == selected_uid:
|
||
id = str(entry.type_id)
|
||
break
|
||
if id.is_empty():
|
||
id = "w10" if GameState.factions[0] == "wurzelbund" else "g04"
|
||
model_inspector.show_model(id)
|
||
|
||
func request_wave_start() -> void:
|
||
if GameState.paused:
|
||
notify("Setze das Spiel mit P fort, bevor du eine Welle startest.")
|
||
return
|
||
if GameState.phase == GameState.Phase.BUILD and EconomyManager.get_team(0).roster.is_empty():
|
||
notify("Setze zuerst Wächter oder nutze die Startformation.", true)
|
||
return
|
||
ready_requested.emit()
|