commit 2085c2dcc94c984ab889d511d386dd1f12004101 Author: iisaev Date: Thu Oct 29 13:22:26 2026 +0100 Split catalog_export out of editor_tools Code, locale and test suites moved from editor_tools; docs in seven languages with screenshots where the window has something to show; own cover. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3030c1a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,30 @@ +# Line endings: LF everywhere, on every OS, whatever core.autocrlf says. +* text=auto eol=lf + +# cmd.exe misreads labels and goto in LF batch files, so they stay CRLF. +*.bat text eol=crlf +*.cmd text eol=crlf + +# Binaries: never converted, never diffed as text. +*.png binary +*.jpg binary +*.jpeg binary +*.webp binary +*.svg text +*.ogg binary +*.wav binary +*.mp3 binary +*.ttf binary +*.otf binary +*.woff binary +*.woff2 binary +*.dll binary +*.so binary +*.dylib binary +*.pdb binary +*.exe binary +*.zip binary +*.pck binary +*.moc3 binary +*.res binary +*.scn binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f79a943 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Godot 4.x +.godot/ +.idea/ +sdk/ +bin/ +obj/ + +#ai +.omo/ +.opencode/ + +gen/ +obj/ +bin/ +art_sources/ + +*.tres~ +*.tscn~ + +# GDScript LSP +.godot/ + +# Export +*.exe +*.dmg +*.apk +*.aab +*.pck +*.zip +*.log + +# OS +.DS_Store +Thumbs.db + +# Editor +*.import +*.godot.uid diff --git a/docs/assets/catalog-export.jpg b/docs/assets/catalog-export.jpg new file mode 100644 index 0000000..e7c1043 Binary files /dev/null and b/docs/assets/catalog-export.jpg differ diff --git a/docs/en.md b/docs/en.md new file mode 100644 index 0000000..df31a79 --- /dev/null +++ b/docs/en.md @@ -0,0 +1,23 @@ +# Catalog Export — publish a mod in a mod catalog + +A mod catalog is a JSON list the mod manager downloads mods from. Catalog Export writes a mod's entry for such a catalog — names and descriptions per language, category, tags, version limits, changelog, download link — and packs the mod into the archive the entry points to, with SHA-256 checksums of the archive and of the installed files. + +## Opening it + +Hammer button on the in-game toolbar → **Export Catalog Entry**. + +## Exporting a mod + +![Export Catalog Entry for the ui_base mod: the form with the mod's id, name, version and author, Download URL, Status, Min Game Version, Icon URL, Tags and the category buttons on the left; the resulting JSON on the right; Copy JSON and Export to File at the top.](assets/catalog-export.jpg) + +1. **Select a mod**. The form is filled from its manifest. +2. Fill **Download URL** (where the archive will be uploaded; required), **Icon URL**, **Category**, **Tags (comma-separated)**, **Min Game Version**, **Status** and **Changelog**. **Add language** adds a name and a description in another language. +3. The preview shows the resulting JSON. **Copy JSON** puts it on the clipboard. +4. **Export to File**: for a folder mod, a `.zip` archive and its `.json` entry are written into the mods folder; for a mod that is already a `.zip`, only the `.json` is written next to it. The status line shows the written path. + +Upload the archive to the download URL and add the entry to your catalog; the mod manager then offers the mod for installation. + +## Dependencies + +`tools_base`, `ui_base`. It uses the game's catalog and archive code and does not need the mod manager window. +The older `editor_tools` package installs this mod together with the other authoring windows. diff --git a/docs/es.md b/docs/es.md new file mode 100644 index 0000000..a43e066 --- /dev/null +++ b/docs/es.md @@ -0,0 +1,23 @@ +# Exportar catálogo — publica un mod en un catálogo de mods + +Un catálogo de mods es una lista JSON desde la que el gestor de mods descarga mods. Exportar catálogo escribe la entrada de un mod para ese catálogo (nombres y descripciones por idioma, categoría, etiquetas, versiones, cambios, enlace de descarga) y empaqueta el mod en el archivo al que apunta la entrada, con sumas SHA-256 del archivo y de los archivos instalados. + +## Cómo abrirlo + +Botón del martillo en la barra del juego → **Exportar entrada del catálogo**. + +## Exportar un mod + +![Exportar entrada del catálogo para el mod ui_base: a la izquierda el formulario con id, nombre, versión y autor del mod, URL de descarga, Estado, Versión mínima del juego, URL del icono, Etiquetas y los botones de categoría; a la derecha el JSON resultante; arriba Copiar JSON y Exportar a archivo.](assets/catalog-export.jpg) + +1. **Selecciona un mod**. El formulario se rellena desde su manifiesto. +2. Rellena **URL de descarga** (donde se subirá el archivo; obligatoria), **URL del icono**, **Categoría**, **Etiquetas (separadas por comas)**, **Versión mínima del juego**, **Estado** y **Cambios**. **Añadir idioma** añade un nombre y una descripción en otro idioma. +3. **JSON resultante** muestra el resultado. **Copiar JSON** lo copia al portapapeles. +4. **Exportar a archivo**: para un mod de carpeta se escriben en la carpeta de mods un archivo `.zip` y su entrada `.json`; para un mod que ya es `.zip`, solo se escribe el `.json` a su lado. La línea de estado muestra la ruta escrita. + +Sube el archivo al enlace de descarga y añade la entrada a tu catálogo; el gestor de mods ofrecerá el mod para instalar. + +## Dependencias + +`tools_base`, `ui_base`. Usa el código de catálogos y archivos del propio juego; no necesita la ventana del gestor de mods. +El paquete antiguo `editor_tools` instala este mod junto con las demás ventanas de autor. diff --git a/docs/ja.md b/docs/ja.md new file mode 100644 index 0000000..c491fe7 --- /dev/null +++ b/docs/ja.md @@ -0,0 +1,23 @@ +# カタログのエクスポート — MOD を MOD カタログで公開する + +MOD カタログは、MOD マネージャーが MOD をダウンロードする元になる JSON の一覧です。カタログのエクスポートは、そうしたカタログ向けに MOD の項目(言語ごとの名前と説明、カテゴリー、タグ、バージョン、変更履歴、ダウンロードリンク)を書き出し、項目が指すアーカイブに MOD をまとめ、アーカイブとインストールされるファイルの SHA-256 チェックサムを付けます。 + +## 開き方 + +ゲームのツールバーのハンマーボタン → **カタログ項目を書き出す**。 + +## MOD の書き出し + +![ui_base MOD のカタログ項目書き出しウィンドウ: 左に MOD の id、名前、バージョン、作者、ダウンロード URL、状態、最低ゲームバージョン、アイコンの URL、タグ、カテゴリーのボタンが並ぶフォーム、右にできあがる JSON、上に JSON をコピーとファイルに書き出す。](assets/catalog-export.jpg) + +1. **MOD を選択**します。フォームはそのマニフェストから埋まります。 +2. **ダウンロード URL**(アーカイブを置く場所、必須)、**アイコンの URL**、**カテゴリー**、**タグ(カンマ区切り)**、**最低ゲームバージョン**、**状態**、**変更履歴**を入力します。**言語を追加**で別の言語の名前と説明を加えます。 +3. **できあがる JSON** に結果が表示されます。**JSON をコピー**でクリップボードに入ります。 +4. **ファイルに書き出す**: フォルダー MOD なら MOD フォルダーに `.zip` アーカイブとその `.json` 項目が書かれます。すでに `.zip` の MOD なら、その横に `.json` だけが書かれます。ステータス行に書き出したパスが出ます。 + +アーカイブをダウンロードリンクの場所にアップロードし、項目をカタログに加えると、MOD マネージャーがその MOD をインストール候補として表示します。 + +## 依存関係 + +`tools_base`、`ui_base`。ゲーム自身のカタログ・アーカイブのコードを使い、MOD マネージャーのウィンドウは不要です。 +古い `editor_tools` パッケージは、この MOD をほかの作成用ウィンドウと一緒にインストールします。 diff --git a/docs/ko.md b/docs/ko.md new file mode 100644 index 0000000..8d4dbf9 --- /dev/null +++ b/docs/ko.md @@ -0,0 +1,23 @@ +# 카탈로그 내보내기 — 모드를 모드 카탈로그에 공개하기 + +모드 카탈로그는 모드 관리자가 모드를 내려받는 JSON 목록입니다. 카탈로그 내보내기는 그런 카탈로그용 모드 항목(언어별 이름과 설명, 분류, 태그, 버전, 변경 기록, 다운로드 링크)을 쓰고, 항목이 가리키는 아카이브로 모드를 묶으며 아카이브와 설치 파일의 SHA-256 체크섬을 붙입니다. + +## 여는 방법 + +게임 툴바의 망치 버튼 → **카탈로그 항목 내보내기**. + +## 모드 내보내기 + +![ui_base 모드의 카탈로그 항목 내보내기: 왼쪽에 모드의 id, 이름, 버전, 제작자, 다운로드 URL, 상태, 최소 게임 버전, 아이콘 URL, 태그와 분류 버튼이 있는 양식, 오른쪽에 결과 JSON, 위쪽에 JSON 복사와 파일로 내보내기.](assets/catalog-export.jpg) + +1. **모드 선택**. 양식이 모드의 매니페스트로 채워집니다. +2. **다운로드 URL**(아카이브를 올릴 곳, 필수), **아이콘 URL**, **분류**, **태그 (쉼표로 구분)**, **최소 게임 버전**, **상태**, **변경 기록**을 채웁니다. **언어 추가**로 다른 언어의 이름과 설명을 더합니다. +3. **결과 JSON**에 결과가 보입니다. **JSON 복사**는 클립보드에 넣습니다. +4. **파일로 내보내기**: 폴더 모드라면 모드 폴더에 `.zip` 아카이브와 그 `.json` 항목이 쓰이고, 이미 `.zip`인 모드라면 옆에 `.json`만 쓰입니다. 상태 줄에 기록된 경로가 표시됩니다. + +아카이브를 다운로드 링크 위치에 올리고 항목을 카탈로그에 추가하면 모드 관리자가 그 모드를 설치 대상으로 보여 줍니다. + +## 의존성 + +`tools_base`, `ui_base`. 게임 자체의 카탈로그·아카이브 코드를 쓰며 모드 관리자 창은 필요 없습니다. +예전 `editor_tools` 패키지는 이 모드를 다른 제작 창과 함께 설치합니다. diff --git a/docs/ru.md b/docs/ru.md new file mode 100644 index 0000000..e356896 --- /dev/null +++ b/docs/ru.md @@ -0,0 +1,23 @@ +# Экспорт каталога — опубликовать мод в каталоге модов + +Каталог модов — это JSON-список, из которого менеджер модов скачивает моды. Экспорт каталога пишет запись мода для такого каталога (названия и описания на разных языках, категория, теги, версии, список изменений, ссылка на скачивание) и упаковывает мод в архив, на который указывает запись, с контрольными суммами SHA-256 архива и установленных файлов. + +## Как открыть + +Кнопка с молотком на игровом тулбаре → **Экспорт записи каталога**. + +## Экспорт мода + +![Экспорт записи каталога для мода ui_base: слева форма с id, названием, версией и автором мода, URL скачивания, Статус, Мин. версия игры, URL иконки, Теги и кнопки категорий; справа итоговый JSON; сверху Копировать JSON и Экспорт в файл.](assets/catalog-export.jpg) + +1. **Выберите мод**. Форма заполнится из его манифеста. +2. Заполните **URL скачивания** (куда будет загружен архив; обязательно), **URL иконки**, **Категорию**, **Теги (через запятую)**, **Мин. версию игры**, **Статус** и **Список изменений**. **Добавить язык** добавляет название и описание на другом языке. +3. **Итоговый JSON** показывает результат. **Копировать JSON** кладёт его в буфер обмена. +4. **Экспорт в файл**: для мода-папки в папку модов записываются архив `.zip` и его запись `.json`; для мода, который уже является `.zip`, рядом с ним пишется только `.json`. Строка статуса показывает путь записанного файла. + +Загрузите архив по ссылке скачивания и добавьте запись в свой каталог — менеджер модов предложит мод к установке. + +## Зависимости + +`tools_base`, `ui_base`. Используется код каталогов и архивов самой игры; окно менеджера модов не нужно. +Старый пакет `editor_tools` ставит этот мод вместе с остальными авторскими окнами. diff --git a/docs/vi.md b/docs/vi.md new file mode 100644 index 0000000..98307a0 --- /dev/null +++ b/docs/vi.md @@ -0,0 +1,23 @@ +# Xuất danh mục — đăng mod lên một danh mục mod + +Danh mục mod là một danh sách JSON mà trình quản lý mod tải mod từ đó. Xuất danh mục ghi mục của một mod cho danh mục như vậy (tên và mô tả theo ngôn ngữ, thể loại, thẻ, phiên bản, nhật ký thay đổi, liên kết tải) và đóng gói mod thành tệp nén mà mục trỏ tới, kèm tổng kiểm SHA-256 của tệp nén và của các tệp được cài. + +## Cách mở + +Nút búa trên thanh công cụ trong trò chơi → **Xuất mục danh mục**. + +## Xuất một mod + +![Xuất mục danh mục cho mod ui_base: bên trái biểu mẫu với id, tên, phiên bản và tác giả của mod, URL tải xuống, Trạng thái, Phiên bản game tối thiểu, URL biểu tượng, Thẻ và các nút thể loại; bên phải JSON kết quả; phía trên Sao chép JSON và Xuất ra tệp.](assets/catalog-export.jpg) + +1. **Chọn một mod**. Biểu mẫu được điền từ manifest của nó. +2. Điền **URL tải xuống** (nơi sẽ tải tệp nén lên; bắt buộc), **URL biểu tượng**, **Thể loại**, **Thẻ (cách nhau bằng dấu phẩy)**, **Phiên bản game tối thiểu**, **Trạng thái** và **Nhật ký thay đổi**. **Thêm ngôn ngữ** thêm tên và mô tả ở ngôn ngữ khác. +3. **JSON kết quả** hiện kết quả. **Sao chép JSON** đưa nó vào bộ nhớ tạm. +4. **Xuất ra tệp**: với mod dạng thư mục, tệp nén `.zip` và mục `.json` của nó được ghi vào thư mục mod; với mod vốn đã là `.zip`, chỉ ghi `.json` bên cạnh. Dòng trạng thái hiện đường dẫn đã ghi. + +Tải tệp nén lên liên kết tải và thêm mục vào danh mục của bạn; trình quản lý mod sẽ đề xuất cài mod đó. + +## Phụ thuộc + +`tools_base`, `ui_base`. Nó dùng mã danh mục và tệp nén của chính trò chơi, không cần cửa sổ trình quản lý mod. +Gói cũ `editor_tools` cài mod này cùng các cửa sổ tác giả khác. diff --git a/docs/zh.md b/docs/zh.md new file mode 100644 index 0000000..19f58a4 --- /dev/null +++ b/docs/zh.md @@ -0,0 +1,23 @@ +# 目录导出 —— 把模组发布到模组目录 + +模组目录是一个 JSON 列表,模组管理器从中下载模组。目录导出为这样的目录写出模组条目(各语言的名称和描述、分类、标签、版本、更新日志、下载链接),并把模组打包成条目所指向的压缩包,附带压缩包和安装文件的 SHA-256 校验和。 + +## 打开方式 + +游戏工具栏上的锤子按钮 → **导出目录条目**。 + +## 导出模组 + +![ui_base 模组的导出目录条目窗口:左侧是表单,包含模组的 id、名称、版本和作者、下载 URL、状态、最低游戏版本、图标 URL、标签和分类按钮;右侧是生成的 JSON;顶部有复制 JSON 和导出到文件。](assets/catalog-export.jpg) + +1. **选择模组**。表单会根据它的清单自动填写。 +2. 填写**下载 URL**(压缩包将上传的位置,必填)、**图标 URL**、**分类**、**标签(逗号分隔)**、**最低游戏版本**、**状态**和**更新日志**。**添加语言**会加上另一种语言的名称和描述。 +3. **生成的 JSON** 显示结果。**复制 JSON** 把它放进剪贴板。 +4. **导出到文件**:对于文件夹模组,会在模组文件夹中写出 `.zip` 压缩包及其 `.json` 条目;对于本身已是 `.zip` 的模组,只在它旁边写出 `.json`。状态栏显示写出的路径。 + +把压缩包上传到下载链接,并把条目加入你的目录;模组管理器就会提供该模组供安装。 + +## 依赖 + +`tools_base`、`ui_base`。它使用游戏自身的目录和压缩包代码,不需要模组管理器窗口。 +旧的 `editor_tools` 包会把这个模组和其他创作窗口一起安装。 diff --git a/locale/catalog_export_en.properties b/locale/catalog_export_en.properties new file mode 100644 index 0000000..8e9c6c3 --- /dev/null +++ b/locale/catalog_export_en.properties @@ -0,0 +1,5 @@ +# Catalog export tool — extra keys +mods/catalog_select_mod = Select a mod: +mods/catalog_add_locale = Add language +mods/catalog_locale_name = Name in this language +mods/catalog_locale_desc = Description in this language diff --git a/locale/catalog_export_es.properties b/locale/catalog_export_es.properties new file mode 100644 index 0000000..87000da --- /dev/null +++ b/locale/catalog_export_es.properties @@ -0,0 +1,5 @@ +# Catalog export tool — extra keys +mods/catalog_select_mod = Selecciona un mod: +mods/catalog_add_locale = Añadir idioma +mods/catalog_locale_name = Nombre en este idioma +mods/catalog_locale_desc = Descripción en este idioma diff --git a/locale/catalog_export_ja.properties b/locale/catalog_export_ja.properties new file mode 100644 index 0000000..29c3e2e --- /dev/null +++ b/locale/catalog_export_ja.properties @@ -0,0 +1,5 @@ +# Catalog export tool — extra keys +mods/catalog_select_mod = MOD を選択: +mods/catalog_add_locale = 言語を追加 +mods/catalog_locale_name = この言語での名前 +mods/catalog_locale_desc = この言語での説明 diff --git a/locale/catalog_export_ko.properties b/locale/catalog_export_ko.properties new file mode 100644 index 0000000..72f8ebf --- /dev/null +++ b/locale/catalog_export_ko.properties @@ -0,0 +1,5 @@ +# Catalog export tool — extra keys +mods/catalog_select_mod = 모드 선택: +mods/catalog_add_locale = 언어 추가 +mods/catalog_locale_name = 이 언어의 이름 +mods/catalog_locale_desc = 이 언어의 설명 diff --git a/locale/catalog_export_ru.properties b/locale/catalog_export_ru.properties new file mode 100644 index 0000000..9de9806 --- /dev/null +++ b/locale/catalog_export_ru.properties @@ -0,0 +1,5 @@ +# Catalog export tool — дополнительные ключи +mods/catalog_select_mod = Выберите мод: +mods/catalog_add_locale = Добавить язык +mods/catalog_locale_name = Название на этом языке +mods/catalog_locale_desc = Описание на этом языке diff --git a/locale/catalog_export_vi.properties b/locale/catalog_export_vi.properties new file mode 100644 index 0000000..cce0a86 --- /dev/null +++ b/locale/catalog_export_vi.properties @@ -0,0 +1,5 @@ +# Catalog export tool — extra keys +mods/catalog_select_mod = Chọn một mod: +mods/catalog_add_locale = Thêm ngôn ngữ +mods/catalog_locale_name = Tên bằng ngôn ngữ này +mods/catalog_locale_desc = Mô tả bằng ngôn ngữ này diff --git a/locale/catalog_export_zh.properties b/locale/catalog_export_zh.properties new file mode 100644 index 0000000..c1b19c2 --- /dev/null +++ b/locale/catalog_export_zh.properties @@ -0,0 +1,5 @@ +# Catalog export tool — extra keys +mods/catalog_select_mod = 选择模组: +mods/catalog_add_locale = 添加语言 +mods/catalog_locale_name = 此语言的名称 +mods/catalog_locale_desc = 此语言的描述 diff --git a/locale/en.properties b/locale/en.properties new file mode 100644 index 0000000..2815f6a --- /dev/null +++ b/locale/en.properties @@ -0,0 +1,2 @@ +mod_catalog_export_name = Export Catalog Entry +mod_catalog_export_desc = Export Catalog Entry diff --git a/locale/es.properties b/locale/es.properties new file mode 100644 index 0000000..3540df8 --- /dev/null +++ b/locale/es.properties @@ -0,0 +1,2 @@ +mod_catalog_export_name = Exportar entrada del catálogo +mod_catalog_export_desc = Exportar entrada del catálogo diff --git a/locale/ja.properties b/locale/ja.properties new file mode 100644 index 0000000..61a6d14 --- /dev/null +++ b/locale/ja.properties @@ -0,0 +1,2 @@ +mod_catalog_export_name = カタログ項目を書き出す +mod_catalog_export_desc = カタログ項目を書き出す diff --git a/locale/ko.properties b/locale/ko.properties new file mode 100644 index 0000000..944b1aa --- /dev/null +++ b/locale/ko.properties @@ -0,0 +1,2 @@ +mod_catalog_export_name = 카탈로그 항목 내보내기 +mod_catalog_export_desc = 카탈로그 항목 내보내기 diff --git a/locale/ru.properties b/locale/ru.properties new file mode 100644 index 0000000..e54b134 --- /dev/null +++ b/locale/ru.properties @@ -0,0 +1,2 @@ +mod_catalog_export_name = Экспорт записи каталога +mod_catalog_export_desc = Экспорт записи каталога diff --git a/locale/ui_en.properties b/locale/ui_en.properties new file mode 100644 index 0000000..1417971 --- /dev/null +++ b/locale/ui_en.properties @@ -0,0 +1,14 @@ +tools/catalog_export/hint = The archive and hashes are computed on export; the JSON is written next to the mod archive +tools/catalog_export/mod_info = Mod +tools/catalog_export/id = ID +tools/catalog_export/name = Name +tools/catalog_export/metadata = Catalog +tools/catalog_export/locales = Localizations +tools/catalog_export/locales_hint = With no rows the English name and description of the manifest are used. +tools/catalog_export/remove_locale = Remove language +tools/catalog_export/preview = Resulting JSON +tools/catalog_export/copied = JSON copied to the clipboard. +tools/catalog_export/export_tip = Build the archive, compute hashes and write the catalog entry JSON +tools/catalog_export/exported = Exported: {0} +tools/catalog_export/export_error = Export error +tools/catalog_export/url_required = Set the download URL — a catalog entry is useless without it. diff --git a/locale/ui_es.properties b/locale/ui_es.properties new file mode 100644 index 0000000..c95c4ba --- /dev/null +++ b/locale/ui_es.properties @@ -0,0 +1,14 @@ +tools/catalog_export/hint = El archivo y los hashes se calculan al exportar; el JSON se escribe junto al archivo del mod +tools/catalog_export/mod_info = Mod +tools/catalog_export/id = ID +tools/catalog_export/name = Nombre +tools/catalog_export/metadata = Catálogo +tools/catalog_export/locales = Localizaciones +tools/catalog_export/locales_hint = Sin filas se usan el nombre y la descripción en inglés del manifiesto. +tools/catalog_export/remove_locale = Quitar idioma +tools/catalog_export/preview = JSON resultante +tools/catalog_export/copied = JSON copiado al portapapeles. +tools/catalog_export/export_tip = Crear el archivo, calcular los hashes y escribir el JSON de la entrada del catálogo +tools/catalog_export/exported = Exportado: {0} +tools/catalog_export/export_error = Error al exportar +tools/catalog_export/url_required = Indica la URL de descarga: sin ella la entrada del catálogo no sirve. diff --git a/locale/ui_ja.properties b/locale/ui_ja.properties new file mode 100644 index 0000000..aef2ba7 --- /dev/null +++ b/locale/ui_ja.properties @@ -0,0 +1,14 @@ +tools/catalog_export/hint = アーカイブとハッシュは書き出し時に計算され、JSON は MOD のアーカイブの隣に書かれます +tools/catalog_export/mod_info = MOD +tools/catalog_export/id = ID +tools/catalog_export/name = 名前 +tools/catalog_export/metadata = カタログ +tools/catalog_export/locales = ローカライズ +tools/catalog_export/locales_hint = 行がない場合は、マニフェストの英語の名前と説明が使われます。 +tools/catalog_export/remove_locale = 言語を削除 +tools/catalog_export/preview = できあがる JSON +tools/catalog_export/copied = JSON をクリップボードにコピーしました。 +tools/catalog_export/export_tip = アーカイブを作り、ハッシュを計算し、カタログ項目の JSON を書き出す +tools/catalog_export/exported = 書き出しました:{0} +tools/catalog_export/export_error = 書き出しエラー +tools/catalog_export/url_required = ダウンロード URL を設定してください——それがないカタログ項目は役に立ちません。 diff --git a/locale/ui_ko.properties b/locale/ui_ko.properties new file mode 100644 index 0000000..27f0672 --- /dev/null +++ b/locale/ui_ko.properties @@ -0,0 +1,14 @@ +tools/catalog_export/hint = 압축 파일과 해시는 내보낼 때 계산되며, JSON은 모드 압축 파일 옆에 쓰입니다 +tools/catalog_export/mod_info = 모드 +tools/catalog_export/id = ID +tools/catalog_export/name = 이름 +tools/catalog_export/metadata = 카탈로그 +tools/catalog_export/locales = 로컬라이즈 +tools/catalog_export/locales_hint = 줄이 없으면 매니페스트의 영어 이름과 설명이 쓰입니다. +tools/catalog_export/remove_locale = 언어 제거 +tools/catalog_export/preview = 결과 JSON +tools/catalog_export/copied = JSON을 클립보드에 복사했습니다. +tools/catalog_export/export_tip = 압축 파일을 만들고 해시를 계산해 카탈로그 항목 JSON을 씁니다 +tools/catalog_export/exported = 내보냄: {0} +tools/catalog_export/export_error = 내보내기 오류 +tools/catalog_export/url_required = 다운로드 URL을 설정하세요 — 그것 없는 카탈로그 항목은 쓸모가 없습니다. diff --git a/locale/ui_ru.properties b/locale/ui_ru.properties new file mode 100644 index 0000000..1abe1b2 --- /dev/null +++ b/locale/ui_ru.properties @@ -0,0 +1,14 @@ +tools/catalog_export/hint = Архив и хэши считаются при экспорте; JSON кладётся рядом с архивом мода +tools/catalog_export/mod_info = Мод +tools/catalog_export/id = ID +tools/catalog_export/name = Название +tools/catalog_export/metadata = Каталог +tools/catalog_export/locales = Локализации +tools/catalog_export/locales_hint = Без строк будет использовано английское название и описание из манифеста. +tools/catalog_export/remove_locale = Убрать язык +tools/catalog_export/preview = Итоговый JSON +tools/catalog_export/copied = JSON скопирован в буфер обмена. +tools/catalog_export/export_tip = Собрать архив, посчитать хэши и записать JSON записи каталога +tools/catalog_export/exported = Экспортировано: {0} +tools/catalog_export/export_error = Ошибка экспорта +tools/catalog_export/url_required = Укажите URL скачивания — без него запись каталога бесполезна. diff --git a/locale/ui_vi.properties b/locale/ui_vi.properties new file mode 100644 index 0000000..121dd7a --- /dev/null +++ b/locale/ui_vi.properties @@ -0,0 +1,14 @@ +tools/catalog_export/hint = Tệp nén và mã băm được tính khi xuất; JSON được ghi cạnh tệp nén của mod +tools/catalog_export/mod_info = Mod +tools/catalog_export/id = ID +tools/catalog_export/name = Tên +tools/catalog_export/metadata = Danh mục +tools/catalog_export/locales = Bản địa hóa +tools/catalog_export/locales_hint = Nếu không có dòng nào thì dùng tên và mô tả tiếng Anh trong manifest. +tools/catalog_export/remove_locale = Bỏ ngôn ngữ +tools/catalog_export/preview = JSON kết quả +tools/catalog_export/copied = Đã sao chép JSON vào bộ nhớ tạm. +tools/catalog_export/export_tip = Tạo tệp nén, tính mã băm và ghi JSON của mục danh mục +tools/catalog_export/exported = Đã xuất: {0} +tools/catalog_export/export_error = Lỗi xuất +tools/catalog_export/url_required = Hãy đặt URL tải xuống — thiếu nó thì mục danh mục vô dụng. diff --git a/locale/ui_zh.properties b/locale/ui_zh.properties new file mode 100644 index 0000000..5a86113 --- /dev/null +++ b/locale/ui_zh.properties @@ -0,0 +1,14 @@ +tools/catalog_export/hint = 归档和哈希在导出时计算;JSON 写在模组归档旁边 +tools/catalog_export/mod_info = 模组 +tools/catalog_export/id = ID +tools/catalog_export/name = 名称 +tools/catalog_export/metadata = 目录 +tools/catalog_export/locales = 本地化 +tools/catalog_export/locales_hint = 没有任何行时,使用清单中的英文名称和描述。 +tools/catalog_export/remove_locale = 移除语言 +tools/catalog_export/preview = 生成的 JSON +tools/catalog_export/copied = JSON 已复制到剪贴板。 +tools/catalog_export/export_tip = 打包归档、计算哈希并写入目录条目的 JSON +tools/catalog_export/exported = 已导出:{0} +tools/catalog_export/export_error = 导出错误 +tools/catalog_export/url_required = 请设置下载 URL——没有它的目录条目毫无用处。 diff --git a/locale/vi.properties b/locale/vi.properties new file mode 100644 index 0000000..9d5ac4d --- /dev/null +++ b/locale/vi.properties @@ -0,0 +1,2 @@ +mod_catalog_export_name = Xuất mục danh mục +mod_catalog_export_desc = Xuất mục danh mục diff --git a/locale/zh.properties b/locale/zh.properties new file mode 100644 index 0000000..bfc163d --- /dev/null +++ b/locale/zh.properties @@ -0,0 +1,2 @@ +mod_catalog_export_name = 导出目录条目 +mod_catalog_export_desc = 导出目录条目 diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..625bd39 --- /dev/null +++ b/manifest.json @@ -0,0 +1,15 @@ +{ + "id": "catalog_export", + "name": "Catalog Export", + "version": "1.0.0", + "author": "ct", + "description": "Export mod catalog metadata and installable archives.", + "category": "tools, editor", + "sources": [ + "src/**/*.cs" + ], + "depends": [ + "tools_base>=1", + "ui_base>=1" + ] +} diff --git a/mod.jpg b/mod.jpg new file mode 100644 index 0000000..f66f164 Binary files /dev/null and b/mod.jpg differ diff --git a/src/CatalogExport/CatalogExportToolBoxButton.cs b/src/CatalogExport/CatalogExportToolBoxButton.cs new file mode 100644 index 0000000..da843da --- /dev/null +++ b/src/CatalogExport/CatalogExportToolBoxButton.cs @@ -0,0 +1,18 @@ +namespace Cthangover.Core.UI.Tool.CatalogExport +{ + /// Exposes catalog export in the in-game toolbar's tools list. + public class CatalogExportToolBoxButton : IToolBoxButton + { + /// + public string ToolId => "catalog_export"; + + /// + public string IconPath => ""; + + /// + public string LocaleKey => "mods/catalog_title"; + + /// + public bool IsVisible() => true; + } +} diff --git a/src/CatalogExport/CatalogExportToolProvider.cs b/src/CatalogExport/CatalogExportToolProvider.cs new file mode 100644 index 0000000..042575e --- /dev/null +++ b/src/CatalogExport/CatalogExportToolProvider.cs @@ -0,0 +1,23 @@ +using Cthangover.Core.UI.Tool; +using Godot; + +namespace Cthangover.Core.UI.Tool.CatalogExport +{ + /// + /// Registers the catalog export window as a developer tool. Moved here from + /// the old mods menu when mod management UI was delegated to the + /// mod_manager mod — exporting a catalog entry is an authoring task, + /// not part of day-to-day mod management. + /// + public class CatalogExportToolProvider : IToolProvider + { + /// Unique tool identifier. + public string Id => "catalog_export"; + + /// Translation key for the tool's display name. + public string LocaleKey => "mods/catalog_title"; + + /// Creates the export window instance. + public Window CreateWindow() => new CatalogExportWindow(); + } +} diff --git a/src/CatalogExport/CatalogExportWindow.cs b/src/CatalogExport/CatalogExportWindow.cs new file mode 100644 index 0000000..cb0b21a --- /dev/null +++ b/src/CatalogExport/CatalogExportWindow.cs @@ -0,0 +1,367 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using Cthangover.Core.Mods; +using Cthangover.Core.UI.Theming; +using Cthangover.Core.Utils; +using Cthangover.Mods.Interface.Theming; +using Cthangover.Mods.Interface.Tool; +using Cthangover.Tools.Common; +using Godot; + +namespace Cthangover.Core.UI.Tool.CatalogExport +{ + /// + /// Developer tool that builds a catalog entry (JSON for the online mod + /// catalog) from a locally installed mod's manifest. Ported out of the old + /// mods menu so that mod-management UI can live in the mod_manager + /// mod while this authoring utility stays a tool. + /// + /// Left: the form — read-only manifest data, catalog-only metadata + /// (download URL, categories, tags, changelog, status, minimum game + /// version, icon URL) and localized names. Right: the live JSON preview. + /// Actions: copy the JSON or export it next to the mod's zip via + /// (archive and hashes are computed there). + /// + public partial class CatalogExportWindow : ToolWindow + { + private static readonly string[] Statuses = { "stable", "beta", "deprecated", "archived" }; + private static readonly CategoryType[] Categories = Enum.GetValues(); + + private OptionButton _modSelect; + private VBoxContainer _formHost; + private CodeEdit _previewEdit; + + private ModInfo _modInfo; + private ModManifest _manifest; + private LineEdit _urlEdit; + private readonly List _categoryChecks = new(); + private LineEdit _tagsEdit; + private TextEdit _changelogEdit; + private OptionButton _statusOpt; + private LineEdit _minGameVerEdit; + private LineEdit _iconUrlEdit; + private VBoxContainer _localeContainer; + + public CatalogExportWindow() : base("mods/catalog_title") + { + Size = new Vector2I(1200, 820); + CthangoverTheme.Apply(this); + + var outer = CreateFillContainer(); + AddChild(outer); + + var toolbar = CreateToolbar(); + outer.AddChild(toolbar); + _modSelect = new OptionButton { CustomMinimumSize = new Vector2(320, 0), TooltipText = ToolUi.Tr("mods/catalog_select_mod") }; + _modSelect.ItemSelected += OnModSelected; + toolbar.AddChild(_modSelect); + toolbar.AddChild(UiKit.Spacer()); + toolbar.AddChild(ToolUi.Button("mods/catalog_copy", OnCopyPressed)); + toolbar.AddChild(ToolUi.Button("mods/catalog_export_file", OnExportPressed, ButtonKind.Primary, "tools/catalog_export/export_tip")); + + var split = new HSplitContainer + { + SizeFlagsHorizontal = Control.SizeFlags.ExpandFill, + SizeFlagsVertical = Control.SizeFlags.ExpandFill + }; + outer.AddChild(split); + + _formHost = new VBoxContainer(); + var form = CreateSidebar(_formHost, 520); + form.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill; + split.AddChild(form); + + var previewColumn = new VBoxContainer + { + SizeFlagsHorizontal = Control.SizeFlags.ExpandFill, + SizeFlagsVertical = Control.SizeFlags.ExpandFill + }; + previewColumn.AddChild(ToolUi.Section("tools/catalog_export/preview")); + _previewEdit = ToolUi.Code(editable: false); + previewColumn.AddChild(_previewEdit); + split.AddChild(previewColumn); + + SetHint(ToolUi.Tr("tools/catalog_export/hint")); + PopulateModList(); + } + + private void PopulateModList() + { + var ordered = ModRegistry.Instance.GetOrderedModIds(filterDisabled: false); + foreach (var id in ordered) + { + var info = ModRegistry.Instance.GetMod(id); + if (info == null) + continue; + + _modSelect.AddItem($"{info.DisplayTitle} ({id})"); + _modSelect.SetItemMetadata(_modSelect.ItemCount - 1, id); + } + + if (_modSelect.ItemCount > 0) + { + _modSelect.Selected = 0; + BuildForm(_modSelect.GetItemMetadata(0).AsString()); + } + } + + private void OnModSelected(long index) + { + BuildForm(_modSelect.GetItemMetadata((int)index).AsString()); + } + + private void BuildForm(string modId) + { + foreach (var child in _formHost.GetChildren()) + child.QueueFree(); + _categoryChecks.Clear(); + + _modInfo = ModRegistry.Instance.GetMod(modId) as ModInfo; + _manifest = _modInfo?.Manifest; + if (_modInfo == null || _manifest == null) + return; + + _formHost.AddChild(ToolUi.Section("tools/catalog_export/mod_info")); + AddReadOnly("tools/catalog_export/id", _modInfo.Id); + AddReadOnly("tools/catalog_export/name", _modInfo.DisplayTitle); + AddReadOnly("mods/version", _modInfo.Version ?? "—"); + AddReadOnly("mods/author", _modInfo.Author ?? "—"); + + _formHost.AddChild(ToolUi.Section("tools/catalog_export/metadata")); + _urlEdit = ToolUi.Field(_formHost, "mods/catalog_download_url", ToolUi.Text("https://…/" + modId + ".zip", _ => UpdatePreview())); + _statusOpt = ToolUi.Field(_formHost, "mods/catalog_status", new OptionButton()); + foreach (var status in Statuses) + _statusOpt.AddItem(status); + _statusOpt.ItemSelected += _ => UpdatePreview(); + _minGameVerEdit = ToolUi.Field(_formHost, "mods/catalog_min_game_version", ToolUi.Text("1.0.0", _ => UpdatePreview())); + _iconUrlEdit = ToolUi.Field(_formHost, "mods/catalog_icon_url", ToolUi.Text("https://…/icon.png", _ => UpdatePreview())); + _tagsEdit = ToolUi.Field(_formHost, "mods/catalog_tags", ToolUi.Text("story, ru", _ => UpdatePreview())); + + _formHost.AddChild(ToolUi.Section("mods/catalog_category")); + var grid = new GridContainer { Columns = 3 }; + grid.AddThemeConstantOverride("h_separation", UiTokens.Space5); + var preselected = _manifest.Category ?? CategoryType.Other; + foreach (var category in Categories) + { + var check = new CheckBox { Text = GetCategoryDisplayName(category), ButtonPressed = (preselected & category) != 0 }; + check.Toggled += _ => UpdatePreview(); + _categoryChecks.Add(check); + grid.AddChild(check); + } + _formHost.AddChild(grid); + + _formHost.AddChild(ToolUi.Section("mods/catalog_changelog")); + _changelogEdit = new TextEdit + { + CustomMinimumSize = new Vector2(0, 80), + WrapMode = TextEdit.LineWrappingMode.Boundary, + }; + _changelogEdit.TextChanged += UpdatePreview; + _formHost.AddChild(_changelogEdit); + + _formHost.AddChild(ToolUi.Section("tools/catalog_export/locales")); + _formHost.AddChild(ToolUi.Hint(ToolUi.Tr("tools/catalog_export/locales_hint"))); + _localeContainer = new VBoxContainer(); + _localeContainer.AddThemeConstantOverride("separation", UiTokens.Space1); + _formHost.AddChild(_localeContainer); + _formHost.AddChild(ToolUi.Button("mods/catalog_add_locale", () => AddLocaleRow(string.Empty, string.Empty, string.Empty))); + + UpdatePreview(); + SetStatus(string.Empty); + } + + private void AddReadOnly(string labelKey, string value) + { + var label = UiKit.Label(value, UiTokens.FontBody, UiTokens.Body); + label.AddThemeFontOverride("font", ToolUi.Mono()); + ToolUi.Field(_formHost, labelKey, label); + } + + private static string GetCategoryDisplayName(CategoryType cat) => cat switch + { + CategoryType.Story => ToolUi.Tr("mods/category_story"), + CategoryType.Characters => ToolUi.Tr("mods/category_characters"), + CategoryType.Gameplay => ToolUi.Tr("mods/category_gameplay"), + CategoryType.Visual => ToolUi.Tr("mods/category_visual"), + CategoryType.Audio => ToolUi.Tr("mods/category_audio"), + CategoryType.UI => ToolUi.Tr("mods/category_ui"), + CategoryType.Locale => ToolUi.Tr("mods/category_locale"), + CategoryType.Library => ToolUi.Tr("mods/category_library"), + CategoryType.Engine => ToolUi.Tr("mods/category_engine"), + CategoryType.Overhaul => ToolUi.Tr("mods/category_overhaul"), + CategoryType.Tools => ToolUi.Tr("mods/category_tools"), + CategoryType.Editor => ToolUi.Tr("mods/category_editor"), + CategoryType.Debug => ToolUi.Tr("mods/category_debug"), + CategoryType.Other => ToolUi.Tr("mods/category_other"), + _ => cat.ToString(), + }; + + private void AddLocaleRow(string lang, string name, string description) + { + var row = new HBoxContainer(); + row.AddThemeConstantOverride("separation", UiTokens.Space1); + + var langEdit = new LineEdit { CustomMinimumSize = new Vector2(52, 0), PlaceholderText = "en", MaxLength = 5, Text = lang }; + row.AddChild(langEdit); + var nameEdit = new LineEdit { SizeFlagsHorizontal = Control.SizeFlags.ExpandFill, PlaceholderText = ToolUi.Tr("mods/catalog_locale_name"), Text = name }; + row.AddChild(nameEdit); + var descEdit = new LineEdit { SizeFlagsHorizontal = Control.SizeFlags.ExpandFill, PlaceholderText = ToolUi.Tr("mods/catalog_locale_desc"), Text = description }; + row.AddChild(descEdit); + + var removeBtn = UiKit.IconButton("✕", ToolUi.Tr("tools/catalog_export/remove_locale")); + removeBtn.Pressed += () => RemoveLocaleRow(row); + row.AddChild(removeBtn); + + langEdit.TextChanged += _ => UpdatePreview(); + nameEdit.TextChanged += _ => UpdatePreview(); + descEdit.TextChanged += _ => UpdatePreview(); + + _localeContainer.AddChild(row); + UpdatePreview(); + } + + private void RemoveLocaleRow(HBoxContainer row) + { + _localeContainer.RemoveChild(row); + row.QueueFree(); + UpdatePreview(); + } + + private void UpdatePreview() + { + if (_previewEdit == null || _modInfo == null || _manifest == null || _changelogEdit == null) + return; + + var entry = BuildExportEntry(); + // Hashes are computed by the exporter from the real archive. + entry.Downloads[0].ArchiveSha256 = "computed-on-export"; + entry.Downloads[0].InstallSha256 = "computed-on-export"; + _previewEdit.Text = JsonSerializer.Serialize(entry, JsonOptionsProvider.WriteIndentedIgnoreNullOptions); + } + + private List CollectLocales() + { + var list = new List(); + foreach (var child in _localeContainer.GetChildren()) + { + if (child is not HBoxContainer row || row.IsQueuedForDeletion() || row.GetChildCount() < 3) + continue; + + var lang = ((LineEdit)row.GetChild(0)).Text.Trim(); + if (lang.Length == 0) + continue; + + list.Add(new CatalogLocaleEntry + { + Lang = lang, + Name = NullIfEmpty(((LineEdit)row.GetChild(1)).Text), + Description = NullIfEmpty(((LineEdit)row.GetChild(2)).Text), + }); + } + + if (list.Count == 0) + list.Add(new CatalogLocaleEntry { Lang = "en", Name = _manifest.Name, Description = _manifest.Description }); + + return list; + } + + private CatalogExportEntry BuildExportEntry() + { + var entry = BuildEntryFromManifest(_manifest); + entry.Downloads = new List + { + new() + { + Version = entry.Version, + Urls = new List { _urlEdit.Text.Trim() }, + SizeBytes = 0, + Changelog = NullIfEmpty(_changelogEdit.Text), + }, + }; + entry.Category = CollectSelectedCategories(); + entry.Tags = ParseTags(_tagsEdit.Text); + entry.Status = Statuses[Math.Max(0, _statusOpt.Selected)]; + entry.MinGameVersion = NullIfEmpty(_minGameVerEdit.Text); + entry.IconUrl = NullIfEmpty(_iconUrlEdit.Text); + entry.Locale = CollectLocales(); + return entry; + } + + private void OnCopyPressed() + { + DisplayServer.ClipboardSet(_previewEdit.Text); + SetStatus(ToolUi.Tr("tools/catalog_export/copied"), ToolStatusKind.Ok); + } + + private void OnExportPressed() + { + if (_modInfo == null) + return; + + if (string.IsNullOrWhiteSpace(_urlEdit.Text)) + { + SetStatus(ToolUi.Tr("tools/catalog_export/url_required"), ToolStatusKind.Error); + return; + } + + try + { + var entry = BuildExportEntry(); + var modsRoot = ModRegistry.ModsPath; + var isZip = _modInfo.FileProvider.RootPath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase); + var path = isZip + ? ModCatalogExporter.ExportFromZip(_modInfo, entry, modsRoot) + : ModCatalogExporter.ExportFromFolder(_modInfo, entry, modsRoot); + SetStatus(string.Format(ToolUi.Tr("tools/catalog_export/exported"), path), ToolStatusKind.Ok); + } + catch (Exception ex) + { + GameLogger.Log("CATALOG_EXPORT", $"export of '{_modInfo.Id}' failed: {ex.Message}", LogLevel.Error); + SetStatus($"{ToolUi.Tr("tools/catalog_export/export_error")}: {ex.Message}", ToolStatusKind.Error); + } + } + + private static List ParseTags(string text) + { + if (string.IsNullOrWhiteSpace(text)) + return null; + return text.Split(',').Select(t => t.Trim()).Where(t => t.Length > 0).ToList(); + } + + private static string NullIfEmpty(string text) + { + var trimmed = text?.Trim(); + return string.IsNullOrEmpty(trimmed) ? null : trimmed; + } + + private static CatalogExportEntry BuildEntryFromManifest(ModManifest manifest) + { + var depends = manifest.Depends?.Select(d => d.ToString()).Where(s => s.Length > 0).ToList(); + + return new CatalogExportEntry + { + Id = manifest.Id, + Name = manifest.Name, + Author = manifest.Author, + Version = manifest.Version, + Description = manifest.Description, + Category = manifest.Category ?? CategoryType.Other, + Depends = depends, + ConflictsWith = manifest.ConflictsWith, + }; + } + + private CategoryType CollectSelectedCategories() + { + var selected = (CategoryType)0; + for (var i = 0; i < Categories.Length && i < _categoryChecks.Count; i++) + { + if (_categoryChecks[i].ButtonPressed) + selected |= Categories[i]; + } + return selected != 0 ? selected : CategoryType.Other; + } + } +}