@tool extends VBoxContainer # ─── State ──────────────────────────────────────────────────────────────────── var _mpq = null # MPQManager const LOCALES := ["ruRU", "enUS", "enGB", "deDE", "frFR", "esES", "esMX", "koKR", "zhCN", "zhTW", "ptBR", "ptPT"] # ─── UI nodes (created in _ready) ───────────────────────────────────────────── var _client_path_edit: LineEdit var _locale_option: OptionButton var _filter_edit: LineEdit var _output_edit: LineEdit var _open_btn: Button var _extract_btn: Button var _list_btn: Button var _status_label: Label var _file_list: ItemList var _archive_list: ItemList var _progress: ProgressBar # ─── Build UI ───────────────────────────────────────────────────────────────── func _ready() -> void: size_flags_horizontal = Control.SIZE_EXPAND_FILL add_theme_constant_override("separation", 6) _add_title() _add_separator() _add_client_row() _add_locale_row() _open_btn = _add_button("Open Client Archives", _on_open_pressed) _add_separator() _add_label("Loaded archives (highest priority first):") _archive_list = _add_item_list(100) _add_separator() _add_filter_row() _add_output_row() var btns := HBoxContainer.new(); add_child(btns) _list_btn = _add_button_to("List Files", btns, _on_list_pressed) _extract_btn = _add_button_to("Extract", btns, _on_extract_pressed) _progress = ProgressBar.new(); _progress.visible = false; add_child(_progress) _add_label("Files matching filter:") _file_list = _add_item_list(0, true) # expand vertically _status_label = _add_label("Ready.") _extract_btn.disabled = true _list_btn.disabled = true # ─── UI helpers ─────────────────────────────────────────────────────────────── func _add_title() -> void: var lbl := Label.new() lbl.text = "MPQ Extractor (WoW 3.3.5a)" lbl.add_theme_font_size_override("font_size", 14) add_child(lbl) func _add_separator() -> void: add_child(HSeparator.new()) func _add_label(text: String) -> Label: var lbl := Label.new(); lbl.text = text add_child(lbl); return lbl func _add_item_list(min_height: int, expand := false) -> ItemList: var il := ItemList.new() if min_height > 0: il.custom_minimum_size = Vector2(0, min_height) if expand: il.size_flags_vertical = Control.SIZE_EXPAND_FILL add_child(il); return il func _add_button(text: String, cb: Callable) -> Button: var btn := Button.new(); btn.text = text btn.pressed.connect(cb); add_child(btn); return btn func _add_button_to(text: String, parent: Control, cb: Callable) -> Button: var btn := Button.new() btn.text = text btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL btn.pressed.connect(cb); parent.add_child(btn); return btn func _add_row(label_text: String) -> HBoxContainer: var row := HBoxContainer.new(); add_child(row) var lbl := Label.new() lbl.text = label_text lbl.custom_minimum_size = Vector2(90, 0) row.add_child(lbl); return row func _add_client_row() -> void: var row := _add_row("Client path") _client_path_edit = LineEdit.new() _client_path_edit.text = "res://sources" _client_path_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL _client_path_edit.placeholder_text = "res://sources" row.add_child(_client_path_edit) func _add_locale_row() -> void: var row := _add_row("Locale") _locale_option = OptionButton.new() _locale_option.size_flags_horizontal = Control.SIZE_EXPAND_FILL for loc in LOCALES: _locale_option.add_item(loc) row.add_child(_locale_option) func _add_filter_row() -> void: var row := _add_row("Filter") _filter_edit = LineEdit.new() _filter_edit.text = "*" _filter_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL _filter_edit.placeholder_text = "DBFilesClient\\*.dbc" row.add_child(_filter_edit) func _add_output_row() -> void: var row := _add_row("Output dir") _output_edit = LineEdit.new() _output_edit.text = "res://data/extracted" _output_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL row.add_child(_output_edit) # ─── Handlers ───────────────────────────────────────────────────────────────── func _on_open_pressed() -> void: if not ClassDB.class_exists("MPQManager"): _set_status("ERROR: MPQManager не найден. Собери GDExtension (build.bat Release).") return if _mpq: _mpq.call("close") _mpq = ClassDB.instantiate("MPQManager") var client_path: String = ProjectSettings.globalize_path(_client_path_edit.text) var locale: String = LOCALES[_locale_option.selected] _set_status("Opening archives…") var count: int = _mpq.call("open_client", client_path, locale) if count == 0: _set_status("ERROR: no archives opened. Check client path / locale.") _extract_btn.disabled = true _list_btn.disabled = true return _archive_list.clear() var info_list: Array = _mpq.call("get_archive_info") for info in info_list: var lbl_text := "[%d] %s (%d files)" % [ info["priority"], (info["path"] as String).get_file(), info["file_count"] ] _archive_list.add_item(lbl_text) _set_status("Opened %d archives. Locale: %s" % [count, locale]) _extract_btn.disabled = false _list_btn.disabled = false func _on_list_pressed() -> void: if not _mpq: return _file_list.clear() _set_status("Listing files…") var filter: String = _filter_edit.text if not _filter_edit.text.is_empty() else "*" var files: PackedStringArray = _mpq.call("list_files", filter) for f in files: _file_list.add_item(f) _set_status("Found %d files matching '%s'" % [files.size(), filter]) func _on_extract_pressed() -> void: if not _mpq: return var filter: String = _filter_edit.text if not _filter_edit.text.is_empty() else "*" var output_dir: String = ProjectSettings.globalize_path(_output_edit.text) _set_status("Extracting…") _progress.visible = true _progress.value = 0 var count: int = _mpq.call("extract_files", filter, output_dir) _progress.value = 100 _progress.visible = false _set_status("Extracted %d files → %s" % [count, output_dir]) # ─── Helpers ────────────────────────────────────────────────────────────────── func _set_status(text: String) -> void: _status_label.text = text print("[MPQTool] ", text)