Compare commits

...
10 Commits
70 changed files with 4692 additions and 316 deletions

No files matched your search

+38
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

+73
View File
@@ -0,0 +1,73 @@
# Editor Tools — making scenes and scenarios inside the running game
Editor Tools are the author's windows that open over the running game: the scene editor, the scenario editor, editors for the world map, interactive objects and lights, a builder of Godot scenes and the catalog export of a mod. Everything they save is ordinary mod files, and the game picks the change up at once, so an author writes a place, walks into it and plays it without restarting.
The mod also holds the base of the item and character editors (they are separate mods: Items Editor, Characters Editor). A game gets these tools through the Developer Tools toolset or its own collection.
## Opening the tools
In a game, the hammer button on the toolbar opens the tool box:
![The toolbar of the novel template with the hammer pressed and the tool box open: Interactive Editor, Light Editor, Map Editor, Scene editor.](assets/toolbox.jpg)
- **Scene editor**, **Map Editor**, **Interactive Editor** and **Light Editor** are in the tool box. Other tool mods add their own entries there (the item, character and RPG map editors, the Data Editor).
- The **scenario editor** opens from the scene editor: select a scene, pick one of its scenarios and press **Open**, or **+ New scenario**.
- When the tools are loaded at the main menu (for example with `--all-mods`), every tool also has a button in the menu's tool block, including the scene builder and the catalog export.
The windows stay open across scene changes, can be moved and resized, and close with the cross.
## A whole small edit: a new place
The novel template has a town, a bar and a home. Here an author adds a park that can be reached from the town, using only the editors.
**1. A new scene.** In the scene editor press **+ New scene**, type the name, choose the mod to save it to and add a background:
![The scene editor: the scenes my_novel_bar, my_novel_home and my_novel_town of my_novel_game on the left; in the middle the background home/vestibule added to the scene; on the right Name my_novel_park, Save to my_novel_game, Sound none, Default scenario none.](assets/new-scene.jpg)
Save (Ctrl+S) writes `scenes/my_novel_park.json` into `my_novel_game`; the game knows the scene at once.
**2. Its scenario.** **+ New scenario** opens the scenario editor with a file for the scene. The park offers one way back:
![The scenario editor with my_novel_park.scenario: scene my_novel_park, priority 10, the narration "A quiet park behind the town. Wet leaves, an empty bench.", the choice "What next?" with "Back to the town" leading to switch_scene my_novel_town.](assets/park-scenario.jpg)
**3. A way there.** In the town's scenario one answer and one label are added:
![The scenario editor with my_novel_town.scenario: the choice "Where to go?" now has the answer "Go to the park" -> :park, and the label :park does switch_scene my_novel_park.](assets/town-scenario.jpg)
```scenario
option "Go to the park" -> :park
:park
switch_scene my_novel_park
end
```
Saving a scenario drops the game's scenario caches, so the next entry into the town plays the new text.
**4. Play it.** **Go to scene** in the scene editor, or simply playing on, enters the town, and the new answer is there:
![The town in the running game: "Where to go?" with Go into the bar, Go to the park, Go back home.](assets/town-choice.jpg)
![The new park after choosing Go to the park: "What next?" with Back to the town.](assets/park.jpg)
## The editors
- **Scene editor** — the places of a novel: name, the mod to save to, backgrounds (one is picked at random on entry), ambient sound with a listen button, the default scenario and the scene's scenarios. **Go to scene** ends the running dialog and enters the scene.
- **Scenario editor** — the files of every mod, a summary and a graph of scenes and transitions; DSL highlighting; Ctrl+S saves; **Play** (F5) saves and plays the scenario inside the running game, and Stop returns to where the game was; **Open in external editor** opens the file in the system's editor.
- **Map Editor** — the zones of the world map.
- **Interactive Editor** — clickable objects of the current scene.
- **Light Editor** — the lights of the current scene.
- **Scene builder** — Godot `.tscn` scenes for mods.
- **Catalog export** — packs a mod into a zip and writes its entry for a mod catalog with checksums.
Undo works while editing (Ctrl+Z in the text and in the forms of the entity editors). Checking a mod for broken references is the Validator's job; the editors do not stop you from saving a scenario with a mistake.
## Whose files are written
The tools write only into mods installed as folders. A scene is the sum of the `scenes/<name>.json` files of all mods, so the scene editor writes the file of the mod chosen in **Save to**. Changing a scene that belongs to another or a packed mod creates a file in your mod that adds to it; backgrounds that come from other files are shown locked. Scenarios are saved in the mod they belong to.
## If something goes wrong
1. **The hammer button is missing.** The game does not include the tools; add the Developer Tools toolset in its collection or run with `--with-mods=editor_tools`.
2. **A new scenario does not play.** Its header names another scene, or another scenario of the scene is chosen first (compare their `priority`).
3. **Save to lists no mods.** Only mods installed as folders can be written to.
+73
View File
@@ -0,0 +1,73 @@
# Editor Tools — crear escenas y escenarios dentro del juego en marcha
Editor Tools son las ventanas de autor que se abren sobre el juego en marcha: el editor de escenas, el de escenarios, los editores del mapa del mundo, de objetos interactivos y de luces, un constructor de escenas de Godot y la exportación al catálogo de un mod. Todo lo que guardan son archivos de mod normales, y el juego toma el cambio al instante, así que puedes escribir un lugar, entrar en él y jugarlo sin reiniciar.
El mod también contiene la base de los editores de objetos y de personajes (que son mods aparte: Items Editor y Characters Editor). Un juego obtiene estas herramientas mediante el conjunto Developer Tools o con su propia colección.
## Cómo abrir las herramientas
Dentro de un juego, el botón del martillo de la barra de herramientas abre la caja de herramientas:
![La barra de herramientas de la plantilla de novela con el martillo pulsado y la caja de herramientas abierta: Interactive Editor, Light Editor, Map Editor, Scene editor.](assets/toolbox.jpg)
- **Editor de escenas**, **Editor de mapas**, **Editor de interactivos** y **Editor de luces** están en la caja. Otros mods de herramientas añaden ahí sus propias entradas (los editores de objetos, de personajes y de mapas RPG, el editor de datos).
- El **editor de escenarios** se abre desde el editor de escenas: elige una escena, selecciona uno de sus escenarios y pulsa **Abrir**, o **+ Nuevo escenario**.
- Si las herramientas se cargan en el menú principal (por ejemplo con `--all-mods`), cada una tiene también un botón en el bloque de herramientas del menú, incluidos el constructor de escenas y la exportación al catálogo.
Las ventanas siguen abiertas al cambiar de escena, se pueden mover y redimensionar, y se cierran con la cruz.
## Una edición pequeña completa: un lugar nuevo
La plantilla de novela tiene un pueblo, un bar y una casa. Aquí añadiremos un parque al que se llega desde el pueblo usando solo los editores.
**1. Una escena nueva.** En el editor de escenas pulsa **+ Nueva escena**, escribe el nombre, elige el mod donde guardarla y añade un fondo:
![El editor de escenas: a la izquierda, las escenas my_novel_bar, my_novel_home y my_novel_town de my_novel_game; en el centro, el fondo home/vestibule añadido a la escena; a la derecha, Nombre my_novel_park, Guardar en my_novel_game, Sonido ninguno, Escenario por defecto ninguno.](assets/new-scene.jpg)
Guardar (Ctrl+S) escribe `scenes/my_novel_park.json` en `my_novel_game`; el juego conoce la escena al instante.
**2. Su escenario.** **+ Nuevo escenario** abre el editor de escenarios con un archivo para la escena. El parque ofrece una única salida:
![El editor de escenarios con my_novel_park.scenario: escena my_novel_park, prioridad 10, la narración «Un parque tranquilo detrás del pueblo. Hojas mojadas, un banco vacío.», la elección «¿Qué hago ahora?» con «Volver al pueblo» que lleva a switch_scene my_novel_town.](assets/park-scenario.jpg)
**3. Un camino hasta allí.** En el escenario del pueblo se añaden una respuesta y una etiqueta:
![El editor de escenarios con my_novel_town.scenario: la elección «¿Adónde ir?» tiene ahora la respuesta «Ir al parque» -> :park, y la etiqueta :park hace switch_scene my_novel_park.](assets/town-scenario.jpg)
```scenario
option "Ir al parque" -> :park
:park
switch_scene my_novel_park
end
```
Guardar un escenario descarta las cachés de escenarios del juego, así que la próxima entrada al pueblo ya reproduce el texto nuevo.
**4. Juégalo.** **Ir a la escena** en el editor de escenas, o simplemente seguir jugando, entra en el pueblo, y la nueva respuesta ya está ahí:
![El pueblo en el juego en marcha: «¿Adónde ir?» con Entrar en el bar, Ir al parque, Volver a casa.](assets/town-choice.jpg)
![El parque nuevo tras elegir Ir al parque: «¿Qué hago ahora?» con Volver al pueblo.](assets/park.jpg)
## Los editores
- **Editor de escenas**: los lugares de una novela: nombre, el mod donde guardar, fondos (al entrar se elige uno al azar), sonido ambiente con botón de escucha, el escenario por defecto y los escenarios de la escena. **Ir a la escena** termina el diálogo en curso y entra en la escena.
- **Editor de escenarios**: los archivos de todos los mods, un resumen y un grafo de escenas y transiciones; resaltado de DSL; Ctrl+S guarda; **Jugar** (F5) guarda y reproduce el escenario dentro del juego en marcha, y Detener vuelve a donde estaba el juego; **Abrir en un editor externo** abre el archivo en el editor del sistema.
- **Editor de mapas**: las zonas del mapa del mundo.
- **Editor de interactivos**: los objetos clicables de la escena actual.
- **Editor de luces**: las luces de la escena actual.
- **Constructor de escenas**: escenas `.tscn` de Godot para los mods.
- **Exportación al catálogo**: empaqueta un mod en un zip y escribe su entrada para un catálogo de mods con sumas de comprobación.
Deshacer funciona mientras editas (Ctrl+Z en el texto y en los formularios de los editores de objetos y personajes). Comprobar un mod en busca de referencias rotas es tarea de Validator; los editores no te impiden guardar un escenario con un error.
## De quién son los archivos que se escriben
Las herramientas solo escriben en mods instalados como carpetas. Una escena es la suma de los archivos `scenes/<name>.json` de todos los mods, así que el editor de escenas escribe el archivo del mod elegido en **Guardar en**. Si cambias una escena que pertenece a otro mod o a un mod empaquetado, se crea un archivo en tu mod que la amplía; los fondos que vienen de otros archivos se muestran bloqueados. Los escenarios se guardan en el mod al que pertenecen.
## Si algo sale mal
1. **No aparece el botón del martillo.** El juego no incluye las herramientas; añade el conjunto Developer Tools a su colección o ejecútalo con `--with-mods=editor_tools`.
2. **Un escenario nuevo no se reproduce.** Su cabecera nombra otra escena, o antes se elige otro escenario de la escena (compara sus `priority`).
3. **Guardar en no muestra ningún mod.** Solo se puede escribir en mods instalados como carpetas.
+73
View File
@@ -0,0 +1,73 @@
# Editor Tools — 実行中のゲーム内でシーンとシナリオを作る
Editor Tools は、実行中のゲームの上に開く作者向けのウィンドウ群です。シーンエディター、シナリオエディター、ワールドマップ・インタラクティブ・ライトの各エディター、Godot シーンのビルダー、MOD カタログの書き出しが含まれます。保存されるのはどれも普通の MOD ファイルで、ゲームは変更をすぐに取り込みます。そのため、場所を書いて、その場に入って遊ぶまでを、再起動なしで行えます。
この MOD には、アイテムエディターとキャラクターエディターの土台も入っています(本体は別の MOD、Items Editor と Characters Editor です)。ゲームにこれらのツールを入れるには、Developer Tools ツールセットか、そのゲーム独自のコレクションを使います。
## ツールを開く
ゲーム内では、ツールバーのハンマーのボタンでツールボックスが開きます:
![ノベルのテンプレートのツールバー。ハンマーが押され、ツールボックスが開いている: Interactive Editor、Light Editor、Map Editor、Scene editor。](assets/toolbox.jpg)
- ツールボックスには**シーンエディター**、**マップエディター**、**インタラクティブエディター**、**ライトエディター**が入っています。他のツール MOD もここに項目を追加します(アイテム、キャラクター、RPG マップの各エディター、データエディター)。
- **シナリオエディター**はシーンエディターから開きます。シーンを選び、そのシナリオのひとつを選んで**開く**を押すか、**+ 新しいシナリオ**を押します。
- メインメニューでツールを読み込んでいる場合(たとえば `--all-mods`)、メニューのツール欄にも各ツールのボタンが並びます。シーンビルダーとカタログの書き出しも含まれます。
ウィンドウはシーンを切り替えても開いたまま残り、移動やサイズ変更ができ、×で閉じます。
## 小さな編集をひと通り: 新しい場所
ノベルのテンプレートには、町、バー、家があります。ここではエディターだけを使って、町から行ける公園を追加します。
**1. 新しいシーン。** シーンエディターで **+ 新しいシーン**を押し、名前を入力し、保存先の MOD を選んで背景を追加します:
![シーンエディター: 左に my_novel_game の my_novel_bar、my_novel_home、my_novel_town。中央にはシーンに追加した背景 home/vestibule。右は名前 my_novel_park、保存先 my_novel_game、音 なし、既定のシナリオ なし。](assets/new-scene.jpg)
保存(Ctrl+S)すると `scenes/my_novel_park.json` が `my_novel_game` に書き込まれ、ゲームはそのシーンをすぐに認識します。
**2. そのシナリオ。** **+ 新しいシナリオ**を押すと、そのシーンのファイルを開いたシナリオエディターが立ち上がります。公園からは戻る道が一本だけあります:
![my_novel_park.scenario を開いたシナリオエディター: シーン my_novel_park、優先度 10、ナレーション「町の裏手にある静かな公園。濡れた落ち葉、誰もいないベンチ。」、選択肢「次はどうする?」の「町へ戻る」が switch_scene my_novel_town へ続く。](assets/park-scenario.jpg)
**3. そこへ行く道。** 町のシナリオに、答えをひとつとラベルをひとつ追加します:
![my_novel_town.scenario を開いたシナリオエディター: 選択肢「どこへ行く?」に「公園へ行く」-> :park が加わり、ラベル :park が switch_scene my_novel_park を実行する。](assets/town-scenario.jpg)
```scenario
option "公園へ行く" -> :park
:park
switch_scene my_novel_park
end
```
シナリオを保存するとゲームのシナリオキャッシュが破棄されるので、次に町へ入ったときには新しいテキストが再生されます。
**4. 遊んでみる。** シーンエディターの**シーンへ移動**を押すか、そのまま遊び続けると町に入り、新しい答えがそこにあります:
![実行中のゲームの町:「どこへ行く?」に、バーに入る、公園へ行く、家に帰る。](assets/town-choice.jpg)
![「公園へ行く」を選んだあとの新しい公園:「次はどうする?」に、町へ戻る。](assets/park.jpg)
## 各エディター
- **シーンエディター** — ノベルの場所: 名前、保存先の MOD、背景(入るたびにひとつがランダムで選ばれます)、試聴ボタン付きの環境音、既定のシナリオ、そのシーンのシナリオ。**シーンへ移動**は進行中のダイアログを終わらせてシーンに入ります。
- **シナリオエディター** — すべての MOD のファイル、概要、シーンと遷移のグラフ、DSL のハイライト、Ctrl+S で保存。**プレイ**(F5)は保存して実行中のゲーム内でシナリオを再生し、停止するとゲームが元いた場所へ戻ります。**外部エディターで開く**はシステムのエディターでファイルを開きます。
- **マップエディター** — ワールドマップのゾーン。
- **インタラクティブエディター** — 現在のシーンのクリックできるオブジェクト。
- **ライトエディター** — 現在のシーンのライト。
- **シーンビルダー** — MOD 用の Godot `.tscn` シーン。
- **カタログの書き出し** — MOD を zip にまとめ、チェックサム付きで MOD カタログ用のエントリーを書き出します。
編集中は元に戻せます(テキスト内と、アイテム・キャラクターエディターのフォームで Ctrl+Z)。壊れた参照の検査は Validator の仕事で、エディターは間違いのあるシナリオの保存を止めません。
## どの MOD のファイルが書かれるか
ツールが書き込むのは、フォルダーとしてインストールされた MOD だけです。シーンは全 MOD の `scenes/<name>.json` の合計なので、シーンエディターは**保存先**で選んだ MOD のファイルを書き込みます。他の MOD やパッケージ済みの MOD に属するシーンを変更すると、それを補うファイルがあなたの MOD に作られ、他のファイル由来の背景はロックされて表示されます。シナリオは所属する MOD に保存されます。
## うまくいかないとき
1. **ハンマーのボタンがない。** そのゲームにツールが入っていません。コレクションに Developer Tools ツールセットを追加するか、`--with-mods=editor_tools` で起動してください。
2. **新しいシナリオが再生されない。** ヘッダーが別のシーンを指しているか、そのシーンの別のシナリオが先に選ばれています(`priority` を比べてください)。
3. **保存先に MOD が出ない。** 書き込めるのはフォルダーとしてインストールされた MOD だけです。
+73
View File
@@ -0,0 +1,73 @@
# Editor Tools — 실행 중인 게임 안에서 장면과 시나리오 만들기
Editor Tools는 실행 중인 게임 위에 열리는 창작용 창 모음입니다. 장면 에디터, 시나리오 에디터, 월드 맵·상호작용·조명 에디터, Godot 장면 빌더, 모드 카탈로그 내보내기가 들어 있습니다. 저장되는 것은 모두 평범한 모드 파일이고 게임은 변경을 곧바로 받아들입니다. 그래서 장소를 쓰고, 바로 들어가 플레이하는 일을 재시작 없이 할 수 있습니다.
이 모드에는 아이템·캐릭터 에디터의 기반도 들어 있습니다(본체는 별도 모드인 Items Editor와 Characters Editor입니다). 게임에 이 도구들을 넣으려면 Developer Tools 도구 모음이나 그 게임의 컬렉션을 사용합니다.
## 도구 열기
게임 안에서 도구 모음의 망치 버튼을 누르면 도구 상자가 열립니다:
![소설 템플릿의 도구 모음. 망치가 눌려 있고 도구 상자가 열려 있습니다: Interactive Editor, Light Editor, Map Editor, Scene editor.](assets/toolbox.jpg)
- 도구 상자에는 **장면 에디터**, **맵 에디터**, **상호작용 에디터**, **조명 에디터**가 있습니다. 다른 도구 모드도 여기에 항목을 추가합니다(아이템, 캐릭터, RPG 맵 에디터, 데이터 에디터).
- **시나리오 에디터**는 장면 에디터에서 엽니다. 장면을 고르고 그 시나리오 중 하나를 선택한 뒤 **열기**를 누르거나 **+ 새 시나리오**를 누릅니다.
- 메인 메뉴에서 도구를 불러오는 경우(예: `--all-mods`), 메뉴의 도구 영역에도 각 도구 버튼이 생깁니다. 장면 빌더와 카탈로그 내보내기도 포함됩니다.
창은 장면을 바꿔도 열린 채로 남고, 옮기고 크기를 바꿀 수 있으며 ×로 닫습니다.
## 작은 편집 하나를 끝까지: 새 장소
소설 템플릿에는 마을, 술집, 집이 있습니다. 여기서는 에디터만 써서 마을에서 갈 수 있는 공원을 추가해 보겠습니다.
**1. 새 장면.** 장면 에디터에서 **+ 새 장면**을 누르고 이름을 입력하고 저장할 모드를 고른 다음 배경을 추가합니다:
![장면 에디터: 왼쪽에 my_novel_game의 my_novel_bar, my_novel_home, my_novel_town 장면. 가운데에는 장면에 추가한 배경 home/vestibule. 오른쪽은 이름 my_novel_park, 저장할 곳 my_novel_game, 소리 없음, 기본 시나리오 없음.](assets/new-scene.jpg)
저장(Ctrl+S)하면 `scenes/my_novel_park.json`이 `my_novel_game`에 기록되고, 게임은 그 장면을 곧바로 인식합니다.
**2. 그 장면의 시나리오.** **+ 새 시나리오**를 누르면 그 장면의 파일이 열린 시나리오 에디터가 뜹니다. 공원에서는 돌아가는 길이 하나뿐입니다:
![my_novel_park.scenario를 연 시나리오 에디터: 장면 my_novel_park, 우선순위 10, 내레이션 "마을 뒤의 조용한 공원. 젖은 낙엽, 비어 있는 벤치.", 선택지 "이제 뭘 하지?"의 "마을로 돌아가기"가 switch_scene my_novel_town으로 이어집니다.](assets/park-scenario.jpg)
**3. 그곳으로 가는 길.** 마을 시나리오에 답 하나와 레이블 하나를 추가합니다:
![my_novel_town.scenario를 연 시나리오 에디터: 선택지 "어디로 갈까?"에 "공원으로 가기" -> :park가 생겼고, :park 레이블이 switch_scene my_novel_park를 실행합니다.](assets/town-scenario.jpg)
```scenario
option "공원으로 가기" -> :park
:park
switch_scene my_novel_park
end
```
시나리오를 저장하면 게임의 시나리오 캐시가 비워지므로, 다음에 마을에 들어가면 새 텍스트가 재생됩니다.
**4. 플레이해 보기.** 장면 에디터의 **장면으로 이동**을 누르거나 그냥 계속 플레이하면 마을로 들어가고, 새 답이 그 자리에 있습니다:
![실행 중인 게임의 마을: "어디로 갈까?"에 술집으로 들어가기, 공원으로 가기, 집으로 돌아가기.](assets/town-choice.jpg)
!["공원으로 가기"를 고른 뒤의 새 공원: "이제 뭘 하지?"에 마을로 돌아가기.](assets/park.jpg)
## 각 에디터
- **장면 에디터** — 소설의 장소: 이름, 저장할 모드, 배경(들어갈 때 하나가 무작위로 선택됩니다), 청취 버튼이 있는 환경음, 기본 시나리오, 장면의 시나리오. **장면으로 이동**은 진행 중인 대화를 끝내고 장면으로 들어갑니다.
- **시나리오 에디터** — 모든 모드의 파일, 요약, 장면과 전환의 그래프, DSL 강조 표시, Ctrl+S 저장. **플레이**(F5)는 저장하고 실행 중인 게임 안에서 시나리오를 재생하며, 정지는 게임이 있던 곳으로 돌아갑니다. **외부 에디터로 열기**는 시스템 편집기로 파일을 엽니다.
- **맵 에디터** — 월드 맵의 구역.
- **상호작용 에디터** — 현재 장면의 클릭 가능한 객체.
- **조명 에디터** — 현재 장면의 조명.
- **장면 빌더** — 모드용 Godot `.tscn` 장면.
- **카탈로그 내보내기** — 모드를 zip으로 묶고 체크섬과 함께 모드 카탈로그용 항목을 기록합니다.
편집 중에는 되돌리기가 됩니다(텍스트와 아이템·캐릭터 에디터의 폼에서 Ctrl+Z). 깨진 참조 검사는 Validator의 일이며, 에디터가 실수가 있는 시나리오의 저장을 막지는 않습니다.
## 어떤 모드의 파일이 기록되는가
도구는 폴더로 설치된 모드에만 씁니다. 장면은 모든 모드의 `scenes/<name>.json` 파일을 합친 것이므로, 장면 에디터는 **저장할 곳**에서 고른 모드의 파일을 씁니다. 다른 모드나 압축된 모드에 속한 장면을 바꾸면 그 장면을 보충하는 파일이 당신의 모드에 만들어지고, 다른 파일에서 온 배경은 잠긴 상태로 표시됩니다. 시나리오는 속한 모드에 저장됩니다.
## 문제가 생겼을 때
1. **망치 버튼이 없습니다.** 그 게임에 도구가 들어 있지 않습니다. 컬렉션에 Developer Tools 도구 모음을 추가하거나 `--with-mods=editor_tools`로 실행하세요.
2. **새 시나리오가 재생되지 않습니다.** 머리말이 다른 장면을 가리키고 있거나, 그 장면의 다른 시나리오가 먼저 선택됩니다(`priority`를 비교해 보세요).
3. **저장할 곳에 모드가 없습니다.** 폴더로 설치된 모드에만 쓸 수 있습니다.
+73
View File
@@ -0,0 +1,73 @@
# Editor Tools — сцены и сценарии прямо в запущенной игре
Editor Tools — это авторские окна поверх работающей игры: редактор сцен, редактор сценариев, редакторы карты мира, интерактивных объектов и света, конструктор сцен Godot и экспорт каталога мода. Всё, что они сохраняют, — обычные файлы модов, и игра подхватывает правки сразу: можно описать место, тут же войти в него и сыграть, ничего не перезапуская.
Здесь же лежит основа редакторов предметов и персонажей (сами они — отдельные моды: Items Editor и Characters Editor). В игру эти инструменты попадают через набор Developer Tools или через её собственную коллекцию.
## Как открыть инструменты
Молоток на панели инструментов в игре открывает набор инструментов:
![Панель шаблона новеллы: нажат молоток, открыт набор инструментов — Interactive Editor, Light Editor, Map Editor, Scene editor.](assets/toolbox.jpg)
- **Редактор сцен**, **Редактор карты**, **Редактор интерактивов** и **Редактор освещения** входят в набор. Другие инструментальные моды добавляют туда свои пункты (редакторы предметов, персонажей и RPG-карт, редактор данных).
- **Редактор сценариев** открывается из редактора сцен: выберите сцену, укажите один из её сценариев и нажмите **Открыть** — или **+ Новый сценарий**.
- Если инструменты загружены в главном меню (например, с `--all-mods`), у каждого есть кнопка и в блоке инструментов меню, включая конструктор сцен и экспорт каталога.
Окна остаются открытыми при смене сцен, их можно двигать и растягивать, а закрываются они крестиком.
## Целая небольшая правка: новое место
В шаблоне новеллы есть город, бар и дом. Добавим туда парк, до которого можно дойти из города, — только средствами редакторов.
**1. Новая сцена.** В редакторе сцен нажмите **+ Новая сцена**, введите имя, выберите мод для сохранения и добавьте фон:
![Редактор сцен: слева сцены my_novel_bar, my_novel_home и my_novel_town мода my_novel_game; в центре добавленный в сцену фон home/vestibule; справа Имя my_novel_park, Сохранять в my_novel_game, Звук нет, Сценарий по умолч. нет.](assets/new-scene.jpg)
Сохранение (Ctrl+S) записывает `scenes/my_novel_park.json` в `my_novel_game` — и игра знает сцену сразу.
**2. Её сценарий.** **+ Новый сценарий** открывает редактор сценариев с файлом для сцены. Из парка есть один путь назад:
![Редактор сценариев с my_novel_park.scenario: сцена my_novel_park, приоритет 10, повествование «Тихий парк за городом. Мокрая листва, пустая скамейка.», выбор «Что дальше?» с ответом «Обратно в город», ведущим к switch_scene my_novel_town.](assets/park-scenario.jpg)
**3. Путь туда.** В сценарии города добавляются один ответ и одна метка:
![Редактор сценариев с my_novel_town.scenario: у выбора «Куда пойти?» появился ответ «Пойти в парк» -> :park, а метка :park выполняет switch_scene my_novel_park.](assets/town-scenario.jpg)
```scenario
option "Пойти в парк" -> :park
:park
switch_scene my_novel_park
end
```
Сохранение сценария сбрасывает кэш сценариев игры, так что при следующем входе в город сыграет уже новый текст.
**4. Сыграйте.** **Перейти в сцену** в редакторе сцен — или просто продолжайте играть — и вы попадёте в город, где новый ответ уже на месте:
![Город в запущенной игре: «Куда пойти?» с ответами «Зайти в бар», «Пойти в парк», «Вернуться домой».](assets/town-choice.jpg)
![Новый парк после выбора «Пойти в парк»: «Что дальше?» с ответом «Обратно в город».](assets/park.jpg)
## Редакторы
- **Редактор сцен** — места новеллы: имя, мод для сохранения, фоны (при входе один выбирается случайно), звук окружения с кнопкой прослушивания, сценарий по умолчанию и сценарии сцены. **Перейти в сцену** завершает текущий диалог и входит в сцену.
- **Редактор сценариев** — файлы всех модов, сводка и граф сцен и переходов; подсветка DSL; Ctrl+S сохраняет; **Запустить** (F5) сохраняет и проигрывает сценарий прямо в запущенной игре, а «Стоп» возвращает туда, где игра была; **Открыть во внешнем редакторе** открывает файл в системном редакторе.
- **Редактор карты** — зоны карты мира.
- **Редактор интерактивов** — кликабельные объекты текущей сцены.
- **Редактор освещения** — источники света текущей сцены.
- **Конструктор сцен** — сцены Godot `.tscn` для модов.
- **Экспорт каталога** — упаковывает мод в zip и пишет его запись для каталога модов с контрольными суммами.
Отмена работает во время редактирования (Ctrl+Z в тексте и в формах редакторов предметов и персонажей). Проверка мода на битые ссылки — задача Validator; редакторы не мешают сохранить сценарий с ошибкой.
## Чьи файлы пишутся
Инструменты пишут только в моды, установленные папками. Сцена — это сумма файлов `scenes/<name>.json` всех модов, поэтому редактор сцен пишет файл того мода, который выбран в **Сохранять в**. Если вы меняете сцену, принадлежащую другому моду или упакованному моду, создаётся файл в вашем моде, который её дополняет; фоны из чужих файлов показываются заблокированными. Сценарии сохраняются в том моде, которому принадлежат.
## Если что-то пошло не так
1. **Нет кнопки-молотка.** В игре нет инструментов — добавьте набор Developer Tools в её коллекцию или запустите с `--with-mods=editor_tools`.
2. **Новый сценарий не играет.** В его заголовке указана другая сцена, или вперёд выбран другой сценарий этой сцены (сравните их `priority`).
3. **В «Сохранять в» нет модов.** Писать можно только в моды, установленные папками.
+73
View File
@@ -0,0 +1,73 @@
# Editor Tools — dựng cảnh và kịch bản ngay trong trò chơi đang chạy
Editor Tools là những cửa sổ dành cho tác giả mở trên trò chơi đang chạy: trình sửa cảnh, trình sửa kịch bản, trình sửa bản đồ thế giới, trình sửa vật tương tác và ánh sáng, trình dựng cảnh Godot và phần xuất danh mục của mod. Mọi thứ chúng lưu đều là tệp mod bình thường, và trò chơi nhận thay đổi ngay lập tức, nên bạn viết ra một địa điểm, bước vào đó và chơi luôn mà không phải khởi động lại.
Mod này cũng chứa phần nền của trình sửa vật phẩm và nhân vật (bản thân chúng là mod riêng: Items Editor và Characters Editor). Một trò chơi nhận các công cụ này qua bộ Developer Tools hoặc qua bộ sưu tập riêng của nó.
## Mở các công cụ
Trong game, nút chiếc búa trên thanh công cụ mở hộp công cụ:
![Thanh công cụ của mẫu tiểu thuyết với chiếc búa đang được nhấn và hộp công cụ mở ra: Interactive Editor, Light Editor, Map Editor, Scene editor.](assets/toolbox.jpg)
- **Trình sửa cảnh**, **trình sửa bản đồ**, **trình sửa tương tác** và **trình sửa ánh sáng** nằm trong hộp công cụ. Các mod công cụ khác thêm mục của riêng chúng vào đó (trình sửa vật phẩm, nhân vật và bản đồ RPG, trình sửa dữ liệu).
- **Trình sửa kịch bản** mở từ trình sửa cảnh: chọn một cảnh, chọn một kịch bản của cảnh rồi nhấn **Mở**, hoặc **+ Kịch bản mới**.
- Nếu các công cụ được tải ở menu chính (ví dụ với `--all-mods`), mỗi công cụ cũng có nút trong khối công cụ của menu, kể cả trình dựng cảnh và phần xuất danh mục.
Các cửa sổ vẫn mở khi chuyển cảnh, có thể di chuyển và đổi kích thước, và đóng bằng dấu ×.
## Một chỉnh sửa nhỏ trọn vẹn: một địa điểm mới
Mẫu tiểu thuyết có một thị trấn, một quán bar và một ngôi nhà. Ở đây ta thêm một công viên đi được từ thị trấn, chỉ bằng các trình sửa.
**1. Một cảnh mới.** Trong trình sửa cảnh nhấn **+ Cảnh mới**, gõ tên, chọn mod để lưu và thêm phông nền:
![Trình sửa cảnh: bên trái là các cảnh my_novel_bar, my_novel_home và my_novel_town của my_novel_game; ở giữa là phông nền home/vestibule đã thêm vào cảnh; bên phải là Tên my_novel_park, Lưu vào my_novel_game, Âm thanh không, Kịch bản mặc định không.](assets/new-scene.jpg)
Lưu (Ctrl+S) ghi `scenes/my_novel_park.json` vào `my_novel_game`; trò chơi biết cảnh này ngay.
**2. Kịch bản của cảnh.** **+ Kịch bản mới** mở trình sửa kịch bản với một tệp cho cảnh. Công viên chỉ có một lối về:
![Trình sửa kịch bản với my_novel_park.scenario: cảnh my_novel_park, ưu tiên 10, lời dẫn "Một công viên yên tĩnh sau thị trấn. Lá ướt, chiếc ghế trống.", lựa chọn "Làm gì tiếp?" với "Về thị trấn" dẫn tới switch_scene my_novel_town.](assets/park-scenario.jpg)
**3. Một lối đến đó.** Trong kịch bản của thị trấn, thêm một câu trả lời và một nhãn:
![Trình sửa kịch bản với my_novel_town.scenario: lựa chọn "Đi đâu?" giờ có câu trả lời "Ra công viên" -> :park, và nhãn :park thực hiện switch_scene my_novel_park.](assets/town-scenario.jpg)
```scenario
option "Ra công viên" -> :park
:park
switch_scene my_novel_park
end
```
Lưu kịch bản sẽ xóa bộ nhớ đệm kịch bản của game, nên lần vào thị trấn tiếp theo sẽ chơi đoạn văn bản mới.
**4. Chơi thử.** **Đến cảnh** trong trình sửa cảnh, hoặc cứ chơi tiếp, sẽ vào thị trấn, và câu trả lời mới đã ở đó:
![Thị trấn trong game đang chạy: "Đi đâu?" với Vào quán bar, Ra công viên, Về nhà.](assets/town-choice.jpg)
![Công viên mới sau khi chọn Ra công viên: "Làm gì tiếp?" với Về thị trấn.](assets/park.jpg)
## Các trình sửa
- **Trình sửa cảnh** — các địa điểm của tiểu thuyết: tên, mod để lưu, phông nền (mỗi lần vào chọn ngẫu nhiên một), âm thanh nền có nút nghe thử, kịch bản mặc định và các kịch bản của cảnh. **Đến cảnh** kết thúc đoạn hội thoại đang chạy và vào cảnh.
- **Trình sửa kịch bản** — tệp của mọi mod, bản tóm tắt và đồ thị cảnh cùng chuyển cảnh; tô sáng DSL; Ctrl+S để lưu; **Chơi** (F5) lưu và phát kịch bản ngay trong game đang chạy, còn Dừng đưa game về chỗ cũ; **Mở bằng trình sửa ngoài** mở tệp bằng chương trình mặc định của hệ thống.
- **Trình sửa bản đồ** — các vùng của bản đồ thế giới.
- **Trình sửa tương tác** — những vật bấm được của cảnh hiện tại.
- **Trình sửa ánh sáng** — đèn của cảnh hiện tại.
- **Trình dựng cảnh** — cảnh `.tscn` của Godot cho các mod.
- **Xuất danh mục** — đóng gói mod thành zip và ghi mục danh mục kèm tổng kiểm tra.
Hoàn tác hoạt động trong khi sửa (Ctrl+Z trong phần văn bản và trong biểu mẫu của trình sửa vật phẩm, nhân vật). Kiểm tra mod có tham chiếu hỏng là việc của Validator; các trình sửa không cản bạn lưu một kịch bản còn lỗi.
## Tệp của ai được ghi
Các công cụ chỉ ghi vào mod được cài dưới dạng thư mục. Một cảnh là tổng các tệp `scenes/<name>.json` của mọi mod, nên trình sửa cảnh ghi tệp của mod được chọn trong **Lưu vào**. Khi sửa một cảnh thuộc mod khác hoặc mod đã đóng gói, một tệp bổ sung cho nó sẽ được tạo trong mod của bạn; những phông nền đến từ tệp khác hiện ở trạng thái khóa. Kịch bản được lưu vào mod mà chúng thuộc về.
## Khi có trục trặc
1. **Không thấy nút chiếc búa.** Trò chơi không có công cụ; hãy thêm bộ Developer Tools vào bộ sưu tập của nó hoặc chạy với `--with-mods=editor_tools`.
2. **Kịch bản mới không phát.** Phần đầu của nó ghi cảnh khác, hoặc một kịch bản khác của cảnh được chọn trước (so sánh `priority` của chúng).
3. **Mục Lưu vào không có mod nào.** Chỉ ghi được vào mod cài dưới dạng thư mục.
+73
View File
@@ -0,0 +1,73 @@
# Editor Tools — 在运行中的游戏里制作场景与剧本
Editor Tools 是叠在运行中游戏之上的作者工具窗口:场景编辑器、剧本编辑器、世界地图编辑器、交互物编辑器和光照编辑器,还有一个 Godot 场景构建器与模组目录导出。它们保存的全是普通的模组文件,游戏会立刻读到改动,所以作者可以写下一个地点,马上走进去试玩,无需重启。
本模组还包含物品与角色编辑器的底层部分(它们是两个独立的模组:Items Editor 和 Characters Editor)。游戏通过 Developer Tools 工具集或自己的集合获得这些工具。
## 打开工具
在游戏中,工具栏上的锤子按钮会打开工具箱:
![小说模板的工具栏:锤子按钮已按下,工具箱已打开:Interactive Editor、Light Editor、Map Editor、Scene editor。](assets/toolbox.jpg)
- 工具箱里有**场景编辑器**、**地图编辑器**、**交互编辑器**和**光照编辑器**。其他工具模组会往里面添加自己的条目(物品、角色和 RPG 地图编辑器,以及数据编辑器)。
- **剧本编辑器**从场景编辑器打开:选中一个场景,挑一个它的剧本,按**打开**,或者按 **+ 新剧本**。
- 如果在主菜单就加载了工具(比如用 `--all-mods`),菜单的工具区里每个工具也都有按钮,包括场景构建器和目录导出。
这些窗口在切换场景后依然开着,可以拖动和调整大小,点叉号关闭。
## 一次完整的小改动:一个新地点
小说模板里有小镇、酒吧和家。下面只用编辑器,给小镇加一个能走过去的公园。
**1. 新场景。** 在场景编辑器里按 **+ 新场景**,输入名称,选择保存到哪个模组,再添加一个背景:
![场景编辑器:左侧是 my_novel_game 的 my_novel_bar、my_novel_home 和 my_novel_town 三个场景;中间是添加到场景的背景 home/vestibule;右侧是名称 my_novel_park、保存到 my_novel_game、声音 无、默认剧本 无。](assets/new-scene.jpg)
保存(Ctrl+S)会把 `scenes/my_novel_park.json` 写进 `my_novel_game`,游戏马上就知道这个场景了。
**2. 它的剧本。** **+ 新剧本**会打开剧本编辑器,里面是该场景的一个文件。公园只有一条回去的路:
![剧本编辑器中的 my_novel_park.scenario:场景 my_novel_park,优先级 10,旁白「小镇后面的一座静园。湿漉漉的落叶,空着的长椅。」,选项「接下来做什么?」里的「回小镇」指向 switch_scene my_novel_town。](assets/park-scenario.jpg)
**3. 通往那里的路。** 在小镇的剧本里加一个回答和一个标签:
![剧本编辑器中的 my_novel_town.scenario:选项「去哪里?」现在多了回答「去公园」-> :park,而 :park 标签执行 switch_scene my_novel_park。](assets/town-scenario.jpg)
```scenario
option "去公园" -> :park
:park
switch_scene my_novel_park
end
```
保存剧本会清掉游戏的剧本缓存,所以下次进小镇时播放的就是新文本。
**4. 试玩。** 在场景编辑器里点**前往场景**,或者干脆继续玩下去,就会进入小镇,新的回答已经在那里了:
![运行中的游戏里的小镇:「去哪里?」下面是 进酒吧、去公园、回家。](assets/town-choice.jpg)
![选择「去公园」后出现的新公园:「接下来做什么?」下面是 回小镇。](assets/park.jpg)
## 各个编辑器
- **场景编辑器** —— 小说的地点:名称、保存到的模组、背景(进入时随机选一个)、带试听按钮的环境音、默认剧本和场景的剧本。**前往场景**会结束当前对话并进入该场景。
- **剧本编辑器** —— 所有模组的文件、一份概要,以及场景与转场的图;DSL 高亮;Ctrl+S 保存;**试玩**(F5)保存并在运行中的游戏里播放剧本,停止则回到游戏原来的位置;**在外部编辑器中打开**会用系统默认程序打开文件。
- **地图编辑器** —— 世界地图上的区域。
- **交互编辑器** —— 当前场景里可点击的对象。
- **光照编辑器** —— 当前场景的灯光。
- **场景构建器** —— 为模组制作 Godot `.tscn` 场景。
- **目录导出** —— 把模组打包成 zip,并写出带校验和的模组目录条目。
编辑期间可以撤销(文本里和物品、角色编辑器的表单里都支持 Ctrl+Z)。检查模组有没有失效引用是 Validator 的活儿;编辑器不会拦着你保存一个有错误的剧本。
## 写的是谁的文件
这些工具只写安装成文件夹的模组。一个场景是所有模组 `scenes/<name>.json` 文件的总和,所以场景编辑器写的是**保存到**里选中的那个模组的文件。改动属于别的模组或打包模组的场景时,会在你的模组里新建一个文件来补充它;来自其他文件的背景会显示为锁定。剧本则保存在它所属的模组里。
## 出问题了怎么办
1. **找不到锤子按钮。** 这个游戏没带工具;在它的集合里加上 Developer Tools 工具集,或者用 `--with-mods=editor_tools` 启动。
2. **新剧本不播放。** 它的头部写的是另一个场景,或者这个场景里有别的剧本先被选中了(比较它们的 `priority`)。
3. **「保存到」里没有模组。** 只有安装成文件夹的模组才能写入。
+5
View File
@@ -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
+5
View File
@@ -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 = この言語での説明
+5
View File
@@ -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 = 이 언어의 설명
+5
View File
@@ -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
+5
View File
@@ -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 = 此语言的描述
+6
View File
@@ -0,0 +1,6 @@
tools/entity_editor/section_schema = Fields of other mods
tools/entity_editor/schema_hint = These fields are described in the schemas of the mods: {0}. The game reads them from the same JSON.
tools/schema/add_ref = + add…
tools/schema/list_add = + Add
tools/schema/list_remove = ✕
tools/schema/map_add = + Stat
+6
View File
@@ -0,0 +1,6 @@
tools/entity_editor/section_schema = Campos de otros mods
tools/entity_editor/schema_hint = Estos campos los describen los esquemas de los mods: {0}. El juego los lee del mismo JSON.
tools/schema/add_ref = + añadir…
tools/schema/list_add = + Añadir
tools/schema/list_remove = ✕
tools/schema/map_add = + Estadística
+6
View File
@@ -0,0 +1,6 @@
tools/entity_editor/section_schema = 他の MOD のフィールド
tools/entity_editor/schema_hint = これらのフィールドは次の MOD のスキーマが記述している:{0}。ゲームは同じ JSON から読む。
tools/schema/add_ref = + 追加…
tools/schema/list_add = + 追加
tools/schema/list_remove = ✕
tools/schema/map_add = + ステータス
+6
View File
@@ -0,0 +1,6 @@
tools/entity_editor/section_schema = 다른 모드의 필드
tools/entity_editor/schema_hint = 이 필드는 다음 모드들의 스키마가 설명합니다: {0}. 게임은 같은 JSON에서 읽습니다.
tools/schema/add_ref = + 추가…
tools/schema/list_add = + 추가
tools/schema/list_remove = ✕
tools/schema/map_add = + 능력치
+6
View File
@@ -0,0 +1,6 @@
tools/entity_editor/section_schema = Поля других модов
tools/entity_editor/schema_hint = Эти поля описали в своих схемах моды: {0}. Игра читает их из того же JSON.
tools/schema/add_ref = + добавить…
tools/schema/list_add = + Добавить
tools/schema/list_remove = ✕
tools/schema/map_add = + Стат
+6
View File
@@ -0,0 +1,6 @@
tools/entity_editor/section_schema = Trường của mod khác
tools/entity_editor/schema_hint = Các trường này do lược đồ của các mod mô tả: {0}. Game đọc chúng từ cùng tệp JSON.
tools/schema/add_ref = + thêm…
tools/schema/list_add = + Thêm
tools/schema/list_remove = ✕
tools/schema/map_add = + Chỉ số
+6
View File
@@ -0,0 +1,6 @@
tools/entity_editor/section_schema = 其他模组的字段
tools/entity_editor/schema_hint = 这些字段由以下模组的模式描述:{0}。游戏从同一个 JSON 读取它们。
tools/schema/add_ref = + 添加…
tools/schema/list_add = + 添加
tools/schema/list_remove = ✕
tools/schema/map_add = + 属性
+36 -6
View File
@@ -1,5 +1,5 @@
mod_editor_tools_name = Editor Tools
mod_editor_tools_desc = Developer toolbox: scenario graph editor, map editor with zones, scene builder with inspector, interactive and light editors, and the mod catalog export window.
mod_editor_tools_desc = The author's windows over a running game: scene, scenario, map, interactive and light editors and a Godot scene builder. What they save are ordinary mod files the game applies at once.
tools/common/save = Save
tools/common/save_tip = Save the file (Ctrl+S)
@@ -243,11 +243,6 @@ 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.
tools/hub/title = Novel Editor
tools/hub/hint = Pick an editor — windows open over this screen. A scenario can be played right from the editor (F5).
tools/hub/editors = Editors
tools/hub/empty = No editors are installed.
tools/hub/main_menu = Main menu
tools/scene_editor/title = Scene editor
tools/scene_editor/hint = A scene is a place: backgrounds, ambient sound and scenarios. Ctrl+S saves · double-click a background in the list to add it
tools/scene_editor/save_tip = Save the scene file into the chosen mod (Ctrl+S); the game sees the change at once
@@ -305,3 +300,38 @@ tools/scene_editor/pick_scene = Pick a scene on the left or create a new one.
tools/scene_editor/preview_hint = Pick a background to see it.
tools/scene_editor/not_in_scene = (not in the scene)
tools/scene_editor/new_line_text = A new scene. Replace this line with your text.
tools/entity_editor/hint = The list with a search on the left, the fields of the selected entry on the right · Ctrl+N new, Ctrl+D copy · Ctrl+Z/Y undo/redo · Ctrl+S save, the game sees the change at once
tools/entity_editor/section_list = Entries of the mod
tools/entity_editor/search = Search by name or id
tools/entity_editor/mod_tip = The mod whose entries are edited; new entries go into its file
tools/entity_editor/new = New
tools/entity_editor/new_tip = Add an entry to the chosen mod (Ctrl+N)
tools/entity_editor/duplicate = Copy
tools/entity_editor/duplicate_tip = Copy the selected entry under a new id (Ctrl+D)
tools/entity_editor/delete = Delete
tools/entity_editor/delete_tip = Remove the selected entry from the mod's file
tools/entity_editor/delete_title = Delete the entry?
tools/entity_editor/delete_text = The entry leaves the mod's file on save. References to it in scenarios and maps stay — check them.
tools/entity_editor/undo = Undo
tools/entity_editor/undo_tip = Undo the last change (Ctrl+Z)
tools/entity_editor/redo = Redo
tools/entity_editor/redo_tip = Redo what was undone (Ctrl+Y)
tools/entity_editor/no_entity = Pick an entry on the left or make one with "New".
tools/entity_editor/file = Mod {0} · file {1}
tools/entity_editor/id = Id
tools/entity_editor/id_tip = Unique id: scenarios, maps and other mods find the entry by it. A mod that depends on the owner and ships the same id replaces the entry
tools/entity_editor/locale_key_tip = Locale key; the texts below go into the mod's locale/*.properties
tools/entity_editor/text_in = text ({0})
tools/entity_editor/picture_pick_tip = Pick a picture from the mod folders
tools/entity_editor/picture_pick_title = Pictures of the “{0}” group
tools/entity_editor/section_other = Other fields
tools/entity_editor/other_hint = Fields the form does not show (other mods' data, loot…) as JSON. Edits apply as soon as the JSON is valid.
tools/entity_editor/other_invalid = Other fields: invalid JSON — {0}
tools/entity_editor/other_applied = Other fields applied
tools/entity_editor/opened = {0} entries in mod {1}
tools/entity_editor/added = Added entry {0} to {1}
tools/entity_editor/deleted = Entry {0} deleted (save to write it)
tools/entity_editor/saved = Saved: {0} file(s), {1} text(s) in mod {2}; the game re-read its data
tools/entity_editor/broken_files = Files with broken JSON were not read: {0}
tools/entity_editor/problem_id = An entry in file {0} has an empty id
tools/entity_editor/problem_duplicate = Id {0} occurs more than once
+337
View File
@@ -0,0 +1,337 @@
mod_editor_tools_name = Herramientas de edición
mod_editor_tools_desc = Ventanas de autor sobre el juego en marcha: editores de escenas, escenarios, mapa, interactivos y luces, y un constructor de escenas de Godot. Todo lo que guardan son archivos de mod normales que el juego aplica al instante.
tools/common/save = Guardar
tools/common/save_tip = Guardar el archivo (Ctrl+S)
tools/common/saved = Guardado: {0}
tools/common/loaded = Abierto: {0}
tools/common/load_error = Error al cargar
tools/common/readonly = No se puede guardar: el mod es de solo lectura (zip) o la ruta no es válida.
tools/common/no_writable_mods = No hay mods editables (solo se pueden editar los mods en carpeta).
tools/common/no_files = El mod «{0}» todavía no tiene archivos de este tipo: crea uno con «+».
tools/common/no_file_to_save = Primero elige o crea un archivo («+» junto a la lista de archivos).
tools/common/new_file = Nuevo archivo
tools/common/new_file_hint = El archivo se crea en {0}/{1}/ con la extensión {2} al guardarlo por primera vez.
tools/common/new_file_status = Nuevo archivo {0}: se crea al guardar.
tools/common/mod_tip = Mod (primero los mods que ya tienen archivos así; · — todavía ninguno)
tools/common/file_tip = Archivo dentro del mod
tools/common/search = Buscar…
tools/common/json = JSON
tools/common/json_tip = Mostrar el JSON del archivo resultante
tools/common/background_missing = Ningún mod aporta el fondo «{0}» de la escena «{1}»: elige uno a mano.
tools/map_editor/title = Editor de mapas
tools/map_editor/hint = Clic izq.: seleccionar / arrastrar · clic der.: borrar vértice · doble clic, Intro o Esc: terminar la región · Supr: borrar zona · Ctrl+S: guardar
tools/map_editor/reload_map = Actualizar el mapa del juego
tools/map_editor/reload_map_tip = Volver a leer los archivos del mapa guardados en el juego en marcha
tools/map_editor/reloaded = Mapa del juego actualizado.
tools/map_editor/select_mode = Seleccionar
tools/map_editor/draw_region = Dibujar
tools/map_editor/draw_started = Haz clic en el mapa para poner vértices. Doble clic, Intro o Esc terminan; el clic derecho quita el último.
tools/map_editor/background = Fondo de vista previa
tools/map_editor/zones = Zonas
tools/map_editor/add_region = + Región
tools/map_editor/add_region_tip = Nueva región: dibuja el polígono haciendo clic en el mapa
tools/map_editor/add_marker = + Marcador
tools/map_editor/add_label = + Rótulo
tools/map_editor/delete = Borrar zona
tools/map_editor/delete_tip = Borrar la zona seleccionada (Supr)
tools/map_editor/delete_confirm = ¿Borrar la zona seleccionada? El cambio llega al archivo al guardar.
tools/map_editor/deleted = Zona «{0}» borrada.
tools/map_editor/props = Propiedades de la zona
tools/map_editor/zone_missing = Elige una zona en la lista o en el mapa.
tools/map_editor/id = ID
tools/map_editor/id_empty = El ID de la zona no puede estar vacío.
tools/map_editor/duplicate_id = ID de zona ya usado:
tools/map_editor/title_key = Título
tools/map_editor/description = Descripción
tools/map_editor/no_translation = clave no encontrada en la localización
tools/map_editor/scene = Escena
tools/map_editor/scene_none = (ninguna)
tools/map_editor/availability = Disponibilidad
tools/map_editor/locked = Bloqueada para viajar
tools/map_editor/hidden = Oculta en el mapa
tools/map_editor/effective = En el juego:
tools/map_editor/effective_open = abierta
tools/map_editor/effective_locked = bloqueada (marca Locked)
tools/map_editor/effective_no_scene = bloqueada (sin escena)
tools/map_editor/effective_unknown_scene = bloqueada (escena no encontrada)
tools/map_editor/effective_hidden = oculta en el mapa
tools/map_editor/marker = Marcador
tools/map_editor/image = Icono
tools/map_editor/image_placeholder = ID de textura
tools/map_editor/icon_search = Buscar icono…
tools/map_editor/label_section = Rótulo
tools/map_editor/label = Rótulo de texto en el mapa
tools/map_editor/x = X (0–1)
tools/map_editor/y = Y (0–1)
tools/map_editor/vertices = Vértices del polígono
tools/map_editor/vertices_hint = Arrastra los vértices en el mapa; clic derecho en un vértice lo borra.
tools/map_editor/add_vertex = + Vértice
tools/map_editor/delete_vertex = Borrar vértice
tools/map_editor/preview = ▶ Como en el juego
tools/map_editor/preview_tip = Ver el mapa como en el juego: paso del ratón, tarjeta de zona, marcadores (incluidos los cambios sin guardar)
tools/map_editor/preview_on = Vista previa: Esc o ✕ vuelve al editor.
tools/map_editor/preview_off = Modo de edición.
tools/map_editor/preview_missing = No se puede cargar la escena del mapa (map_widget.tscn).
tools/scenario_editor/title = Editor de escenarios
tools/scenario_editor/hint = Ctrl+S: guardar · F5: jugar el escenario · clic en una escena del grafo: sus escenarios
tools/scenario_editor/play = ▶ Jugar
tools/scenario_editor/play_tooltip = Guardar y jugar el escenario en el juego (F5)
tools/scenario_editor/play_stopped = Prueba detenida.
tools/scenario_editor/stop = ■ Detener
tools/scenario_editor/open_file = Abrir en un editor externo
tools/scenario_editor/open_file_tip = Abrir el archivo con el programa predeterminado del sistema
tools/scenario_editor/open_file_error = No se puede abrir el archivo
tools/scenario_editor/rescan = Volver a escanear
tools/scenario_editor/rescan_tip = Volver a leer la lista de escenarios y el grafo (tras añadir archivos)
tools/scenario_editor/rescanned = Lista de escenarios actualizada.
tools/scenario_editor/search_files = Buscar archivo…
tools/scenario_editor/tab_files = Archivos
tools/scenario_editor/tab_info = Resumen
tools/scenario_editor/tab_graph = Grafo de escenas
tools/scenario_editor/graph_select_node = Selecciona una escena en el grafo para ver sus escenarios y transiciones.
tools/scenario_editor/graph_node = mod {0} · escenarios {1}
tools/scenario_editor/graph_node_bg = fondo: {0}
tools/scenario_editor/graph_detail_mod = mod: {0}
tools/scenario_editor/graph_detail_scenarios = Escenarios ({0}), por prioridad:
tools/scenario_editor/info_scene = Escena
tools/scenario_editor/info_priority = Prioridad
tools/scenario_editor/info_condition = Condición
tools/scenario_editor/info_switch_targets = Cambios de escena
tools/scenario_editor/info_locale_keys = Claves de localización
tools/scenario_editor/info_quest_refs = Misiones
tools/scenario_editor/bg_previews = Fondos del escenario
tools/scenario_editor/bg_missing = Fondo no encontrado: {0}
tools/scenario_editor/no_backgrounds = El escenario no cambia el fondo
tools/scenario_editor/save_error = Error al guardar: {0}
tools/scenario_editor/readonly_zip = Los archivos de los mods zip son de solo lectura
tools/scenario_editor/no_scene_error = La cabecera del escenario no tiene escena (scene:): no hay nada que jugar.
tools/scenario_editor/unsaved_title = Cambios sin guardar
tools/scenario_editor/unsaved_switch_text = Esta pestaña tiene cambios sin guardar. ¿Cerrarla sin guardar?
tools/scene_builder/title = Constructor de escenas
tools/scene_builder/hint = Arrastra un nodo por su cuerpo o sus tiradores · clic: elegir el nodo bajo el cursor · Supr: borrar nodo · Ctrl+S: guardar y reconstruir
tools/scene_builder/base_scene = Escena base sobre la que se muestra la escena del mod
tools/scene_builder/mod = Mod
tools/scene_builder/scene = Escena del mod (.tscn)
tools/scene_builder/empty_scene = (escena base vacía)
tools/scene_builder/select_mod = — elige un mod —
tools/scene_builder/select_scene = — elige una escena —
tools/scene_builder/save_tip = Guardar el .tscn y reconstruir la vista previa (Ctrl+S)
tools/scene_builder/reload = Recargar desde el disco
tools/scene_builder/reload_tip = Descartar los cambios sin guardar y volver a leer el archivo de la escena
tools/scene_builder/reloaded = Escena recargada desde el disco.
tools/scene_builder/saved_ok = Guardado.
tools/scene_builder/saved_rebuilt = Guardado, vista previa reconstruida.
tools/scene_builder/autosaved = Guardado automático.
tools/scene_builder/error_save = Error al guardar: el mod es de solo lectura.
tools/scene_builder/error_no_scene = Primero elige una escena del mod.
tools/scene_builder/hierarchy = Jerarquía
tools/scene_builder/hierarchy_hint = Dorado: raíz de la escena del mod; brillante: nodos editables; tenue: escena base (solo lectura).
tools/scene_builder/node_type_tip = Tipo del nuevo nodo
tools/scene_builder/add_node_tip = Añadir un nodo del tipo elegido dentro del nodo seleccionado (o en la raíz de la escena)
tools/scene_builder/delete_node_tip = Borrar el nodo seleccionado de la escena del mod
tools/scene_builder/delete_node_title = Borrar nodo
tools/scene_builder/delete_node_confirm = ¿Borrar el nodo seleccionado con sus hijos? El archivo se guarda automáticamente.
tools/scene_builder/refresh_tip = Actualizar el árbol de nodos
tools/scene_builder/tab_inspector = Inspector
tools/scene_builder/tab_tscn = Código .tscn
tools/scene_builder/tscn_hint = Los cambios del inspector y de los tiradores se guardan solos; los cambios a mano en el texto se guardan con Ctrl+S, que también reconstruye la vista previa.
tools/scene_builder/inspector_cat_container = En contenedor
tools/scene_builder/inspector_select = Selecciona un nodo de la escena del mod para editar sus propiedades
tools/scene_builder/inspector_readonly = Solo lectura: el nodo pertenece a la escena base
tools/scene_builder/inspector_missing_section = No se encontró la sección del nodo en el tscn (los nodos de subescenas instanciadas todavía no se pueden editar)
tools/scene_builder/inspector_preset = Preajuste de anclas
tools/scene_builder/inspector_cat_layout = Disposición
tools/scene_builder/inspector_cat_rect = Rectángulo
tools/scene_builder/inspector_cat_visibility = Visibilidad
tools/scene_builder/inspector_cat_misc = Varios
tools/scene_builder/inspector_bad_value = El valor no se puede escribir en el tscn.
tools/scene_builder/inspector_in_file_tooltip = Guardado en el archivo tscn
tools/scene_builder/inspector_container_banner = Este nodo lo gestiona un contenedor: el padre recalcula las anclas y los desplazamientos, así que aquí no tendrán efecto.
tools/scene_builder/err_add_failed = Error: no se puede crear un nodo de este tipo.
tools/scene_builder/err_add_parent_missing = Error: no se encontró la sección del padre en el tscn (no se admiten padres de subescenas instanciadas).
tools/scene_builder/err_del_root = Error: no se puede borrar la raíz de la escena.
tools/scene_builder/err_del_none = Error: elige un nodo del mod para borrarlo.
tools/interactive_editor/title = Editor de interactivos
tools/interactive_editor/hint = Clic en un colisionador: seleccionar · arrastra los tiradores · en modo polígono, un clic en el fondo añade un vértice y el clic derecho en un vértice lo borra · Supr: borrar objeto · Ctrl+S: guardar
tools/interactive_editor/items = Objetos del archivo
tools/interactive_editor/add = + Nuevo
tools/interactive_editor/duplicate = Duplicar
tools/interactive_editor/delete = Borrar objeto
tools/interactive_editor/delete_confirm = ¿Quitar del archivo el interactivo seleccionado? El cambio llega al archivo al guardar.
tools/interactive_editor/deleted = Objeto «{0}» quitado.
tools/interactive_editor/background = Fondo de vista previa
tools/interactive_editor/props = Propiedades
tools/interactive_editor/none_selected = Elige un objeto en la lista o en la vista previa.
tools/interactive_editor/label_id = ID
tools/interactive_editor/label_texture_key = Textura
tools/interactive_editor/texture_placeholder = sin sprite
tools/interactive_editor/texture_tip = ID de la textura del sprite; vacío: un área clicable invisible
tools/interactive_editor/label_layer = Capa
tools/interactive_editor/label_z = Orden Z
tools/interactive_editor/label_cursor = Cursor
tools/interactive_editor/cursor_none = (por defecto)
tools/interactive_editor/enabled = Activado
tools/interactive_editor/visible = Visible
tools/interactive_editor/collider = Colisionador
tools/interactive_editor/label_collider_type = Forma
tools/interactive_editor/hit_rect = rectángulo
tools/interactive_editor/hit_circle = círculo
tools/interactive_editor/hit_polygon = polígono
tools/interactive_editor/label_x = X
tools/interactive_editor/label_y = Y
tools/interactive_editor/label_w = Ancho
tools/interactive_editor/label_h = Alto
tools/interactive_editor/label_cx = Centro X
tools/interactive_editor/label_cy = Centro Y
tools/interactive_editor/label_r = Radio
tools/interactive_editor/polygon_hint = Un clic en el fondo añade un vértice; el clic derecho en un vértice lo borra. Al menos 3 vértices.
tools/interactive_editor/polygon_too_small = El polígono «{0}» tiene menos de 3 vértices.
tools/interactive_editor/highlight = Resaltado
tools/interactive_editor/label_highlight_color = Color
tools/interactive_editor/label_highlight_scale = Escala
tools/interactive_editor/actions = Acciones
tools/interactive_editor/label_onclick_scenario = Escenario
tools/interactive_editor/label_onclick_commands = Comandos al hacer clic (uno por línea)
tools/interactive_editor/label_hover_enter = Al entrar el ratón
tools/interactive_editor/label_hover_leave = Al salir el ratón
tools/interactive_editor/id_empty = Un objeto tiene el ID vacío.
tools/interactive_editor/duplicate_id = ID de objeto duplicado:
tools/light_editor/title = Editor de luces
tools/light_editor/hint = Doble clic en el fondo: nueva luz · arrastra las luces · Supr: borrar · Ctrl+S: escribir light_set en el escenario
tools/light_editor/scenario_tip = Escenario cuya línea light_set se edita (●: ya define luces)
tools/light_editor/pick_scenario = — elige un escenario —
tools/light_editor/save_tip = Escribir la línea light_set en el escenario (Ctrl+S)
tools/light_editor/copy_line = Copiar línea
tools/light_editor/copy_line_tip = Copiar el comando light_set al portapapeles
tools/light_editor/copied = Línea light_set copiada.
tools/light_editor/show_line = light_set
tools/light_editor/loaded = Luces de {0} cargadas.
tools/light_editor/no_lights_yet = {0} todavía no tiene light_set: añade luces y la línea se insertará tras la cabecera.
tools/light_editor/static_lights = Luces
tools/light_editor/add_light = + Luz
tools/light_editor/delete = Borrar
tools/light_editor/limit = Como máximo {0} luces estáticas.
tools/light_editor/selected_props = Luz seleccionada
tools/light_editor/none_selected = Elige una luz en la lista o en la vista previa.
tools/light_editor/x = X (0–1)
tools/light_editor/y = Y (0–1)
tools/light_editor/radius = Radio
tools/light_editor/radius_tip = Radio en píxeles de la pantalla de juego de 1920 px
tools/light_editor/influence = Intensidad
tools/light_editor/influence_tip = Influencia de la luz (0–1)
tools/light_editor/color = Color
tools/light_editor/background = Fondo de vista previa
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.
tools/scene_editor/title = Editor de escenas
tools/scene_editor/hint = Una escena es un lugar: fondos, sonido ambiente y escenarios. Ctrl+S guarda · doble clic en un fondo de la lista para añadirlo
tools/scene_editor/save_tip = Guardar el archivo de la escena en el mod elegido (Ctrl+S); el juego ve el cambio al momento
tools/scene_editor/new_scene = + Nueva escena
tools/scene_editor/new_scene_tip = Crear un lugar nuevo: nombre, mod, fondos. Los escenarios se pueden añadir después; una escena también puede no tener ninguno
tools/scene_editor/delete = Borrar
tools/scene_editor/delete_tip = Borrar el archivo de la escena del mod elegido
tools/scene_editor/delete_confirm = ¿Borrar el archivo de esta escena del mod elegido? Los escenarios de la escena se conservan.
tools/scene_editor/go = ▶ Ir a la escena
tools/scene_editor/go_tip = Cambiar el juego a esta escena
tools/scene_editor/rescan_tip = Volver a leer escenas, fondos y sonidos (tras añadir archivos a los mods)
tools/scene_editor/scenes = Escenas
tools/scene_editor/backgrounds = Fondos de la escena (al entrar se elige uno al azar)
tools/scene_editor/all_backgrounds = Todos los fondos
tools/scene_editor/add_background = + Añadir fondo a la escena
tools/scene_editor/add_background_tip = Añadir a la escena el fondo mostrado (o haz doble clic en él en la lista)
tools/scene_editor/remove_background = Quitar el fondo de la escena
tools/scene_editor/properties = Propiedades
tools/scene_editor/name = Nombre
tools/scene_editor/name_tip = Id de la escena: letras latinas, dígitos y _; lo usan switch_scene y las cabeceras de los escenarios. Solo se indica al crearla
tools/scene_editor/mod = Guardar en
tools/scene_editor/mod_tip = Mod en el que se escribe el archivo de la escena (solo mods en carpeta)
tools/scene_editor/ambient = Sonido
tools/scene_editor/ambient_tip = Sonido ambiente que suena en la escena
tools/scene_editor/listen = ▶
tools/scene_editor/listen_tip = Escuchar el sonido ambiente
tools/scene_editor/stop = ■
tools/scene_editor/default_scenario = Escenario por defecto
tools/scene_editor/default_scenario_tip = Se reproduce cuando no queda ningún otro escenario de la escena
tools/scene_editor/scenarios = Escenarios de la escena
tools/scene_editor/open_scenario = Abrir
tools/scene_editor/open_scenario_tip = Abrir el escenario seleccionado en el editor de escenarios (o doble clic)
tools/scene_editor/new_scenario = + Nuevo escenario
tools/scene_editor/new_scenario_tip = Crear un escenario de esta escena en el mod elegido y abrirlo
tools/scene_editor/none = — ninguno —
tools/scene_editor/effective = En el juego: {0} (del mod {1})
tools/scene_editor/adds_to = La escena está definida en: {0}. Al guardar se crea un archivo en el mod elegido que la amplía.
tools/scene_editor/also_in = También la amplían: {0}
tools/scene_editor/no_writable_mod = No hay ningún mod en carpeta donde guardar la escena.
tools/scene_editor/bad_name = Indica el nombre de la escena: letras latinas, dígitos y _ (empezando por una letra).
tools/scene_editor/name_taken = La escena «{0}» ya existe; elígela en la lista.
tools/scene_editor/need_background = Añade al menos un fondo a la escena.
tools/scene_editor/created = Escena «{0}» creada.
tools/scene_editor/deleted = Borrado: {0}
tools/scene_editor/opened = Escena: {0}
tools/scene_editor/new_started = Nueva escena: indica el nombre y el mod, añade fondos.
tools/scene_editor/already_there = El fondo {0} ya está en la escena.
tools/scene_editor/go_failed = El juego no conoce la escena «{0}»; guárdala primero.
tools/scene_editor/went = Cambiado a la escena {0}.
tools/scene_editor/rescanned = Escenas, fondos y sonidos releídos.
tools/scene_editor/scenario_created = Escenario {0} creado.
tools/scene_editor/no_scenarios = La escena todavía no tiene escenarios
tools/scene_editor/no_backgrounds = La escena no tiene fondos: elige uno en la lista de abajo y añádelo.
tools/scene_editor/pick_scene = Elige una escena a la izquierda o crea una nueva.
tools/scene_editor/preview_hint = Elige un fondo para verlo.
tools/scene_editor/not_in_scene = (no está en la escena)
tools/scene_editor/new_line_text = Una escena nueva. Sustituye esta línea por tu texto.
tools/entity_editor/hint = A la izquierda, la lista con búsqueda; a la derecha, los campos de la entrada elegida · Ctrl+N: nueva, Ctrl+D: copia · Ctrl+Z/Y: deshacer/rehacer · Ctrl+S: guardar, el juego ve el cambio al momento
tools/entity_editor/section_list = Entradas del mod
tools/entity_editor/search = Buscar por nombre o id
tools/entity_editor/mod_tip = El mod cuyas entradas se editan; las nuevas entradas van a su archivo
tools/entity_editor/new = Nueva
tools/entity_editor/new_tip = Añadir una entrada al mod elegido (Ctrl+N)
tools/entity_editor/duplicate = Copia
tools/entity_editor/duplicate_tip = Copiar la entrada seleccionada con un id nuevo (Ctrl+D)
tools/entity_editor/delete = Borrar
tools/entity_editor/delete_tip = Quitar la entrada seleccionada del archivo del mod
tools/entity_editor/delete_title = ¿Borrar la entrada?
tools/entity_editor/delete_text = La entrada sale del archivo del mod al guardar. Las referencias a ella en escenarios y mapas se quedan: revísalas.
tools/entity_editor/undo = Deshacer
tools/entity_editor/undo_tip = Deshacer el último cambio (Ctrl+Z)
tools/entity_editor/redo = Rehacer
tools/entity_editor/redo_tip = Rehacer lo deshecho (Ctrl+Y)
tools/entity_editor/no_entity = Elige una entrada a la izquierda o crea una con «Nueva».
tools/entity_editor/file = Mod {0} · archivo {1}
tools/entity_editor/id = Id
tools/entity_editor/id_tip = Id único: los escenarios, los mapas y otros mods encuentran la entrada por él. Un mod que depende del propietario y trae el mismo id sustituye la entrada
tools/entity_editor/locale_key_tip = Clave de localización; los textos de abajo van a locale/*.properties del mod
tools/entity_editor/text_in = texto ({0})
tools/entity_editor/picture_pick_tip = Elegir una imagen de las carpetas del mod
tools/entity_editor/picture_pick_title = Imágenes del grupo «{0}»
tools/entity_editor/section_other = Otros campos
tools/entity_editor/other_hint = Campos que el formulario no muestra (datos de otros mods, botín…) como JSON. Los cambios se aplican en cuanto el JSON es válido.
tools/entity_editor/other_invalid = Otros campos: JSON no válido — {0}
tools/entity_editor/other_applied = Otros campos aplicados
tools/entity_editor/opened = {0} entradas en el mod {1}
tools/entity_editor/added = Entrada {0} añadida a {1}
tools/entity_editor/deleted = Entrada {0} borrada (guarda para escribirlo)
tools/entity_editor/saved = Guardado: {0} archivo(s), {1} texto(s) en el mod {2}; el juego ha releído sus datos
tools/entity_editor/broken_files = No se leyeron los archivos con JSON roto: {0}
tools/entity_editor/problem_id = Una entrada del archivo {0} tiene el id vacío
tools/entity_editor/problem_duplicate = El id {0} aparece más de una vez
+337
View File
@@ -0,0 +1,337 @@
mod_editor_tools_name = エディターツール
mod_editor_tools_desc = 実行中のゲームの上に開くオーサリング用ウィンドウ:シーン、シナリオ、マップ、インタラクティブ、ライトの各エディターと Godot シーンビルダー。保存内容は普通の MOD ファイルで、ゲームはすぐに反映します。
tools/common/save = 保存
tools/common/save_tip = ファイルを保存(Ctrl+S)
tools/common/saved = 保存しました:{0}
tools/common/loaded = 開きました:{0}
tools/common/load_error = 読み込みエラー
tools/common/readonly = 保存できません:MOD が読み取り専用(zip)か、パスが不正です。
tools/common/no_writable_mods = 書き込める MOD がありません(編集できるのはフォルダー形式の MOD だけです)。
tools/common/no_files = MOD「{0}」にはまだこの種類のファイルがありません——「+」で作成してください。
tools/common/no_file_to_save = 先にファイルを選ぶか作成してください(ファイル一覧の横の「+」)。
tools/common/new_file = 新しいファイル
tools/common/new_file_hint = 最初に保存したとき、{0}/{1}/ に拡張子 {2} のファイルが作られます。
tools/common/new_file_status = 新しいファイル {0}——保存時に作成されます。
tools/common/mod_tip = MOD(この種類のファイルがある MOD が先頭;·——まだなし)
tools/common/file_tip = MOD 内のファイル
tools/common/search = 検索…
tools/common/json = JSON
tools/common/json_tip = できあがるファイルの JSON を表示
tools/common/background_missing = シーン「{1}」の背景「{0}」はどの MOD にもありません——手で選んでください。
tools/map_editor/title = マップエディター
tools/map_editor/hint = 左クリック——選択/ドラッグ · 右クリック——頂点を削除 · ダブルクリック、Enter、Esc——領域を確定 · Del——ゾーンを削除 · Ctrl+S——保存
tools/map_editor/reload_map = ゲーム内のマップを更新
tools/map_editor/reload_map_tip = 実行中のゲームで、保存済みのマップファイルを読み直す
tools/map_editor/reloaded = ゲーム内のマップを更新しました。
tools/map_editor/select_mode = 選択
tools/map_editor/draw_region = 描画
tools/map_editor/draw_started = マップをクリックして頂点を置きます。ダブルクリック、Enter、Esc で確定、右クリックで最後の頂点を取り消します。
tools/map_editor/background = プレビューの背景
tools/map_editor/zones = ゾーン
tools/map_editor/add_region = + 領域
tools/map_editor/add_region_tip = 新しい領域:マップをクリックして多角形を描く
tools/map_editor/add_marker = + マーカー
tools/map_editor/add_label = + ラベル
tools/map_editor/delete = ゾーンを削除
tools/map_editor/delete_tip = 選択したゾーンを削除(Del)
tools/map_editor/delete_confirm = 選択したゾーンを削除しますか? 変更は保存時にファイルへ反映されます。
tools/map_editor/deleted = ゾーン「{0}」を削除しました。
tools/map_editor/props = ゾーンのプロパティ
tools/map_editor/zone_missing = 一覧かマップでゾーンを選んでください。
tools/map_editor/id = ID
tools/map_editor/id_empty = ゾーンの ID は空にできません。
tools/map_editor/duplicate_id = ゾーンの ID はすでに使われています:
tools/map_editor/title_key = タイトル
tools/map_editor/description = 説明
tools/map_editor/no_translation = ローカライズにキーがありません
tools/map_editor/scene = シーン
tools/map_editor/scene_none = (なし)
tools/map_editor/availability = 利用可否
tools/map_editor/locked = 移動不可
tools/map_editor/hidden = マップに表示しない
tools/map_editor/effective = ゲーム内:
tools/map_editor/effective_open = 開いている
tools/map_editor/effective_locked = 閉鎖(Locked フラグ)
tools/map_editor/effective_no_scene = 閉鎖(シーンなし)
tools/map_editor/effective_unknown_scene = 閉鎖(シーンが見つからない)
tools/map_editor/effective_hidden = マップに表示しない
tools/map_editor/marker = マーカー
tools/map_editor/image = アイコン
tools/map_editor/image_placeholder = テクスチャ ID
tools/map_editor/icon_search = アイコンを探す…
tools/map_editor/label_section = ラベル
tools/map_editor/label = マップ上のテキストラベル
tools/map_editor/x = X(0–1)
tools/map_editor/y = Y(0–1)
tools/map_editor/vertices = 多角形の頂点
tools/map_editor/vertices_hint = マップ上で頂点をドラッグ。頂点を右クリックすると削除。
tools/map_editor/add_vertex = + 頂点
tools/map_editor/delete_vertex = 頂点を削除
tools/map_editor/preview = ▶ ゲームと同じ表示
tools/map_editor/preview_tip = ゲームと同じようにマップを見る:ホバー、ゾーンカード、マーカー(未保存の編集も含む)
tools/map_editor/preview_on = プレビュー:Esc か ✕ でエディターに戻ります。
tools/map_editor/preview_off = 編集モード。
tools/map_editor/preview_missing = マップのシーン(map_widget.tscn)を読み込めません。
tools/scenario_editor/title = シナリオエディター
tools/scenario_editor/hint = Ctrl+S——保存 · F5——シナリオをプレイ · グラフのシーンをクリック——そのシナリオ
tools/scenario_editor/play = ▶ プレイ
tools/scenario_editor/play_tooltip = 保存してゲーム内でシナリオをプレイ(F5)
tools/scenario_editor/play_stopped = テストプレイを止めました。
tools/scenario_editor/stop = ■ 停止
tools/scenario_editor/open_file = 外部エディターで開く
tools/scenario_editor/open_file_tip = システムの既定のプログラムでファイルを開く
tools/scenario_editor/open_file_error = ファイルを開けません
tools/scenario_editor/rescan = 再スキャン
tools/scenario_editor/rescan_tip = シナリオ一覧とグラフを読み直す(ファイルを追加した後)
tools/scenario_editor/rescanned = シナリオ一覧を更新しました。
tools/scenario_editor/search_files = ファイルを探す…
tools/scenario_editor/tab_files = ファイル
tools/scenario_editor/tab_info = 概要
tools/scenario_editor/tab_graph = シーングラフ
tools/scenario_editor/graph_select_node = グラフでシーンを選ぶと、そのシナリオと遷移が表示されます。
tools/scenario_editor/graph_node = MOD {0} · シナリオ {1}
tools/scenario_editor/graph_node_bg = 背景:{0}
tools/scenario_editor/graph_detail_mod = MOD:{0}
tools/scenario_editor/graph_detail_scenarios = シナリオ({0})、優先度順:
tools/scenario_editor/info_scene = シーン
tools/scenario_editor/info_priority = 優先度
tools/scenario_editor/info_condition = 条件
tools/scenario_editor/info_switch_targets = シーン切り替え
tools/scenario_editor/info_locale_keys = ローカライズキー
tools/scenario_editor/info_quest_refs = クエスト
tools/scenario_editor/bg_previews = シナリオの背景
tools/scenario_editor/bg_missing = 背景が見つかりません:{0}
tools/scenario_editor/no_backgrounds = このシナリオは背景を変えません
tools/scenario_editor/save_error = 保存エラー:{0}
tools/scenario_editor/readonly_zip = zip の MOD のファイルは読み取り専用です
tools/scenario_editor/no_scene_error = シナリオのヘッダーにシーン(scene:)がありません——プレイするものがありません。
tools/scenario_editor/unsaved_title = 保存していない変更
tools/scenario_editor/unsaved_switch_text = このタブには保存していない変更があります。保存せずに閉じますか?
tools/scene_builder/title = シーンビルダー
tools/scene_builder/hint = ノードは本体かハンドルでドラッグ · クリック——カーソル下のノードを選択 · Del——ノードを削除 · Ctrl+S——保存して再構築
tools/scene_builder/base_scene = MOD のシーンを重ねる基本シーン
tools/scene_builder/mod = MOD
tools/scene_builder/scene = MOD のシーン(.tscn)
tools/scene_builder/empty_scene = (空の基本シーン)
tools/scene_builder/select_mod = — MOD を選択 —
tools/scene_builder/select_scene = — シーンを選択 —
tools/scene_builder/save_tip = .tscn を保存してプレビューを再構築(Ctrl+S)
tools/scene_builder/reload = ディスクから読み直す
tools/scene_builder/reload_tip = 保存していない編集を捨てて、シーンファイルを読み直す
tools/scene_builder/reloaded = ディスクからシーンを読み直しました。
tools/scene_builder/saved_ok = 保存しました。
tools/scene_builder/saved_rebuilt = 保存し、プレビューを再構築しました。
tools/scene_builder/autosaved = 自動保存しました。
tools/scene_builder/error_save = 保存エラー:MOD は読み取り専用です。
tools/scene_builder/error_no_scene = 先に MOD のシーンを選んでください。
tools/scene_builder/hierarchy = 階層
tools/scene_builder/hierarchy_hint = 金——MOD シーンのルート、明るい——編集できるノード、薄い——基本シーン(読み取り専用)。
tools/scene_builder/node_type_tip = 新しいノードの型
tools/scene_builder/add_node_tip = 選択したノード(またはシーンのルート)の中に、選んだ型のノードを追加
tools/scene_builder/delete_node_tip = 選択した MOD シーンのノードを削除
tools/scene_builder/delete_node_title = ノードを削除
tools/scene_builder/delete_node_confirm = 選択したノードを子ごと削除しますか? ファイルは自動で保存されます。
tools/scene_builder/refresh_tip = ノードツリーを更新
tools/scene_builder/tab_inspector = インスペクター
tools/scene_builder/tab_tscn = .tscn ソース
tools/scene_builder/tscn_hint = インスペクターとハンドルの編集は自動で保存されます。テキストを手で編集したときは Ctrl+S で保存し、同時にプレビューも再構築されます。
tools/scene_builder/inspector_cat_container = コンテナー内
tools/scene_builder/inspector_select = MOD シーンのノードを選ぶとプロパティを編集できます
tools/scene_builder/inspector_readonly = 読み取り専用:基本シーンのノードです
tools/scene_builder/inspector_missing_section = tscn にノードのセクションがありません(インスタンス化したサブシーンのノードはまだ編集できません)
tools/scene_builder/inspector_preset = アンカーのプリセット
tools/scene_builder/inspector_cat_layout = レイアウト
tools/scene_builder/inspector_cat_rect = 矩形
tools/scene_builder/inspector_cat_visibility = 表示
tools/scene_builder/inspector_cat_misc = その他
tools/scene_builder/inspector_bad_value = この値は tscn に書き込めません。
tools/scene_builder/inspector_in_file_tooltip = tscn ファイルに保存されています
tools/scene_builder/inspector_container_banner = このノードはコンテナーが管理しています:アンカーとオフセットは親が計算し直すので、ここでは効果がありません。
tools/scene_builder/err_add_failed = エラー:この型のノードを作成できません。
tools/scene_builder/err_add_parent_missing = エラー:tscn に親のセクションがありません(インスタンス化したサブシーンの親には対応していません)。
tools/scene_builder/err_del_root = エラー:シーンのルートは削除できません。
tools/scene_builder/err_del_none = エラー:削除する MOD のノードを選んでください。
tools/interactive_editor/title = インタラクティブエディター
tools/interactive_editor/hint = コライダーをクリック——選択 · ハンドルをドラッグ · 多角形モードでは背景のクリックで頂点を追加、頂点の右クリックで削除 · Del——オブジェクトを削除 · Ctrl+S——保存
tools/interactive_editor/items = ファイル内のオブジェクト
tools/interactive_editor/add = + 新規
tools/interactive_editor/duplicate = 複製
tools/interactive_editor/delete = オブジェクトを削除
tools/interactive_editor/delete_confirm = 選択したインタラクティブをファイルから取り除きますか? 変更は保存時にファイルへ反映されます。
tools/interactive_editor/deleted = オブジェクト「{0}」を取り除きました。
tools/interactive_editor/background = プレビューの背景
tools/interactive_editor/props = プロパティ
tools/interactive_editor/none_selected = 一覧かプレビューでオブジェクトを選んでください。
tools/interactive_editor/label_id = ID
tools/interactive_editor/label_texture_key = テクスチャ
tools/interactive_editor/texture_placeholder = スプライトなし
tools/interactive_editor/texture_tip = スプライトのテクスチャ ID。空——見えないクリック領域
tools/interactive_editor/label_layer = レイヤー
tools/interactive_editor/label_z = Z 順
tools/interactive_editor/label_cursor = カーソル
tools/interactive_editor/cursor_none = (既定)
tools/interactive_editor/enabled = 有効
tools/interactive_editor/visible = 表示
tools/interactive_editor/collider = コライダー
tools/interactive_editor/label_collider_type = 形
tools/interactive_editor/hit_rect = 矩形
tools/interactive_editor/hit_circle = 円
tools/interactive_editor/hit_polygon = 多角形
tools/interactive_editor/label_x = X
tools/interactive_editor/label_y = Y
tools/interactive_editor/label_w = 幅
tools/interactive_editor/label_h = 高さ
tools/interactive_editor/label_cx = 中心 X
tools/interactive_editor/label_cy = 中心 Y
tools/interactive_editor/label_r = 半径
tools/interactive_editor/polygon_hint = 背景をクリックすると頂点を追加、頂点を右クリックすると削除。頂点は 3 つ以上。
tools/interactive_editor/polygon_too_small = 多角形「{0}」の頂点が 3 つ未満です。
tools/interactive_editor/highlight = ハイライト
tools/interactive_editor/label_highlight_color = 色
tools/interactive_editor/label_highlight_scale = 拡大率
tools/interactive_editor/actions = アクション
tools/interactive_editor/label_onclick_scenario = シナリオ
tools/interactive_editor/label_onclick_commands = クリック時のコマンド(1 行に 1 つ)
tools/interactive_editor/label_hover_enter = カーソルが入ったとき
tools/interactive_editor/label_hover_leave = カーソルが出たとき
tools/interactive_editor/id_empty = ID が空のオブジェクトがあります。
tools/interactive_editor/duplicate_id = オブジェクト ID が重複しています:
tools/light_editor/title = ライトエディター
tools/light_editor/hint = 背景をダブルクリック——新しいライト · ライトをドラッグ · Del——削除 · Ctrl+S——シナリオに light_set を書き込む
tools/light_editor/scenario_tip = light_set の行を編集するシナリオ(●——すでにライトを設定済み)
tools/light_editor/pick_scenario = — シナリオを選択 —
tools/light_editor/save_tip = シナリオに light_set の行を書き込む(Ctrl+S)
tools/light_editor/copy_line = 行をコピー
tools/light_editor/copy_line_tip = light_set コマンドをクリップボードにコピー
tools/light_editor/copied = light_set の行をコピーしました。
tools/light_editor/show_line = light_set
tools/light_editor/loaded = {0} のライトを読み込みました。
tools/light_editor/no_lights_yet = {0} にはまだ light_set がありません——ライトを追加すると、ヘッダーの後に行が入ります。
tools/light_editor/static_lights = ライト
tools/light_editor/add_light = + ライト
tools/light_editor/delete = 削除
tools/light_editor/limit = 静的ライトは最大 {0} 個です。
tools/light_editor/selected_props = 選択したライト
tools/light_editor/none_selected = 一覧かプレビューでライトを選んでください。
tools/light_editor/x = X(0–1)
tools/light_editor/y = Y(0–1)
tools/light_editor/radius = 半径
tools/light_editor/radius_tip = 幅 1920 px のゲーム画面でのピクセル半径
tools/light_editor/influence = 強さ
tools/light_editor/influence_tip = ライトの影響(0–1)
tools/light_editor/color = 色
tools/light_editor/background = プレビューの背景
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 を設定してください——それがないカタログ項目は役に立ちません。
tools/scene_editor/title = シーンエディター
tools/scene_editor/hint = シーンは場所です:背景、環境音、シナリオ。Ctrl+S で保存 · 一覧の背景をダブルクリックで追加
tools/scene_editor/save_tip = 選んだ MOD にシーンファイルを保存(Ctrl+S)。ゲームはすぐに変更を反映します
tools/scene_editor/new_scene = + 新しいシーン
tools/scene_editor/new_scene_tip = 新しい場所を作る:名前、MOD、背景。シナリオは後から追加でき、シナリオのないシーンでも構いません
tools/scene_editor/delete = 削除
tools/scene_editor/delete_tip = 選んだ MOD のシーンファイルを削除
tools/scene_editor/delete_confirm = 選んだ MOD からこのシーンのファイルを削除しますか? シーンのシナリオは残ります。
tools/scene_editor/go = ▶ シーンへ移動
tools/scene_editor/go_tip = ゲームをこのシーンに切り替える
tools/scene_editor/rescan_tip = シーン、背景、音を読み直す(MOD にファイルを追加した後)
tools/scene_editor/scenes = シーン
tools/scene_editor/backgrounds = シーンの背景(入るたびにランダムに 1 つ)
tools/scene_editor/all_backgrounds = すべての背景
tools/scene_editor/add_background = + シーンに背景を追加
tools/scene_editor/add_background_tip = 表示中の背景をシーンに追加(または一覧でダブルクリック)
tools/scene_editor/remove_background = シーンから背景を外す
tools/scene_editor/properties = プロパティ
tools/scene_editor/name = 名前
tools/scene_editor/name_tip = シーンの id:ラテン文字、数字、_。switch_scene とシナリオのヘッダーが参照します。作成時にだけ設定
tools/scene_editor/mod = 保存先
tools/scene_editor/mod_tip = シーンファイルを書き込む MOD(フォルダー形式の MOD のみ)
tools/scene_editor/ambient = 音
tools/scene_editor/ambient_tip = シーンで流れる環境音
tools/scene_editor/listen = ▶
tools/scene_editor/listen_tip = 環境音を聴く
tools/scene_editor/stop = ■
tools/scene_editor/default_scenario = 既定のシナリオ
tools/scene_editor/default_scenario_tip = シーンの他のシナリオがなくなったときに流れる
tools/scene_editor/scenarios = シーンのシナリオ
tools/scene_editor/open_scenario = 開く
tools/scene_editor/open_scenario_tip = 選択したシナリオをシナリオエディターで開く(またはダブルクリック)
tools/scene_editor/new_scenario = + 新しいシナリオ
tools/scene_editor/new_scenario_tip = 選んだ MOD にこのシーンのシナリオを作って開く
tools/scene_editor/none = — なし —
tools/scene_editor/effective = ゲーム内:{0}(MOD {1} から)
tools/scene_editor/adds_to = シーンの定義元:{0}。保存すると、選んだ MOD にそれを補うファイルが作られます。
tools/scene_editor/also_in = ほかに補っている MOD:{0}
tools/scene_editor/no_writable_mod = シーンを保存できるフォルダー形式の MOD がありません。
tools/scene_editor/bad_name = シーンの名前を設定してください:ラテン文字、数字、_(先頭は文字)。
tools/scene_editor/name_taken = シーン「{0}」はすでにあります。一覧から選んでください。
tools/scene_editor/need_background = シーンに背景を 1 つ以上追加してください。
tools/scene_editor/created = シーン「{0}」を作成しました。
tools/scene_editor/deleted = 削除しました:{0}
tools/scene_editor/opened = シーン:{0}
tools/scene_editor/new_started = 新しいシーン:名前と MOD を設定し、背景を追加してください。
tools/scene_editor/already_there = 背景 {0} はすでにシーンにあります。
tools/scene_editor/go_failed = ゲームはシーン「{0}」を知りません。先に保存してください。
tools/scene_editor/went = シーン {0} に切り替えました。
tools/scene_editor/rescanned = シーン、背景、音を読み直しました。
tools/scene_editor/scenario_created = シナリオ {0} を作成しました。
tools/scene_editor/no_scenarios = このシーンにはまだシナリオがありません
tools/scene_editor/no_backgrounds = このシーンには背景がありません:下の一覧から選んで追加してください。
tools/scene_editor/pick_scene = 左でシーンを選ぶか、新しく作ってください。
tools/scene_editor/preview_hint = 背景を選ぶと表示されます。
tools/scene_editor/not_in_scene = (シーンにない)
tools/scene_editor/new_line_text = 新しいシーン。この行をあなたの文章に置き換えてください。
tools/entity_editor/hint = 左は検索付きの一覧、右は選んだ項目のフィールド · Ctrl+N 新規、Ctrl+D コピー · Ctrl+Z/Y 元に戻す/やり直す · Ctrl+S 保存、ゲームはすぐに変更を反映します
tools/entity_editor/section_list = MOD の項目
tools/entity_editor/search = 名前か id で検索
tools/entity_editor/mod_tip = 項目を編集する MOD。新しい項目はこの MOD のファイルに入ります
tools/entity_editor/new = 新規
tools/entity_editor/new_tip = 選んだ MOD に項目を追加(Ctrl+N)
tools/entity_editor/duplicate = コピー
tools/entity_editor/duplicate_tip = 選択した項目を新しい id でコピー(Ctrl+D)
tools/entity_editor/delete = 削除
tools/entity_editor/delete_tip = 選択した項目を MOD のファイルから取り除く
tools/entity_editor/delete_title = 項目を削除しますか?
tools/entity_editor/delete_text = 保存すると項目は MOD のファイルから消えます。シナリオやマップからの参照は残るので確認してください。
tools/entity_editor/undo = 元に戻す
tools/entity_editor/undo_tip = 最後の変更を元に戻す(Ctrl+Z)
tools/entity_editor/redo = やり直す
tools/entity_editor/redo_tip = 元に戻した操作をやり直す(Ctrl+Y)
tools/entity_editor/no_entity = 左で項目を選ぶか、「新規」で作ってください。
tools/entity_editor/file = MOD {0} · ファイル {1}
tools/entity_editor/id = Id
tools/entity_editor/id_tip = 一意の id:シナリオ、マップ、他の MOD はこれで項目を見つけます。所有者に依存して同じ id を持つ MOD は、その項目を置き換えます
tools/entity_editor/locale_key_tip = ローカライズキー。下のテキストは MOD の locale/*.properties に書かれます
tools/entity_editor/text_in = テキスト({0})
tools/entity_editor/picture_pick_tip = MOD のフォルダーから画像を選ぶ
tools/entity_editor/picture_pick_title = 「{0}」グループの画像
tools/entity_editor/section_other = その他のフィールド
tools/entity_editor/other_hint = フォームに表示されないフィールド(他の MOD のデータ、戦利品…)を JSON で。JSON が正しくなった時点で編集が適用されます。
tools/entity_editor/other_invalid = その他のフィールド:不正な JSON——{0}
tools/entity_editor/other_applied = その他のフィールドを適用しました
tools/entity_editor/opened = MOD {1} の項目 {0} 件
tools/entity_editor/added = 項目 {0} を {1} に追加しました
tools/entity_editor/deleted = 項目 {0} を削除しました(書き込むには保存)
tools/entity_editor/saved = 保存しました:ファイル {0}、テキスト {1}(MOD {2})。ゲームはデータを読み直しました
tools/entity_editor/broken_files = JSON が壊れているファイルは読み込みませんでした:{0}
tools/entity_editor/problem_id = ファイル {0} に id が空の項目があります
tools/entity_editor/problem_duplicate = id {0} が複数回出てきます
+337
View File
@@ -0,0 +1,337 @@
mod_editor_tools_name = 에디터 도구
mod_editor_tools_desc = 실행 중인 게임 위에 열리는 창작용 창: 장면, 시나리오, 맵, 상호작용, 조명 에디터와 Godot 장면 빌더. 저장하는 것은 모두 일반 모드 파일이며 게임이 즉시 적용합니다.
tools/common/save = 저장
tools/common/save_tip = 파일 저장 (Ctrl+S)
tools/common/saved = 저장됨: {0}
tools/common/loaded = 열림: {0}
tools/common/load_error = 불러오기 오류
tools/common/readonly = 저장할 수 없습니다: 모드가 읽기 전용(zip)이거나 경로가 잘못되었습니다.
tools/common/no_writable_mods = 쓸 수 있는 모드가 없습니다(폴더형 모드만 편집할 수 있습니다).
tools/common/no_files = 모드 "{0}"에는 아직 이런 파일이 없습니다 — "+"로 만드세요.
tools/common/no_file_to_save = 먼저 파일을 고르거나 만드세요(파일 목록 옆의 "+").
tools/common/new_file = 새 파일
tools/common/new_file_hint = 처음 저장할 때 {0}/{1}/에 확장자 {2}인 파일이 만들어집니다.
tools/common/new_file_status = 새 파일 {0} — 저장할 때 만들어집니다.
tools/common/mod_tip = 모드(이런 파일이 이미 있는 모드가 먼저; · — 아직 없음)
tools/common/file_tip = 모드 안의 파일
tools/common/search = 검색…
tools/common/json = JSON
tools/common/json_tip = 결과 파일의 JSON 보기
tools/common/background_missing = 장면 "{1}"의 배경 "{0}"을(를) 제공하는 모드가 없습니다 — 직접 고르세요.
tools/map_editor/title = 맵 에디터
tools/map_editor/hint = 좌클릭 — 선택 / 드래그 · 우클릭 — 꼭짓점 삭제 · 더블클릭, Enter 또는 Esc — 영역 완성 · Del — 구역 삭제 · Ctrl+S — 저장
tools/map_editor/reload_map = 게임 내 지도 새로 고침
tools/map_editor/reload_map_tip = 실행 중인 게임에서 저장된 지도 파일을 다시 읽습니다
tools/map_editor/reloaded = 게임 내 지도를 새로 고쳤습니다.
tools/map_editor/select_mode = 선택
tools/map_editor/draw_region = 그리기
tools/map_editor/draw_started = 지도를 클릭해 꼭짓점을 놓으세요. 더블클릭, Enter 또는 Esc로 완성, 우클릭으로 마지막 꼭짓점을 지웁니다.
tools/map_editor/background = 미리 보기 배경
tools/map_editor/zones = 구역
tools/map_editor/add_region = + 영역
tools/map_editor/add_region_tip = 새 영역: 지도를 클릭해 다각형을 그립니다
tools/map_editor/add_marker = + 표식
tools/map_editor/add_label = + 라벨
tools/map_editor/delete = 구역 삭제
tools/map_editor/delete_tip = 선택한 구역 삭제 (Del)
tools/map_editor/delete_confirm = 선택한 구역을 삭제할까요? 변경은 저장할 때 파일에 반영됩니다.
tools/map_editor/deleted = 구역 "{0}"을(를) 삭제했습니다.
tools/map_editor/props = 구역 속성
tools/map_editor/zone_missing = 목록이나 지도에서 구역을 고르세요.
tools/map_editor/id = ID
tools/map_editor/id_empty = 구역 ID는 비워 둘 수 없습니다.
tools/map_editor/duplicate_id = 이미 쓰인 구역 ID:
tools/map_editor/title_key = 제목
tools/map_editor/description = 설명
tools/map_editor/no_translation = 로컬라이즈에 키가 없음
tools/map_editor/scene = 장면
tools/map_editor/scene_none = (없음)
tools/map_editor/availability = 이용 여부
tools/map_editor/locked = 이동 불가
tools/map_editor/hidden = 지도에서 숨김
tools/map_editor/effective = 게임에서:
tools/map_editor/effective_open = 열림
tools/map_editor/effective_locked = 잠김 (Locked 플래그)
tools/map_editor/effective_no_scene = 잠김 (장면 없음)
tools/map_editor/effective_unknown_scene = 잠김 (장면을 찾을 수 없음)
tools/map_editor/effective_hidden = 지도에서 숨김
tools/map_editor/marker = 표식
tools/map_editor/image = 아이콘
tools/map_editor/image_placeholder = 텍스처 ID
tools/map_editor/icon_search = 아이콘 찾기…
tools/map_editor/label_section = 라벨
tools/map_editor/label = 지도 위의 텍스트 라벨
tools/map_editor/x = X (0–1)
tools/map_editor/y = Y (0–1)
tools/map_editor/vertices = 다각형 꼭짓점
tools/map_editor/vertices_hint = 지도에서 꼭짓점을 끌어 옮기세요. 꼭짓점을 우클릭하면 삭제됩니다.
tools/map_editor/add_vertex = + 꼭짓점
tools/map_editor/delete_vertex = 꼭짓점 삭제
tools/map_editor/preview = ▶ 게임처럼 보기
tools/map_editor/preview_tip = 게임에서처럼 지도 보기: 호버, 구역 카드, 표식(저장하지 않은 편집 포함)
tools/map_editor/preview_on = 미리 보기: Esc 또는 ✕로 에디터로 돌아갑니다.
tools/map_editor/preview_off = 편집 모드.
tools/map_editor/preview_missing = 지도 장면(map_widget.tscn)을 불러올 수 없습니다.
tools/scenario_editor/title = 시나리오 에디터
tools/scenario_editor/hint = Ctrl+S — 저장 · F5 — 시나리오 플레이 · 그래프의 장면 클릭 — 그 장면의 시나리오
tools/scenario_editor/play = ▶ 플레이
tools/scenario_editor/play_tooltip = 저장하고 게임에서 시나리오 플레이 (F5)
tools/scenario_editor/play_stopped = 테스트 플레이를 멈췄습니다.
tools/scenario_editor/stop = ■ 정지
tools/scenario_editor/open_file = 외부 에디터로 열기
tools/scenario_editor/open_file_tip = 시스템 기본 프로그램으로 파일 열기
tools/scenario_editor/open_file_error = 파일을 열 수 없습니다
tools/scenario_editor/rescan = 다시 검색
tools/scenario_editor/rescan_tip = 시나리오 목록과 그래프를 다시 읽습니다(파일을 추가한 뒤)
tools/scenario_editor/rescanned = 시나리오 목록을 새로 고쳤습니다.
tools/scenario_editor/search_files = 파일 찾기…
tools/scenario_editor/tab_files = 파일
tools/scenario_editor/tab_info = 요약
tools/scenario_editor/tab_graph = 장면 그래프
tools/scenario_editor/graph_select_node = 그래프에서 장면을 고르면 그 시나리오와 전환이 보입니다.
tools/scenario_editor/graph_node = 모드 {0} · 시나리오 {1}
tools/scenario_editor/graph_node_bg = 배경: {0}
tools/scenario_editor/graph_detail_mod = 모드: {0}
tools/scenario_editor/graph_detail_scenarios = 시나리오 ({0}), 우선순위순:
tools/scenario_editor/info_scene = 장면
tools/scenario_editor/info_priority = 우선순위
tools/scenario_editor/info_condition = 조건
tools/scenario_editor/info_switch_targets = 장면 전환
tools/scenario_editor/info_locale_keys = 로컬라이즈 키
tools/scenario_editor/info_quest_refs = 퀘스트
tools/scenario_editor/bg_previews = 시나리오 배경
tools/scenario_editor/bg_missing = 배경을 찾을 수 없음: {0}
tools/scenario_editor/no_backgrounds = 이 시나리오는 배경을 바꾸지 않습니다
tools/scenario_editor/save_error = 저장 오류: {0}
tools/scenario_editor/readonly_zip = zip 모드의 파일은 읽기 전용입니다
tools/scenario_editor/no_scene_error = 시나리오 헤더에 장면(scene:)이 없습니다 — 플레이할 것이 없습니다.
tools/scenario_editor/unsaved_title = 저장하지 않은 변경
tools/scenario_editor/unsaved_switch_text = 이 탭에 저장하지 않은 변경이 있습니다. 저장하지 않고 닫을까요?
tools/scene_builder/title = 장면 빌더
tools/scene_builder/hint = 노드는 본체나 핸들로 끌어 옮기기 · 클릭 — 커서 아래 노드 선택 · Del — 노드 삭제 · Ctrl+S — 저장하고 다시 빌드
tools/scene_builder/base_scene = 모드 장면을 겹쳐 보여 줄 기본 장면
tools/scene_builder/mod = 모드
tools/scene_builder/scene = 모드 장면 (.tscn)
tools/scene_builder/empty_scene = (빈 기본 장면)
tools/scene_builder/select_mod = — 모드 선택 —
tools/scene_builder/select_scene = — 장면 선택 —
tools/scene_builder/save_tip = .tscn을 저장하고 미리 보기를 다시 빌드 (Ctrl+S)
tools/scene_builder/reload = 디스크에서 다시 읽기
tools/scene_builder/reload_tip = 저장하지 않은 편집을 버리고 장면 파일을 다시 읽습니다
tools/scene_builder/reloaded = 디스크에서 장면을 다시 읽었습니다.
tools/scene_builder/saved_ok = 저장했습니다.
tools/scene_builder/saved_rebuilt = 저장했고, 미리 보기를 다시 빌드했습니다.
tools/scene_builder/autosaved = 자동 저장했습니다.
tools/scene_builder/error_save = 저장 오류: 모드가 읽기 전용입니다.
tools/scene_builder/error_no_scene = 먼저 모드 장면을 고르세요.
tools/scene_builder/hierarchy = 계층
tools/scene_builder/hierarchy_hint = 금색 — 모드 장면의 루트, 밝은색 — 편집 가능한 노드, 흐린색 — 기본 장면(읽기 전용).
tools/scene_builder/node_type_tip = 새 노드의 유형
tools/scene_builder/add_node_tip = 선택한 노드(또는 장면 루트) 안에 고른 유형의 노드를 추가합니다
tools/scene_builder/delete_node_tip = 선택한 모드 장면 노드 삭제
tools/scene_builder/delete_node_title = 노드 삭제
tools/scene_builder/delete_node_confirm = 선택한 노드를 자식과 함께 삭제할까요? 파일은 자동으로 저장됩니다.
tools/scene_builder/refresh_tip = 노드 트리 새로 고침
tools/scene_builder/tab_inspector = 인스펙터
tools/scene_builder/tab_tscn = .tscn 소스
tools/scene_builder/tscn_hint = 인스펙터와 핸들로 한 편집은 저절로 저장됩니다. 텍스트를 직접 고친 내용은 Ctrl+S로 저장하며, 그때 미리 보기도 다시 빌드됩니다.
tools/scene_builder/inspector_cat_container = 컨테이너 안
tools/scene_builder/inspector_select = 속성을 편집하려면 모드 장면의 노드를 고르세요
tools/scene_builder/inspector_readonly = 읽기 전용: 기본 장면에 속한 노드입니다
tools/scene_builder/inspector_missing_section = tscn에서 노드 섹션을 찾을 수 없습니다(인스턴스화한 하위 장면의 노드는 아직 편집할 수 없습니다)
tools/scene_builder/inspector_preset = 앵커 프리셋
tools/scene_builder/inspector_cat_layout = 레이아웃
tools/scene_builder/inspector_cat_rect = 사각형
tools/scene_builder/inspector_cat_visibility = 보이기
tools/scene_builder/inspector_cat_misc = 기타
tools/scene_builder/inspector_bad_value = 이 값은 tscn에 쓸 수 없습니다.
tools/scene_builder/inspector_in_file_tooltip = tscn 파일에 저장됨
tools/scene_builder/inspector_container_banner = 이 노드는 컨테이너가 관리합니다: 앵커와 오프셋은 부모가 다시 계산하므로 여기서는 효과가 없습니다.
tools/scene_builder/err_add_failed = 오류: 이 유형의 노드를 만들 수 없습니다.
tools/scene_builder/err_add_parent_missing = 오류: tscn에서 부모 섹션을 찾을 수 없습니다(인스턴스화한 하위 장면의 부모는 지원하지 않습니다).
tools/scene_builder/err_del_root = 오류: 장면 루트는 삭제할 수 없습니다.
tools/scene_builder/err_del_none = 오류: 삭제할 모드 노드를 고르세요.
tools/interactive_editor/title = 상호작용 에디터
tools/interactive_editor/hint = 충돌체 클릭 — 선택 · 핸들 드래그 · 다각형 모드에서는 배경 클릭으로 꼭짓점 추가, 꼭짓점 우클릭으로 삭제 · Del — 오브젝트 삭제 · Ctrl+S — 저장
tools/interactive_editor/items = 파일 안의 오브젝트
tools/interactive_editor/add = + 새로 만들기
tools/interactive_editor/duplicate = 복제
tools/interactive_editor/delete = 오브젝트 삭제
tools/interactive_editor/delete_confirm = 선택한 상호작용을 파일에서 지울까요? 변경은 저장할 때 파일에 반영됩니다.
tools/interactive_editor/deleted = 오브젝트 "{0}"을(를) 지웠습니다.
tools/interactive_editor/background = 미리 보기 배경
tools/interactive_editor/props = 속성
tools/interactive_editor/none_selected = 목록이나 미리 보기에서 오브젝트를 고르세요.
tools/interactive_editor/label_id = ID
tools/interactive_editor/label_texture_key = 텍스처
tools/interactive_editor/texture_placeholder = 스프라이트 없음
tools/interactive_editor/texture_tip = 스프라이트 텍스처 ID. 비우면 보이지 않는 클릭 영역
tools/interactive_editor/label_layer = 레이어
tools/interactive_editor/label_z = Z 순서
tools/interactive_editor/label_cursor = 커서
tools/interactive_editor/cursor_none = (기본)
tools/interactive_editor/enabled = 켜짐
tools/interactive_editor/visible = 보임
tools/interactive_editor/collider = 충돌체
tools/interactive_editor/label_collider_type = 모양
tools/interactive_editor/hit_rect = 사각형
tools/interactive_editor/hit_circle = 원
tools/interactive_editor/hit_polygon = 다각형
tools/interactive_editor/label_x = X
tools/interactive_editor/label_y = Y
tools/interactive_editor/label_w = 너비
tools/interactive_editor/label_h = 높이
tools/interactive_editor/label_cx = 중심 X
tools/interactive_editor/label_cy = 중심 Y
tools/interactive_editor/label_r = 반지름
tools/interactive_editor/polygon_hint = 배경을 클릭하면 꼭짓점이 추가되고, 꼭짓점을 우클릭하면 삭제됩니다. 꼭짓점은 3개 이상.
tools/interactive_editor/polygon_too_small = 다각형 "{0}"의 꼭짓점이 3개보다 적습니다.
tools/interactive_editor/highlight = 강조
tools/interactive_editor/label_highlight_color = 색
tools/interactive_editor/label_highlight_scale = 크기
tools/interactive_editor/actions = 액션
tools/interactive_editor/label_onclick_scenario = 시나리오
tools/interactive_editor/label_onclick_commands = 클릭할 때의 명령(한 줄에 하나)
tools/interactive_editor/label_hover_enter = 마우스가 들어올 때
tools/interactive_editor/label_hover_leave = 마우스가 나갈 때
tools/interactive_editor/id_empty = ID가 비어 있는 오브젝트가 있습니다.
tools/interactive_editor/duplicate_id = 중복된 오브젝트 ID:
tools/light_editor/title = 조명 에디터
tools/light_editor/hint = 배경 더블클릭 — 새 조명 · 조명 드래그 · Del — 삭제 · Ctrl+S — 시나리오에 light_set 쓰기
tools/light_editor/scenario_tip = light_set 줄을 편집할 시나리오 (● — 이미 조명을 설정함)
tools/light_editor/pick_scenario = — 시나리오 선택 —
tools/light_editor/save_tip = 시나리오에 light_set 줄 쓰기 (Ctrl+S)
tools/light_editor/copy_line = 줄 복사
tools/light_editor/copy_line_tip = light_set 명령을 클립보드에 복사
tools/light_editor/copied = light_set 줄을 복사했습니다.
tools/light_editor/show_line = light_set
tools/light_editor/loaded = {0}의 조명을 불러왔습니다.
tools/light_editor/no_lights_yet = {0}에는 아직 light_set이 없습니다 — 조명을 추가하면 헤더 뒤에 줄이 들어갑니다.
tools/light_editor/static_lights = 조명
tools/light_editor/add_light = + 조명
tools/light_editor/delete = 삭제
tools/light_editor/limit = 정적 조명은 최대 {0}개입니다.
tools/light_editor/selected_props = 선택한 조명
tools/light_editor/none_selected = 목록이나 미리 보기에서 조명을 고르세요.
tools/light_editor/x = X (0–1)
tools/light_editor/y = Y (0–1)
tools/light_editor/radius = 반지름
tools/light_editor/radius_tip = 너비 1920 px 게임 화면 기준 픽셀 반지름
tools/light_editor/influence = 세기
tools/light_editor/influence_tip = 조명 영향 (0–1)
tools/light_editor/color = 색
tools/light_editor/background = 미리 보기 배경
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을 설정하세요 — 그것 없는 카탈로그 항목은 쓸모가 없습니다.
tools/scene_editor/title = 장면 에디터
tools/scene_editor/hint = 장면은 장소입니다: 배경, 환경음, 시나리오. Ctrl+S로 저장 · 목록의 배경을 더블클릭하면 추가
tools/scene_editor/save_tip = 선택한 모드에 장면 파일 저장 (Ctrl+S). 게임에 바로 반영됩니다
tools/scene_editor/new_scene = + 새 장면
tools/scene_editor/new_scene_tip = 새 장소 만들기: 이름, 모드, 배경. 시나리오는 나중에 추가할 수 있고, 시나리오가 없는 장면도 괜찮습니다
tools/scene_editor/delete = 삭제
tools/scene_editor/delete_tip = 선택한 모드의 장면 파일 삭제
tools/scene_editor/delete_confirm = 선택한 모드에서 이 장면의 파일을 삭제할까요? 장면의 시나리오는 남습니다.
tools/scene_editor/go = ▶ 장면으로 이동
tools/scene_editor/go_tip = 게임을 이 장면으로 전환
tools/scene_editor/rescan_tip = 장면, 배경, 소리를 다시 읽습니다(모드에 파일을 추가한 뒤)
tools/scene_editor/scenes = 장면
tools/scene_editor/backgrounds = 장면 배경 (들어갈 때 하나가 무작위로 선택됨)
tools/scene_editor/all_backgrounds = 모든 배경
tools/scene_editor/add_background = + 장면에 배경 추가
tools/scene_editor/add_background_tip = 보이는 배경을 장면에 추가(또는 목록에서 더블클릭)
tools/scene_editor/remove_background = 장면에서 배경 빼기
tools/scene_editor/properties = 속성
tools/scene_editor/name = 이름
tools/scene_editor/name_tip = 장면 id: 라틴 문자, 숫자, _. switch_scene과 시나리오 헤더가 이를 참조합니다. 만들 때만 정합니다
tools/scene_editor/mod = 저장할 곳
tools/scene_editor/mod_tip = 장면 파일을 쓸 모드(폴더형 모드만)
tools/scene_editor/ambient = 소리
tools/scene_editor/ambient_tip = 장면에서 재생되는 환경음
tools/scene_editor/listen = ▶
tools/scene_editor/listen_tip = 환경음 듣기
tools/scene_editor/stop = ■
tools/scene_editor/default_scenario = 기본 시나리오
tools/scene_editor/default_scenario_tip = 장면에 다른 시나리오가 남지 않았을 때 재생됩니다
tools/scene_editor/scenarios = 장면의 시나리오
tools/scene_editor/open_scenario = 열기
tools/scene_editor/open_scenario_tip = 선택한 시나리오를 시나리오 에디터에서 열기(또는 더블클릭)
tools/scene_editor/new_scenario = + 새 시나리오
tools/scene_editor/new_scenario_tip = 선택한 모드에 이 장면의 시나리오를 만들고 엽니다
tools/scene_editor/none = — 없음 —
tools/scene_editor/effective = 게임에서: {0} (모드 {1})
tools/scene_editor/adds_to = 장면이 정의된 곳: {0}. 저장하면 선택한 모드에 이를 보충하는 파일이 만들어집니다.
tools/scene_editor/also_in = 이 장면을 보충하는 모드: {0}
tools/scene_editor/no_writable_mod = 장면을 저장할 폴더형 모드가 없습니다.
tools/scene_editor/bad_name = 장면 이름을 정하세요: 라틴 문자, 숫자, _ (문자로 시작).
tools/scene_editor/name_taken = 장면 "{0}"이(가) 이미 있습니다. 목록에서 고르세요.
tools/scene_editor/need_background = 장면에 배경을 하나 이상 추가하세요.
tools/scene_editor/created = 장면 "{0}"을(를) 만들었습니다.
tools/scene_editor/deleted = 삭제함: {0}
tools/scene_editor/opened = 장면: {0}
tools/scene_editor/new_started = 새 장면: 이름과 모드를 정하고 배경을 추가하세요.
tools/scene_editor/already_there = 배경 {0}은(는) 이미 장면에 있습니다.
tools/scene_editor/go_failed = 게임이 장면 "{0}"을(를) 모릅니다. 먼저 저장하세요.
tools/scene_editor/went = 장면 {0}(으)로 전환했습니다.
tools/scene_editor/rescanned = 장면, 배경, 소리를 다시 읽었습니다.
tools/scene_editor/scenario_created = 시나리오 {0}을(를) 만들었습니다.
tools/scene_editor/no_scenarios = 이 장면에는 아직 시나리오가 없습니다
tools/scene_editor/no_backgrounds = 이 장면에는 배경이 없습니다: 아래 목록에서 하나 골라 추가하세요.
tools/scene_editor/pick_scene = 왼쪽에서 장면을 고르거나 새로 만드세요.
tools/scene_editor/preview_hint = 배경을 고르면 보입니다.
tools/scene_editor/not_in_scene = (장면에 없음)
tools/scene_editor/new_line_text = 새 장면입니다. 이 줄을 당신의 글로 바꾸세요.
tools/entity_editor/hint = 왼쪽은 검색이 있는 목록, 오른쪽은 선택한 항목의 필드 · Ctrl+N 새로 만들기, Ctrl+D 복사 · Ctrl+Z/Y 되돌리기/다시 실행 · Ctrl+S 저장, 게임에 바로 반영
tools/entity_editor/section_list = 모드의 항목
tools/entity_editor/search = 이름 또는 id로 검색
tools/entity_editor/mod_tip = 항목을 편집할 모드. 새 항목은 이 모드의 파일에 들어갑니다
tools/entity_editor/new = 새로 만들기
tools/entity_editor/new_tip = 선택한 모드에 항목 추가 (Ctrl+N)
tools/entity_editor/duplicate = 복사
tools/entity_editor/duplicate_tip = 선택한 항목을 새 id로 복사 (Ctrl+D)
tools/entity_editor/delete = 삭제
tools/entity_editor/delete_tip = 선택한 항목을 모드 파일에서 제거
tools/entity_editor/delete_title = 항목을 삭제할까요?
tools/entity_editor/delete_text = 저장하면 항목이 모드 파일에서 사라집니다. 시나리오와 맵의 참조는 남으니 확인하세요.
tools/entity_editor/undo = 되돌리기
tools/entity_editor/undo_tip = 마지막 변경 되돌리기 (Ctrl+Z)
tools/entity_editor/redo = 다시 실행
tools/entity_editor/redo_tip = 되돌린 것을 다시 실행 (Ctrl+Y)
tools/entity_editor/no_entity = 왼쪽에서 항목을 고르거나 "새로 만들기"로 만드세요.
tools/entity_editor/file = 모드 {0} · 파일 {1}
tools/entity_editor/id = Id
tools/entity_editor/id_tip = 고유 id: 시나리오, 맵, 다른 모드가 이것으로 항목을 찾습니다. 소유자에 의존하고 같은 id를 가진 모드가 그 항목을 대체합니다
tools/entity_editor/locale_key_tip = 로컬라이즈 키. 아래 텍스트는 모드의 locale/*.properties에 쓰입니다
tools/entity_editor/text_in = 텍스트 ({0})
tools/entity_editor/picture_pick_tip = 모드 폴더에서 그림 고르기
tools/entity_editor/picture_pick_title = "{0}" 그룹의 그림
tools/entity_editor/section_other = 기타 필드
tools/entity_editor/other_hint = 폼에 보이지 않는 필드(다른 모드의 데이터, 전리품…)를 JSON으로. JSON이 올바르면 바로 적용됩니다.
tools/entity_editor/other_invalid = 기타 필드: 잘못된 JSON — {0}
tools/entity_editor/other_applied = 기타 필드를 적용했습니다
tools/entity_editor/opened = 모드 {1}의 항목 {0}개
tools/entity_editor/added = 항목 {0}을(를) {1}에 추가했습니다
tools/entity_editor/deleted = 항목 {0}을(를) 삭제했습니다(기록하려면 저장)
tools/entity_editor/saved = 저장함: 파일 {0}개, 텍스트 {1}개 (모드 {2}). 게임이 데이터를 다시 읽었습니다
tools/entity_editor/broken_files = JSON이 깨진 파일은 읽지 않았습니다: {0}
tools/entity_editor/problem_id = 파일 {0}에 id가 비어 있는 항목이 있습니다
tools/entity_editor/problem_duplicate = id {0}이(가) 두 번 이상 나옵니다
+36 -6
View File
@@ -1,5 +1,5 @@
mod_editor_tools_name = Редакторы
mod_editor_tools_desc = Инструменты разработчика: редактор сценариев (граф), редактор карты с зонами, конструктор сцен с инспектором, редакторы интерактива и света, экспорт каталога модов.
mod_editor_tools_desc = Авторские окна поверх запущенной игры: редакторы сцен, сценариев, карты, интерактива и света, а также конструктор сцен Godot. Всё, что они сохраняют, — обычные файлы модов, которые игра подхватывает сразу.
tools/common/save = Сохранить
tools/common/save_tip = Сохранить файл (Ctrl+S)
@@ -243,11 +243,6 @@ tools/catalog_export/exported = Экспортировано: {0}
tools/catalog_export/export_error = Ошибка экспорта
tools/catalog_export/url_required = Укажите URL скачивания — без него запись каталога бесполезна.
tools/hub/title = Редактор новелл
tools/hub/hint = Выберите редактор — окна открываются поверх этого экрана. Сценарий можно проиграть прямо из редактора (F5).
tools/hub/editors = Редакторы
tools/hub/empty = Не установлено ни одного редактора.
tools/hub/main_menu = Главное меню
tools/scene_editor/title = Редактор сцен
tools/scene_editor/hint = Сцена — это место: фоны, звук окружения и сценарии. Ctrl+S — сохранить · двойной клик по фону в списке — добавить в сцену
tools/scene_editor/save_tip = Сохранить файл сцены в выбранный мод (Ctrl+S); игра увидит изменения сразу
@@ -305,3 +300,38 @@ tools/scene_editor/pick_scene = Выберите сцену слева или с
tools/scene_editor/preview_hint = Выберите фон, чтобы посмотреть его.
tools/scene_editor/not_in_scene = (не в сцене)
tools/scene_editor/new_line_text = Новая сцена. Замените эту строку своим текстом.
tools/entity_editor/hint = Слева — список с поиском, справа — поля выбранной записи · Ctrl+N — новая, Ctrl+D — копия · Ctrl+Z/Y — отмена/повтор · Ctrl+S — сохранить, игра сразу видит изменения
tools/entity_editor/section_list = Записи мода
tools/entity_editor/search = Поиск по названию или id
tools/entity_editor/mod_tip = Мод, записи которого правятся; новые записи попадают в его файл
tools/entity_editor/new = Новый
tools/entity_editor/new_tip = Добавить запись в выбранный мод (Ctrl+N)
tools/entity_editor/duplicate = Копия
tools/entity_editor/duplicate_tip = Скопировать выбранную запись под новым id (Ctrl+D)
tools/entity_editor/delete = Удалить
tools/entity_editor/delete_tip = Удалить выбранную запись из файла мода
tools/entity_editor/delete_title = Удалить запись?
tools/entity_editor/delete_text = Запись исчезнет из файла мода после сохранения. Ссылки на неё в сценариях и картах останутся — проверьте их.
tools/entity_editor/undo = Отменить
tools/entity_editor/undo_tip = Отменить последнее изменение (Ctrl+Z)
tools/entity_editor/redo = Повторить
tools/entity_editor/redo_tip = Повторить отменённое (Ctrl+Y)
tools/entity_editor/no_entity = Выберите запись слева или создайте новую кнопкой «Новый».
tools/entity_editor/file = Мод {0} · файл {1}
tools/entity_editor/id = Id
tools/entity_editor/id_tip = Уникальный id: по нему запись находят сценарии, карты и другие моды. Мод, который зависит от владельца и даёт тот же id, заменяет запись
tools/entity_editor/locale_key_tip = Ключ локализации; тексты ниже пишутся в locale/*.properties мода
tools/entity_editor/text_in = текст ({0})
tools/entity_editor/picture_pick_tip = Выбрать картинку из папки мода
tools/entity_editor/picture_pick_title = Картинки группы «{0}»
tools/entity_editor/section_other = Прочие поля
tools/entity_editor/other_hint = Поля, которых нет в форме (данные других модов, лут…), как JSON. Правки применяются, как только JSON корректен.
tools/entity_editor/other_invalid = Прочие поля: неверный JSON — {0}
tools/entity_editor/other_applied = Прочие поля применены
tools/entity_editor/opened = {0} записей в моде {1}
tools/entity_editor/added = Добавлена запись {0} в {1}
tools/entity_editor/deleted = Запись {0} удалена (сохраните, чтобы записать)
tools/entity_editor/saved = Сохранено: файлов {0}, текстов {1} в моде {2}; игра перечитала данные
tools/entity_editor/broken_files = Не прочитаны файлы с ошибкой JSON: {0}
tools/entity_editor/problem_id = У записи в файле {0} пустой id
tools/entity_editor/problem_duplicate = Id {0} встречается несколько раз
+337
View File
@@ -0,0 +1,337 @@
mod_editor_tools_name = Công cụ chỉnh sửa
mod_editor_tools_desc = Các cửa sổ tác giả mở trên trò chơi đang chạy: trình sửa cảnh, kịch bản, bản đồ, tương tác và ánh sáng, cùng trình dựng cảnh Godot. Mọi thứ chúng lưu là tệp mod bình thường và game áp dụng ngay lập tức.
tools/common/save = Lưu
tools/common/save_tip = Lưu tệp (Ctrl+S)
tools/common/saved = Đã lưu: {0}
tools/common/loaded = Đã mở: {0}
tools/common/load_error = Lỗi tải
tools/common/readonly = Không thể lưu: mod chỉ đọc (zip) hoặc đường dẫn không hợp lệ.
tools/common/no_writable_mods = Không có mod nào ghi được (chỉ sửa được mod dạng thư mục).
tools/common/no_files = Mod "{0}" chưa có tệp loại này — hãy tạo bằng "+".
tools/common/no_file_to_save = Hãy chọn hoặc tạo một tệp trước ("+" cạnh danh sách tệp).
tools/common/new_file = Tệp mới
tools/common/new_file_hint = Tệp được tạo trong {0}/{1}/ với phần mở rộng {2} khi lưu lần đầu.
tools/common/new_file_status = Tệp mới {0} — được tạo khi lưu.
tools/common/mod_tip = Mod (mod đã có tệp loại này đứng trước; · — chưa có)
tools/common/file_tip = Tệp trong mod
tools/common/search = Tìm…
tools/common/json = JSON
tools/common/json_tip = Hiển thị JSON của tệp kết quả
tools/common/background_missing = Không mod nào cung cấp phông nền "{0}" của cảnh "{1}" — hãy chọn thủ công.
tools/map_editor/title = Trình sửa bản đồ
tools/map_editor/hint = Chuột trái — chọn / kéo · chuột phải — xóa đỉnh · nhấp đúp, Enter hoặc Esc — hoàn tất vùng · Del — xóa vùng · Ctrl+S — lưu
tools/map_editor/reload_map = Làm mới bản đồ trong game
tools/map_editor/reload_map_tip = Đọc lại các tệp bản đồ đã lưu trong game đang chạy
tools/map_editor/reloaded = Đã làm mới bản đồ trong game.
tools/map_editor/select_mode = Chọn
tools/map_editor/draw_region = Vẽ
tools/map_editor/draw_started = Nhấp vào bản đồ để đặt đỉnh. Nhấp đúp, Enter hoặc Esc để hoàn tất, chuột phải bỏ đỉnh cuối.
tools/map_editor/background = Phông nền xem trước
tools/map_editor/zones = Vùng
tools/map_editor/add_region = + Khu vực
tools/map_editor/add_region_tip = Khu vực mới: nhấp vào bản đồ để vẽ đa giác
tools/map_editor/add_marker = + Điểm đánh dấu
tools/map_editor/add_label = + Nhãn
tools/map_editor/delete = Xóa vùng
tools/map_editor/delete_tip = Xóa vùng đã chọn (Del)
tools/map_editor/delete_confirm = Xóa vùng đã chọn? Thay đổi sẽ vào tệp khi lưu.
tools/map_editor/deleted = Đã xóa vùng "{0}".
tools/map_editor/props = Thuộc tính vùng
tools/map_editor/zone_missing = Chọn một vùng trong danh sách hoặc trên bản đồ.
tools/map_editor/id = ID
tools/map_editor/id_empty = ID vùng không được để trống.
tools/map_editor/duplicate_id = ID vùng đã được dùng:
tools/map_editor/title_key = Tiêu đề
tools/map_editor/description = Mô tả
tools/map_editor/no_translation = không tìm thấy khóa trong bản địa hóa
tools/map_editor/scene = Cảnh
tools/map_editor/scene_none = (không có)
tools/map_editor/availability = Khả dụng
tools/map_editor/locked = Không cho di chuyển tới
tools/map_editor/hidden = Ẩn khỏi bản đồ
tools/map_editor/effective = Trong game:
tools/map_editor/effective_open = mở
tools/map_editor/effective_locked = bị khóa (cờ Locked)
tools/map_editor/effective_no_scene = bị khóa (không có cảnh)
tools/map_editor/effective_unknown_scene = bị khóa (không tìm thấy cảnh)
tools/map_editor/effective_hidden = ẩn khỏi bản đồ
tools/map_editor/marker = Điểm đánh dấu
tools/map_editor/image = Biểu tượng
tools/map_editor/image_placeholder = ID texture
tools/map_editor/icon_search = Tìm biểu tượng…
tools/map_editor/label_section = Nhãn
tools/map_editor/label = Nhãn chữ trên bản đồ
tools/map_editor/x = X (0–1)
tools/map_editor/y = Y (0–1)
tools/map_editor/vertices = Các đỉnh đa giác
tools/map_editor/vertices_hint = Kéo các đỉnh trên bản đồ; chuột phải vào một đỉnh để xóa.
tools/map_editor/add_vertex = + Đỉnh
tools/map_editor/delete_vertex = Xóa đỉnh
tools/map_editor/preview = ▶ Như trong game
tools/map_editor/preview_tip = Xem bản đồ như trong game: rê chuột, thẻ vùng, điểm đánh dấu (kể cả thay đổi chưa lưu)
tools/map_editor/preview_on = Xem trước: Esc hoặc ✕ để quay lại trình sửa.
tools/map_editor/preview_off = Chế độ chỉnh sửa.
tools/map_editor/preview_missing = Không tải được cảnh bản đồ (map_widget.tscn).
tools/scenario_editor/title = Trình sửa kịch bản
tools/scenario_editor/hint = Ctrl+S — lưu · F5 — chơi thử kịch bản · nhấp vào một cảnh trên đồ thị — các kịch bản của nó
tools/scenario_editor/play = ▶ Chơi
tools/scenario_editor/play_tooltip = Lưu và chơi thử kịch bản trong game (F5)
tools/scenario_editor/play_stopped = Đã dừng chơi thử.
tools/scenario_editor/stop = ■ Dừng
tools/scenario_editor/open_file = Mở bằng trình sửa ngoài
tools/scenario_editor/open_file_tip = Mở tệp bằng chương trình mặc định của hệ thống
tools/scenario_editor/open_file_error = Không mở được tệp
tools/scenario_editor/rescan = Quét lại
tools/scenario_editor/rescan_tip = Đọc lại danh sách kịch bản và đồ thị (sau khi thêm tệp)
tools/scenario_editor/rescanned = Đã làm mới danh sách kịch bản.
tools/scenario_editor/search_files = Tìm tệp…
tools/scenario_editor/tab_files = Tệp
tools/scenario_editor/tab_info = Tóm tắt
tools/scenario_editor/tab_graph = Đồ thị cảnh
tools/scenario_editor/graph_select_node = Chọn một cảnh trên đồ thị để xem các kịch bản và chuyển cảnh của nó.
tools/scenario_editor/graph_node = mod {0} · kịch bản {1}
tools/scenario_editor/graph_node_bg = phông nền: {0}
tools/scenario_editor/graph_detail_mod = mod: {0}
tools/scenario_editor/graph_detail_scenarios = Kịch bản ({0}), theo ưu tiên:
tools/scenario_editor/info_scene = Cảnh
tools/scenario_editor/info_priority = Ưu tiên
tools/scenario_editor/info_condition = Điều kiện
tools/scenario_editor/info_switch_targets = Chuyển cảnh
tools/scenario_editor/info_locale_keys = Khóa bản địa hóa
tools/scenario_editor/info_quest_refs = Nhiệm vụ
tools/scenario_editor/bg_previews = Phông nền của kịch bản
tools/scenario_editor/bg_missing = Không tìm thấy phông nền: {0}
tools/scenario_editor/no_backgrounds = Kịch bản không đổi phông nền
tools/scenario_editor/save_error = Lỗi lưu: {0}
tools/scenario_editor/readonly_zip = Tệp của mod zip là chỉ đọc
tools/scenario_editor/no_scene_error = Phần đầu kịch bản không có cảnh (scene:) — không có gì để chơi.
tools/scenario_editor/unsaved_title = Thay đổi chưa lưu
tools/scenario_editor/unsaved_switch_text = Thẻ này có thay đổi chưa lưu. Đóng mà không lưu?
tools/scene_builder/title = Trình dựng cảnh
tools/scene_builder/hint = Kéo nút bằng thân hoặc tay cầm · nhấp — chọn nút dưới con trỏ · Del — xóa nút · Ctrl+S — lưu và dựng lại
tools/scene_builder/base_scene = Cảnh nền mà cảnh của mod hiển thị lên trên
tools/scene_builder/mod = Mod
tools/scene_builder/scene = Cảnh của mod (.tscn)
tools/scene_builder/empty_scene = (cảnh nền trống)
tools/scene_builder/select_mod = — chọn mod —
tools/scene_builder/select_scene = — chọn cảnh —
tools/scene_builder/save_tip = Lưu .tscn và dựng lại bản xem trước (Ctrl+S)
tools/scene_builder/reload = Tải lại từ đĩa
tools/scene_builder/reload_tip = Bỏ các chỉnh sửa chưa lưu và đọc lại tệp cảnh
tools/scene_builder/reloaded = Đã tải lại cảnh từ đĩa.
tools/scene_builder/saved_ok = Đã lưu.
tools/scene_builder/saved_rebuilt = Đã lưu, bản xem trước đã được dựng lại.
tools/scene_builder/autosaved = Đã tự động lưu.
tools/scene_builder/error_save = Lỗi lưu: mod chỉ đọc.
tools/scene_builder/error_no_scene = Hãy chọn một cảnh của mod trước.
tools/scene_builder/hierarchy = Phân cấp
tools/scene_builder/hierarchy_hint = Vàng — gốc cảnh của mod, sáng — nút sửa được, mờ — cảnh nền (chỉ đọc).
tools/scene_builder/node_type_tip = Loại của nút mới
tools/scene_builder/add_node_tip = Thêm một nút thuộc loại đã chọn vào nút đang chọn (hoặc gốc cảnh)
tools/scene_builder/delete_node_tip = Xóa nút đang chọn của cảnh mod
tools/scene_builder/delete_node_title = Xóa nút
tools/scene_builder/delete_node_confirm = Xóa nút đang chọn cùng các nút con? Tệp được lưu tự động.
tools/scene_builder/refresh_tip = Làm mới cây nút
tools/scene_builder/tab_inspector = Thuộc tính
tools/scene_builder/tab_tscn = Mã nguồn .tscn
tools/scene_builder/tscn_hint = Chỉnh sửa ở thanh thuộc tính và tay cầm tự lưu; sửa tay văn bản thì lưu bằng Ctrl+S, đồng thời dựng lại bản xem trước.
tools/scene_builder/inspector_cat_container = Trong vùng chứa
tools/scene_builder/inspector_select = Chọn một nút của cảnh mod để sửa thuộc tính
tools/scene_builder/inspector_readonly = Chỉ đọc: nút thuộc cảnh nền
tools/scene_builder/inspector_missing_section = Không tìm thấy phần của nút trong tscn (nút của cảnh con được thể hiện chưa sửa được)
tools/scene_builder/inspector_preset = Mẫu neo
tools/scene_builder/inspector_cat_layout = Bố cục
tools/scene_builder/inspector_cat_rect = Hình chữ nhật
tools/scene_builder/inspector_cat_visibility = Hiển thị
tools/scene_builder/inspector_cat_misc = Khác
tools/scene_builder/inspector_bad_value = Không thể ghi giá trị vào tscn.
tools/scene_builder/inspector_in_file_tooltip = Được lưu trong tệp tscn
tools/scene_builder/inspector_container_banner = Nút này do vùng chứa quản lý: neo và độ lệch được nút cha tính lại nên chỉnh ở đây sẽ không có tác dụng.
tools/scene_builder/err_add_failed = Lỗi: không tạo được nút loại này.
tools/scene_builder/err_add_parent_missing = Lỗi: không tìm thấy phần của nút cha trong tscn (không hỗ trợ nút cha của cảnh con được thể hiện).
tools/scene_builder/err_del_root = Lỗi: không thể xóa gốc cảnh.
tools/scene_builder/err_del_none = Lỗi: hãy chọn một nút của mod để xóa.
tools/interactive_editor/title = Trình sửa tương tác
tools/interactive_editor/hint = Nhấp vào vùng va chạm — chọn · kéo tay cầm · ở chế độ đa giác, nhấp vào nền để thêm đỉnh, chuột phải vào đỉnh để xóa · Del — xóa đối tượng · Ctrl+S — lưu
tools/interactive_editor/items = Đối tượng trong tệp
tools/interactive_editor/add = + Mới
tools/interactive_editor/duplicate = Nhân bản
tools/interactive_editor/delete = Xóa đối tượng
tools/interactive_editor/delete_confirm = Gỡ đối tượng tương tác đã chọn khỏi tệp? Thay đổi sẽ vào tệp khi lưu.
tools/interactive_editor/deleted = Đã gỡ đối tượng "{0}".
tools/interactive_editor/background = Phông nền xem trước
tools/interactive_editor/props = Thuộc tính
tools/interactive_editor/none_selected = Chọn một đối tượng trong danh sách hoặc trên bản xem trước.
tools/interactive_editor/label_id = ID
tools/interactive_editor/label_texture_key = Texture
tools/interactive_editor/texture_placeholder = không có sprite
tools/interactive_editor/texture_tip = ID texture của sprite; để trống — vùng nhấp vô hình
tools/interactive_editor/label_layer = Lớp
tools/interactive_editor/label_z = Thứ tự Z
tools/interactive_editor/label_cursor = Con trỏ
tools/interactive_editor/cursor_none = (mặc định)
tools/interactive_editor/enabled = Bật
tools/interactive_editor/visible = Hiển thị
tools/interactive_editor/collider = Vùng va chạm
tools/interactive_editor/label_collider_type = Hình dạng
tools/interactive_editor/hit_rect = hình chữ nhật
tools/interactive_editor/hit_circle = hình tròn
tools/interactive_editor/hit_polygon = đa giác
tools/interactive_editor/label_x = X
tools/interactive_editor/label_y = Y
tools/interactive_editor/label_w = Chiều rộng
tools/interactive_editor/label_h = Chiều cao
tools/interactive_editor/label_cx = Tâm X
tools/interactive_editor/label_cy = Tâm Y
tools/interactive_editor/label_r = Bán kính
tools/interactive_editor/polygon_hint = Nhấp vào nền để thêm đỉnh, chuột phải vào đỉnh để xóa. Ít nhất 3 đỉnh.
tools/interactive_editor/polygon_too_small = Đa giác "{0}" có ít hơn 3 đỉnh.
tools/interactive_editor/highlight = Làm nổi bật
tools/interactive_editor/label_highlight_color = Màu
tools/interactive_editor/label_highlight_scale = Tỉ lệ
tools/interactive_editor/actions = Hành động
tools/interactive_editor/label_onclick_scenario = Kịch bản
tools/interactive_editor/label_onclick_commands = Lệnh khi nhấp (mỗi dòng một lệnh)
tools/interactive_editor/label_hover_enter = Khi rê chuột vào
tools/interactive_editor/label_hover_leave = Khi rê chuột ra
tools/interactive_editor/id_empty = Có đối tượng để trống ID.
tools/interactive_editor/duplicate_id = Trùng ID đối tượng:
tools/light_editor/title = Trình sửa ánh sáng
tools/light_editor/hint = Nhấp đúp vào nền — đèn mới · kéo các đèn · Del — xóa · Ctrl+S — ghi light_set vào kịch bản
tools/light_editor/scenario_tip = Kịch bản có dòng light_set được sửa (● — đã đặt đèn)
tools/light_editor/pick_scenario = — chọn kịch bản —
tools/light_editor/save_tip = Ghi dòng light_set vào kịch bản (Ctrl+S)
tools/light_editor/copy_line = Sao chép dòng
tools/light_editor/copy_line_tip = Sao chép lệnh light_set vào bộ nhớ tạm
tools/light_editor/copied = Đã sao chép dòng light_set.
tools/light_editor/show_line = light_set
tools/light_editor/loaded = Đã tải đèn của {0}.
tools/light_editor/no_lights_yet = {0} chưa có light_set — hãy thêm đèn, dòng sẽ được chèn sau phần đầu.
tools/light_editor/static_lights = Đèn
tools/light_editor/add_light = + Đèn
tools/light_editor/delete = Xóa
tools/light_editor/limit = Tối đa {0} đèn tĩnh.
tools/light_editor/selected_props = Đèn đã chọn
tools/light_editor/none_selected = Chọn một đèn trong danh sách hoặc trên bản xem trước.
tools/light_editor/x = X (0–1)
tools/light_editor/y = Y (0–1)
tools/light_editor/radius = Bán kính
tools/light_editor/radius_tip = Bán kính tính bằng điểm ảnh của màn hình game rộng 1920 px
tools/light_editor/influence = Cường độ
tools/light_editor/influence_tip = Mức ảnh hưởng của đèn (0–1)
tools/light_editor/color = Màu
tools/light_editor/background = Phông nền xem trước
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.
tools/scene_editor/title = Trình sửa cảnh
tools/scene_editor/hint = Cảnh là một địa điểm: phông nền, âm thanh nền và kịch bản. Ctrl+S để lưu · nhấp đúp vào một phông nền trong danh sách để thêm
tools/scene_editor/save_tip = Lưu tệp cảnh vào mod đã chọn (Ctrl+S); game thấy thay đổi ngay
tools/scene_editor/new_scene = + Cảnh mới
tools/scene_editor/new_scene_tip = Tạo một địa điểm mới: tên, mod, phông nền. Có thể thêm kịch bản sau; cảnh cũng có thể không có kịch bản nào
tools/scene_editor/delete = Xóa
tools/scene_editor/delete_tip = Xóa tệp cảnh của mod đã chọn
tools/scene_editor/delete_confirm = Xóa tệp của cảnh này khỏi mod đã chọn? Các kịch bản của cảnh vẫn được giữ.
tools/scene_editor/go = ▶ Đến cảnh
tools/scene_editor/go_tip = Chuyển game sang cảnh này
tools/scene_editor/rescan_tip = Đọc lại cảnh, phông nền và âm thanh (sau khi thêm tệp vào mod)
tools/scene_editor/scenes = Cảnh
tools/scene_editor/backgrounds = Phông nền của cảnh (mỗi lần vào chọn ngẫu nhiên một cái)
tools/scene_editor/all_backgrounds = Tất cả phông nền
tools/scene_editor/add_background = + Thêm phông nền vào cảnh
tools/scene_editor/add_background_tip = Thêm phông nền đang hiển thị vào cảnh (hoặc nhấp đúp vào nó trong danh sách)
tools/scene_editor/remove_background = Bỏ phông nền khỏi cảnh
tools/scene_editor/properties = Thuộc tính
tools/scene_editor/name = Tên
tools/scene_editor/name_tip = Id của cảnh: chữ Latin, chữ số và _; switch_scene và phần đầu kịch bản tham chiếu tới nó. Chỉ đặt khi tạo
tools/scene_editor/mod = Lưu vào
tools/scene_editor/mod_tip = Mod mà tệp cảnh được ghi vào (chỉ mod dạng thư mục)
tools/scene_editor/ambient = Âm thanh
tools/scene_editor/ambient_tip = Âm thanh nền phát trong cảnh
tools/scene_editor/listen = ▶
tools/scene_editor/listen_tip = Nghe âm thanh nền
tools/scene_editor/stop = ■
tools/scene_editor/default_scenario = Kịch bản mặc định
tools/scene_editor/default_scenario_tip = Phát khi cảnh không còn kịch bản nào khác
tools/scene_editor/scenarios = Kịch bản của cảnh
tools/scene_editor/open_scenario = Mở
tools/scene_editor/open_scenario_tip = Mở kịch bản đã chọn trong trình sửa kịch bản (hoặc nhấp đúp)
tools/scene_editor/new_scenario = + Kịch bản mới
tools/scene_editor/new_scenario_tip = Tạo một kịch bản của cảnh này trong mod đã chọn và mở nó
tools/scene_editor/none = — không có —
tools/scene_editor/effective = Trong game: {0} (từ mod {1})
tools/scene_editor/adds_to = Cảnh được định nghĩa trong: {0}. Khi lưu sẽ tạo trong mod đã chọn một tệp bổ sung cho nó.
tools/scene_editor/also_in = Cũng được bổ sung bởi: {0}
tools/scene_editor/no_writable_mod = Không có mod dạng thư mục nào để lưu cảnh.
tools/scene_editor/bad_name = Hãy đặt tên cảnh: chữ Latin, chữ số và _ (bắt đầu bằng chữ cái).
tools/scene_editor/name_taken = Cảnh "{0}" đã tồn tại; hãy chọn nó trong danh sách.
tools/scene_editor/need_background = Hãy thêm ít nhất một phông nền cho cảnh.
tools/scene_editor/created = Đã tạo cảnh "{0}".
tools/scene_editor/deleted = Đã xóa: {0}
tools/scene_editor/opened = Cảnh: {0}
tools/scene_editor/new_started = Cảnh mới: đặt tên và mod, thêm phông nền.
tools/scene_editor/already_there = Phông nền {0} đã có trong cảnh.
tools/scene_editor/go_failed = Game không biết cảnh "{0}"; hãy lưu nó trước.
tools/scene_editor/went = Đã chuyển sang cảnh {0}.
tools/scene_editor/rescanned = Đã đọc lại cảnh, phông nền và âm thanh.
tools/scene_editor/scenario_created = Đã tạo kịch bản {0}.
tools/scene_editor/no_scenarios = Cảnh chưa có kịch bản nào
tools/scene_editor/no_backgrounds = Cảnh chưa có phông nền: hãy chọn một cái trong danh sách bên dưới và thêm vào.
tools/scene_editor/pick_scene = Chọn một cảnh bên trái hoặc tạo cảnh mới.
tools/scene_editor/preview_hint = Chọn một phông nền để xem.
tools/scene_editor/not_in_scene = (không có trong cảnh)
tools/scene_editor/new_line_text = Một cảnh mới. Hãy thay dòng này bằng văn bản của bạn.
tools/entity_editor/hint = Bên trái là danh sách có tìm kiếm, bên phải là các trường của mục đã chọn · Ctrl+N mới, Ctrl+D sao chép · Ctrl+Z/Y hoàn tác/làm lại · Ctrl+S lưu, game thấy thay đổi ngay
tools/entity_editor/section_list = Các mục của mod
tools/entity_editor/search = Tìm theo tên hoặc id
tools/entity_editor/mod_tip = Mod có các mục đang được sửa; mục mới được ghi vào tệp của nó
tools/entity_editor/new = Mới
tools/entity_editor/new_tip = Thêm một mục vào mod đã chọn (Ctrl+N)
tools/entity_editor/duplicate = Sao chép
tools/entity_editor/duplicate_tip = Sao chép mục đã chọn với id mới (Ctrl+D)
tools/entity_editor/delete = Xóa
tools/entity_editor/delete_tip = Gỡ mục đã chọn khỏi tệp của mod
tools/entity_editor/delete_title = Xóa mục này?
tools/entity_editor/delete_text = Mục sẽ rời khỏi tệp của mod khi lưu. Các tham chiếu tới nó trong kịch bản và bản đồ vẫn còn — hãy kiểm tra chúng.
tools/entity_editor/undo = Hoàn tác
tools/entity_editor/undo_tip = Hoàn tác thay đổi cuối (Ctrl+Z)
tools/entity_editor/redo = Làm lại
tools/entity_editor/redo_tip = Làm lại thao tác đã hoàn tác (Ctrl+Y)
tools/entity_editor/no_entity = Chọn một mục bên trái hoặc tạo mới bằng "Mới".
tools/entity_editor/file = Mod {0} · tệp {1}
tools/entity_editor/id = Id
tools/entity_editor/id_tip = Id duy nhất: kịch bản, bản đồ và các mod khác tìm mục qua nó. Một mod phụ thuộc vào chủ sở hữu và có cùng id sẽ thay thế mục đó
tools/entity_editor/locale_key_tip = Khóa bản địa hóa; văn bản bên dưới được ghi vào locale/*.properties của mod
tools/entity_editor/text_in = văn bản ({0})
tools/entity_editor/picture_pick_tip = Chọn một hình từ các thư mục của mod
tools/entity_editor/picture_pick_title = Hình của nhóm "{0}"
tools/entity_editor/section_other = Các trường khác
tools/entity_editor/other_hint = Các trường mà biểu mẫu không hiển thị (dữ liệu của mod khác, chiến lợi phẩm…) dưới dạng JSON. Chỉnh sửa có hiệu lực ngay khi JSON hợp lệ.
tools/entity_editor/other_invalid = Các trường khác: JSON không hợp lệ — {0}
tools/entity_editor/other_applied = Đã áp dụng các trường khác
tools/entity_editor/opened = {0} mục trong mod {1}
tools/entity_editor/added = Đã thêm mục {0} vào {1}
tools/entity_editor/deleted = Đã xóa mục {0} (lưu để ghi lại)
tools/entity_editor/saved = Đã lưu: {0} tệp, {1} văn bản trong mod {2}; game đã đọc lại dữ liệu
tools/entity_editor/broken_files = Không đọc các tệp có JSON hỏng: {0}
tools/entity_editor/problem_id = Một mục trong tệp {0} để trống id
tools/entity_editor/problem_duplicate = Id {0} xuất hiện nhiều lần
+337
View File
@@ -0,0 +1,337 @@
mod_editor_tools_name = 编辑工具
mod_editor_tools_desc = 运行中的游戏之上的创作窗口:场景、剧本、地图、交互物和光照编辑器,以及 Godot 场景构建器。保存的都是普通模组文件,游戏会立即生效。
tools/common/save = 保存
tools/common/save_tip = 保存文件(Ctrl+S)
tools/common/saved = 已保存:{0}
tools/common/loaded = 已打开:{0}
tools/common/load_error = 加载错误
tools/common/readonly = 无法保存:模组是只读的(zip)或路径无效。
tools/common/no_writable_mods = 没有可写的模组(只能编辑文件夹形式的模组)。
tools/common/no_files = 模组"{0}"还没有这类文件——用"+"创建一个。
tools/common/no_file_to_save = 请先选择或创建一个文件(文件列表旁的"+")。
tools/common/new_file = 新文件
tools/common/new_file_hint = 首次保存时会在 {0}/{1}/ 中创建扩展名为 {2} 的文件。
tools/common/new_file_status = 新文件 {0}——保存时创建。
tools/common/mod_tip = 模组(已有此类文件的模组排在前面;· —— 还没有)
tools/common/file_tip = 模组内的文件
tools/common/search = 搜索…
tools/common/json = JSON
tools/common/json_tip = 显示生成文件的 JSON
tools/common/background_missing = 场景"{1}"的背景"{0}"没有任何模组提供——请手动选择一个。
tools/map_editor/title = 地图编辑器
tools/map_editor/hint = 左键——选择 / 拖动 · 右键——删除顶点 · 双击、Enter 或 Esc——完成区域 · Del——删除区域 · Ctrl+S——保存
tools/map_editor/reload_map = 刷新游戏内地图
tools/map_editor/reload_map_tip = 在运行中的游戏里重新读取已保存的地图文件
tools/map_editor/reloaded = 游戏内地图已刷新。
tools/map_editor/select_mode = 选择
tools/map_editor/draw_region = 绘制
tools/map_editor/draw_started = 点击地图放置顶点。双击、Enter 或 Esc 完成,右键删除最后一个。
tools/map_editor/background = 预览背景
tools/map_editor/zones = 区域
tools/map_editor/add_region = + 区域
tools/map_editor/add_region_tip = 新区域:点击地图绘制多边形
tools/map_editor/add_marker = + 标记
tools/map_editor/add_label = + 文字标签
tools/map_editor/delete = 删除区域
tools/map_editor/delete_tip = 删除选中的区域(Del)
tools/map_editor/delete_confirm = 删除选中的区域?保存时更改会写入文件。
tools/map_editor/deleted = 区域"{0}"已删除。
tools/map_editor/props = 区域属性
tools/map_editor/zone_missing = 在列表或地图上选择一个区域。
tools/map_editor/id = ID
tools/map_editor/id_empty = 区域 ID 不能为空。
tools/map_editor/duplicate_id = 区域 ID 已被使用:
tools/map_editor/title_key = 标题
tools/map_editor/description = 描述
tools/map_editor/no_translation = 在本地化中找不到该键
tools/map_editor/scene = 场景
tools/map_editor/scene_none = (无)
tools/map_editor/availability = 可用性
tools/map_editor/locked = 禁止前往
tools/map_editor/hidden = 在地图上隐藏
tools/map_editor/effective = 游戏中:
tools/map_editor/effective_open = 开放
tools/map_editor/effective_locked = 锁定(Locked 标志)
tools/map_editor/effective_no_scene = 锁定(没有场景)
tools/map_editor/effective_unknown_scene = 锁定(找不到场景)
tools/map_editor/effective_hidden = 在地图上隐藏
tools/map_editor/marker = 标记
tools/map_editor/image = 图标
tools/map_editor/image_placeholder = 纹理 ID
tools/map_editor/icon_search = 查找图标…
tools/map_editor/label_section = 文字标签
tools/map_editor/label = 地图上的文字标签
tools/map_editor/x = X(0–1)
tools/map_editor/y = Y(0–1)
tools/map_editor/vertices = 多边形顶点
tools/map_editor/vertices_hint = 在地图上拖动顶点;右键点击顶点将其删除。
tools/map_editor/add_vertex = + 顶点
tools/map_editor/delete_vertex = 删除顶点
tools/map_editor/preview = ▶ 游戏中效果
tools/map_editor/preview_tip = 以游戏中的样子查看地图:悬停、区域卡片、标记(包括未保存的修改)
tools/map_editor/preview_on = 预览:Esc 或 ✕ 返回编辑器。
tools/map_editor/preview_off = 编辑模式。
tools/map_editor/preview_missing = 无法加载地图场景(map_widget.tscn)。
tools/scenario_editor/title = 剧本编辑器
tools/scenario_editor/hint = Ctrl+S——保存 · F5——试玩剧本 · 点击图中的场景——查看其剧本
tools/scenario_editor/play = ▶ 试玩
tools/scenario_editor/play_tooltip = 保存并在游戏中试玩剧本(F5)
tools/scenario_editor/play_stopped = 试玩已停止。
tools/scenario_editor/stop = ■ 停止
tools/scenario_editor/open_file = 在外部编辑器中打开
tools/scenario_editor/open_file_tip = 用系统默认程序打开文件
tools/scenario_editor/open_file_error = 无法打开文件
tools/scenario_editor/rescan = 重新扫描
tools/scenario_editor/rescan_tip = 重新读取剧本列表和图(添加文件后)
tools/scenario_editor/rescanned = 剧本列表已刷新。
tools/scenario_editor/search_files = 查找文件…
tools/scenario_editor/tab_files = 文件
tools/scenario_editor/tab_info = 概要
tools/scenario_editor/tab_graph = 场景图
tools/scenario_editor/graph_select_node = 在图中选择一个场景,查看它的剧本和转场。
tools/scenario_editor/graph_node = 模组 {0} · 剧本 {1}
tools/scenario_editor/graph_node_bg = 背景:{0}
tools/scenario_editor/graph_detail_mod = 模组:{0}
tools/scenario_editor/graph_detail_scenarios = 剧本({0}),按优先级:
tools/scenario_editor/info_scene = 场景
tools/scenario_editor/info_priority = 优先级
tools/scenario_editor/info_condition = 条件
tools/scenario_editor/info_switch_targets = 场景切换
tools/scenario_editor/info_locale_keys = 本地化键
tools/scenario_editor/info_quest_refs = 任务
tools/scenario_editor/bg_previews = 剧本的背景
tools/scenario_editor/bg_missing = 找不到背景:{0}
tools/scenario_editor/no_backgrounds = 这个剧本不更换背景
tools/scenario_editor/save_error = 保存错误:{0}
tools/scenario_editor/readonly_zip = zip 模组中的文件是只读的
tools/scenario_editor/no_scene_error = 剧本头部没有场景(scene:)——没有可试玩的内容。
tools/scenario_editor/unsaved_title = 未保存的更改
tools/scenario_editor/unsaved_switch_text = 这个标签页有未保存的更改。不保存就关闭吗?
tools/scene_builder/title = 场景构建器
tools/scene_builder/hint = 拖动节点本体或手柄 · 点击——选择光标下的节点 · Del——删除节点 · Ctrl+S——保存并重建
tools/scene_builder/base_scene = 显示模组场景的基础场景
tools/scene_builder/mod = 模组
tools/scene_builder/scene = 模组场景(.tscn)
tools/scene_builder/empty_scene = (空的基础场景)
tools/scene_builder/select_mod = — 选择模组 —
tools/scene_builder/select_scene = — 选择场景 —
tools/scene_builder/save_tip = 保存 .tscn 并重建预览(Ctrl+S)
tools/scene_builder/reload = 从磁盘重新加载
tools/scene_builder/reload_tip = 放弃未保存的修改并重新读取场景文件
tools/scene_builder/reloaded = 已从磁盘重新加载场景。
tools/scene_builder/saved_ok = 已保存。
tools/scene_builder/saved_rebuilt = 已保存,预览已重建。
tools/scene_builder/autosaved = 已自动保存。
tools/scene_builder/error_save = 保存错误:模组是只读的。
tools/scene_builder/error_no_scene = 请先选择一个模组场景。
tools/scene_builder/hierarchy = 层级
tools/scene_builder/hierarchy_hint = 金色——模组场景根节点,亮色——可编辑的节点,暗色——基础场景(只读)。
tools/scene_builder/node_type_tip = 新节点的类型
tools/scene_builder/add_node_tip = 在选中的节点(或场景根节点)中添加所选类型的节点
tools/scene_builder/delete_node_tip = 删除选中的模组场景节点
tools/scene_builder/delete_node_title = 删除节点
tools/scene_builder/delete_node_confirm = 删除选中的节点及其子节点?文件会自动保存。
tools/scene_builder/refresh_tip = 刷新节点树
tools/scene_builder/tab_inspector = 检查器
tools/scene_builder/tab_tscn = .tscn 源码
tools/scene_builder/tscn_hint = 检查器和手柄的修改会自动保存;手动修改文本需用 Ctrl+S 保存,同时会重建预览。
tools/scene_builder/inspector_cat_container = 在容器中
tools/scene_builder/inspector_select = 选择模组场景中的一个节点来编辑其属性
tools/scene_builder/inspector_readonly = 只读:该节点属于基础场景
tools/scene_builder/inspector_missing_section = 在 tscn 中找不到节点段(实例化子场景中的节点暂不能编辑)
tools/scene_builder/inspector_preset = 锚点预设
tools/scene_builder/inspector_cat_layout = 布局
tools/scene_builder/inspector_cat_rect = 矩形
tools/scene_builder/inspector_cat_visibility = 可见性
tools/scene_builder/inspector_cat_misc = 其他
tools/scene_builder/inspector_bad_value = 该值无法写入 tscn。
tools/scene_builder/inspector_in_file_tooltip = 存储在 tscn 文件中
tools/scene_builder/inspector_container_banner = 这个节点由容器管理:锚点和偏移由父节点重新计算,在这里修改不会生效。
tools/scene_builder/err_add_failed = 错误:无法创建这种类型的节点。
tools/scene_builder/err_add_parent_missing = 错误:在 tscn 中找不到父节点段(不支持实例化子场景的父节点)。
tools/scene_builder/err_del_root = 错误:不能删除场景根节点。
tools/scene_builder/err_del_none = 错误:请选择一个要删除的模组节点。
tools/interactive_editor/title = 交互编辑器
tools/interactive_editor/hint = 点击碰撞体——选择 · 拖动手柄 · 多边形模式下点击背景添加顶点,右键点击顶点将其删除 · Del——删除对象 · Ctrl+S——保存
tools/interactive_editor/items = 文件中的对象
tools/interactive_editor/add = + 新建
tools/interactive_editor/duplicate = 复制
tools/interactive_editor/delete = 删除对象
tools/interactive_editor/delete_confirm = 从文件中移除选中的交互物?保存时更改会写入文件。
tools/interactive_editor/deleted = 已移除对象"{0}"。
tools/interactive_editor/background = 预览背景
tools/interactive_editor/props = 属性
tools/interactive_editor/none_selected = 在列表或预览中选择一个对象。
tools/interactive_editor/label_id = ID
tools/interactive_editor/label_texture_key = 纹理
tools/interactive_editor/texture_placeholder = 无精灵图
tools/interactive_editor/texture_tip = 精灵图的纹理 ID;为空——不可见的可点击区域
tools/interactive_editor/label_layer = 图层
tools/interactive_editor/label_z = Z 顺序
tools/interactive_editor/label_cursor = 光标
tools/interactive_editor/cursor_none = (默认)
tools/interactive_editor/enabled = 启用
tools/interactive_editor/visible = 可见
tools/interactive_editor/collider = 碰撞体
tools/interactive_editor/label_collider_type = 形状
tools/interactive_editor/hit_rect = 矩形
tools/interactive_editor/hit_circle = 圆形
tools/interactive_editor/hit_polygon = 多边形
tools/interactive_editor/label_x = X
tools/interactive_editor/label_y = Y
tools/interactive_editor/label_w = 宽度
tools/interactive_editor/label_h = 高度
tools/interactive_editor/label_cx = 中心 X
tools/interactive_editor/label_cy = 中心 Y
tools/interactive_editor/label_r = 半径
tools/interactive_editor/polygon_hint = 点击背景添加顶点,右键点击顶点将其删除。至少 3 个顶点。
tools/interactive_editor/polygon_too_small = 多边形"{0}"的顶点少于 3 个。
tools/interactive_editor/highlight = 高亮
tools/interactive_editor/label_highlight_color = 颜色
tools/interactive_editor/label_highlight_scale = 缩放
tools/interactive_editor/actions = 动作
tools/interactive_editor/label_onclick_scenario = 剧本
tools/interactive_editor/label_onclick_commands = 点击时的命令(每行一条)
tools/interactive_editor/label_hover_enter = 鼠标移入
tools/interactive_editor/label_hover_leave = 鼠标移出
tools/interactive_editor/id_empty = 有对象的 ID 为空。
tools/interactive_editor/duplicate_id = 对象 ID 重复:
tools/light_editor/title = 光照编辑器
tools/light_editor/hint = 双击背景——新建灯光 · 拖动灯光 · Del——删除 · Ctrl+S——把 light_set 写入剧本
tools/light_editor/scenario_tip = 要编辑其 light_set 行的剧本(●——已设置灯光)
tools/light_editor/pick_scenario = — 选择剧本 —
tools/light_editor/save_tip = 把 light_set 行写入剧本(Ctrl+S)
tools/light_editor/copy_line = 复制行
tools/light_editor/copy_line_tip = 把 light_set 命令复制到剪贴板
tools/light_editor/copied = 已复制 light_set 行。
tools/light_editor/show_line = light_set
tools/light_editor/loaded = 已加载 {0} 的灯光。
tools/light_editor/no_lights_yet = {0} 还没有 light_set——添加灯光后,这一行会插在头部之后。
tools/light_editor/static_lights = 灯光
tools/light_editor/add_light = + 灯光
tools/light_editor/delete = 删除
tools/light_editor/limit = 最多 {0} 个静态灯光。
tools/light_editor/selected_props = 选中的灯光
tools/light_editor/none_selected = 在列表或预览中选择一个灯光。
tools/light_editor/x = X(0–1)
tools/light_editor/y = Y(0–1)
tools/light_editor/radius = 半径
tools/light_editor/radius_tip = 以 1920 像素宽的游戏画面计的半径(像素)
tools/light_editor/influence = 强度
tools/light_editor/influence_tip = 灯光影响(0–1)
tools/light_editor/color = 颜色
tools/light_editor/background = 预览背景
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——没有它的目录条目毫无用处。
tools/scene_editor/title = 场景编辑器
tools/scene_editor/hint = 场景就是一个地点:背景、环境音和剧本。Ctrl+S 保存 · 双击列表中的背景将其添加
tools/scene_editor/save_tip = 把场景文件保存到所选模组(Ctrl+S);游戏会立即看到更改
tools/scene_editor/new_scene = + 新场景
tools/scene_editor/new_scene_tip = 创建一个新地点:名称、模组、背景。剧本可以以后再加;场景也可以没有剧本
tools/scene_editor/delete = 删除
tools/scene_editor/delete_tip = 删除所选模组中的场景文件
tools/scene_editor/delete_confirm = 从所选模组中删除这个场景的文件?场景的剧本会保留。
tools/scene_editor/go = ▶ 前往场景
tools/scene_editor/go_tip = 把游戏切换到这个场景
tools/scene_editor/rescan_tip = 重新读取场景、背景和声音(向模组添加文件后)
tools/scene_editor/scenes = 场景
tools/scene_editor/backgrounds = 场景背景(进入时随机选一个)
tools/scene_editor/all_backgrounds = 所有背景
tools/scene_editor/add_background = + 把背景加入场景
tools/scene_editor/add_background_tip = 把显示的背景加入场景(或在列表中双击它)
tools/scene_editor/remove_background = 从场景中移除背景
tools/scene_editor/properties = 属性
tools/scene_editor/name = 名称
tools/scene_editor/name_tip = 场景 id:拉丁字母、数字和 _;switch_scene 和剧本头部会引用它。只能在创建时设置
tools/scene_editor/mod = 保存到
tools/scene_editor/mod_tip = 写入场景文件的模组(仅限文件夹模组)
tools/scene_editor/ambient = 声音
tools/scene_editor/ambient_tip = 场景中播放的环境音
tools/scene_editor/listen = ▶
tools/scene_editor/listen_tip = 试听环境音
tools/scene_editor/stop = ■
tools/scene_editor/default_scenario = 默认剧本
tools/scene_editor/default_scenario_tip = 当场景没有其他剧本剩下时播放
tools/scene_editor/scenarios = 场景的剧本
tools/scene_editor/open_scenario = 打开
tools/scene_editor/open_scenario_tip = 在剧本编辑器中打开选中的剧本(或双击)
tools/scene_editor/new_scenario = + 新剧本
tools/scene_editor/new_scenario_tip = 在所选模组中创建本场景的剧本并打开
tools/scene_editor/none = — 无 —
tools/scene_editor/effective = 游戏中:{0}(来自模组 {1})
tools/scene_editor/adds_to = 场景定义在:{0}。保存时会在所选模组中创建一个补充它的文件。
tools/scene_editor/also_in = 也补充它的有:{0}
tools/scene_editor/no_writable_mod = 没有可以保存场景的文件夹模组。
tools/scene_editor/bad_name = 请设置场景名称:拉丁字母、数字和 _(以字母开头)。
tools/scene_editor/name_taken = 场景"{0}"已存在;请在列表中选择它。
tools/scene_editor/need_background = 请至少给场景添加一个背景。
tools/scene_editor/created = 已创建场景"{0}"。
tools/scene_editor/deleted = 已删除:{0}
tools/scene_editor/opened = 场景:{0}
tools/scene_editor/new_started = 新场景:设置名称和模组,添加背景。
tools/scene_editor/already_there = 背景 {0} 已在场景中。
tools/scene_editor/go_failed = 游戏不认识场景"{0}";请先保存它。
tools/scene_editor/went = 已切换到场景 {0}。
tools/scene_editor/rescanned = 已重新读取场景、背景和声音。
tools/scene_editor/scenario_created = 已创建剧本 {0}。
tools/scene_editor/no_scenarios = 这个场景还没有剧本
tools/scene_editor/no_backgrounds = 这个场景没有背景:在下方列表中选一个加入。
tools/scene_editor/pick_scene = 在左边选择一个场景,或创建一个新场景。
tools/scene_editor/preview_hint = 选择一个背景来查看。
tools/scene_editor/not_in_scene = (不在场景中)
tools/scene_editor/new_line_text = 一个新场景。用你的文字替换这一行。
tools/entity_editor/hint = 左边是带搜索的列表,右边是所选条目的字段 · Ctrl+N 新建,Ctrl+D 复制 · Ctrl+Z/Y 撤销/重做 · Ctrl+S 保存,游戏会立即看到更改
tools/entity_editor/section_list = 模组的条目
tools/entity_editor/search = 按名称或 id 搜索
tools/entity_editor/mod_tip = 要编辑其条目的模组;新条目会写入它的文件
tools/entity_editor/new = 新建
tools/entity_editor/new_tip = 向所选模组添加一个条目(Ctrl+N)
tools/entity_editor/duplicate = 复制
tools/entity_editor/duplicate_tip = 用新 id 复制选中的条目(Ctrl+D)
tools/entity_editor/delete = 删除
tools/entity_editor/delete_tip = 从模组文件中移除选中的条目
tools/entity_editor/delete_title = 删除该条目?
tools/entity_editor/delete_text = 保存时该条目会从模组文件中移除。剧本和地图中对它的引用会保留——请检查它们。
tools/entity_editor/undo = 撤销
tools/entity_editor/undo_tip = 撤销上一次更改(Ctrl+Z)
tools/entity_editor/redo = 重做
tools/entity_editor/redo_tip = 重做已撤销的操作(Ctrl+Y)
tools/entity_editor/no_entity = 在左边选择一个条目,或用"新建"创建一个。
tools/entity_editor/file = 模组 {0} · 文件 {1}
tools/entity_editor/id = Id
tools/entity_editor/id_tip = 唯一 id:剧本、地图和其他模组通过它找到这个条目。依赖所有者并提供相同 id 的模组会替换该条目
tools/entity_editor/locale_key_tip = 本地化键;下面的文本会写入模组的 locale/*.properties
tools/entity_editor/text_in = 文本({0})
tools/entity_editor/picture_pick_tip = 从模组文件夹中选择图片
tools/entity_editor/picture_pick_title = "{0}"组的图片
tools/entity_editor/section_other = 其他字段
tools/entity_editor/other_hint = 表单中没有显示的字段(其他模组的数据、战利品…),以 JSON 形式显示。JSON 一旦有效,修改就会生效。
tools/entity_editor/other_invalid = 其他字段:JSON 无效——{0}
tools/entity_editor/other_applied = 已应用其他字段
tools/entity_editor/opened = 模组 {1} 中有 {0} 个条目
tools/entity_editor/added = 已把条目 {0} 加入 {1}
tools/entity_editor/deleted = 已删除条目 {0}(保存后写入)
tools/entity_editor/saved = 已保存:{0} 个文件、{1} 条文本,模组 {2};游戏已重新读取数据
tools/entity_editor/broken_files = 没有读取 JSON 损坏的文件:{0}
tools/entity_editor/problem_id = 文件 {0} 中有条目的 id 为空
tools/entity_editor/problem_duplicate = id {0} 出现了不止一次
+5 -2
View File
@@ -3,12 +3,15 @@
"name": "Editor Tools",
"version": "1.0.0",
"author": "ct",
"description": "Developer toolbox of ToolBox windows: scenario graph editor, scene editor (create and change the places of a novel: backgrounds, ambient sound, default scenario, the scene's scenarios), map editor with zones, scene builder with inspector, interactive and light editors, and the mod catalog export window.",
"description": "The author's windows over a running game: scene, scenario, map, interactive and light editors and a Godot scene builder. What they save are ordinary mod files the game applies at once.",
"category": "tools, editor",
"sources": [
"src/**/*.cs"
],
"depends": ["dialog_base>=1", "interactive_base>=1", "time_base>=1", "map_base>=1", "ui_base>=1"],
"test_sources": [
"tests/*.cs"
],
"depends": ["dialog_base>=1", "interactive_base>=1", "time_base>=1", "map_base>=1", "ui_base>=1", "schema_base>=1"],
"test_suites": [
{"id": "windows", "activate": ["--itest=tools_windows"]},
{"id": "ingame_play", "activate": ["--itest=ingame_play"]},
-12
View File
@@ -1,12 +0,0 @@
[gd_scene format=3]
[ext_resource type="Script" path="src/Hub/NovelEditorHub.cs" id="NovelEditorHubScript"]
[node name="NovelEditorHub" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("NovelEditorHubScript")
+23
View File
@@ -15,12 +15,16 @@ namespace Cthangover.Tools.Common
/// </summary>
public partial class FilteredList : VBoxContainer
{
// Scroll bar, item padding and a little text room on the right.
private const int IconBoxMargin = 40;
private readonly LineEdit _search;
private readonly ItemList _list;
private readonly List<(string Id, string Text, Texture2D Icon)> _items = new();
private readonly List<string> _visibleIds = new();
private string _selectedId;
private bool _silent;
private int _fitSide;
/// <summary>Raised when the user selects a row (click or keyboard).</summary>
public event Action<string> Chosen;
@@ -65,6 +69,19 @@ namespace Cthangover.Tools.Common
_list.FixedColumnWidth = iconSize + 24;
}
/// <summary>
/// Scales row icons to fit a box at most <paramref name="maxSide"/> px high and never wider than the
/// list (minus room for the scroll bar): pictures of any size — a 1024×1920 portrait as well as a
/// 32 px icon — keep their aspect and the row stays inside the panel. The box follows the list
/// width, so narrowing the panel shrinks the pictures further.
/// </summary>
public void FitIcons(int maxSide)
{
_fitSide = maxSide;
_list.Resized += UpdateIconBox;
UpdateIconBox();
}
/// <summary>Replaces all items; keeps the current selection when its id is still present.</summary>
public void SetItems(IEnumerable<(string Id, string Text, Texture2D Icon)> items)
{
@@ -131,6 +148,12 @@ namespace Cthangover.Tools.Common
Chosen?.Invoke(_selectedId);
}
private void UpdateIconBox()
{
var width = (int)_list.Size.X - IconBoxMargin;
_list.FixedIconSize = new Vector2I(Math.Clamp(width, 16, _fitSide), _fitSide);
}
private string IdAt(int index) => index >= 0 && index < _visibleIds.Count ? _visibleIds[index] : null;
}
}
+2 -1
View File
@@ -119,12 +119,13 @@ namespace Cthangover.Tools.Common
/// <summary>Monospace font for IDs, numbers and code (style guide <c>--ct-mono</c>).</summary>
public static Font Mono() => ToolWindow.GetMonospaceFont();
/// <summary>Code editor preset: monospace, line numbers, current-line highlight, no wrap.</summary>
/// <summary>Code editor preset: monospace, line numbers, JSON highlighting and no wrap; scenario/scene callers select their syntax.</summary>
public static CodeEdit Code(bool editable = true)
{
var edit = new CodeEdit
{
Editable = editable,
SyntaxHighlighter = SourceSyntax.CreateJsonHighlighter(),
GuttersDrawLineNumbers = true,
HighlightCurrentLine = true,
WrapMode = TextEdit.LineWrappingMode.None,
+252
View File
@@ -0,0 +1,252 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace Cthangover.Tools.EntityEditor
{
/// <summary>
/// The entities of one kind (<c>"Kind": "item"</c>, <c>"character"</c>…) in one
/// mod, as an editor changes them, free of any UI.
/// <para/>
/// Files are kept as JSON trees, not as typed DTOs: a form changes only the
/// properties it knows, and everything else — fields of other mods' C#
/// (<c>heal</c>, <c>verb</c>, <c>Charset</c>), loot tables, the property
/// order — survives a save untouched. A file is written only when its text
/// differs from what was loaded or last saved, so opening and saving does not
/// reformat files nobody changed. New entities go to <see cref="NewFile"/>,
/// created on the first save when the mod has no such file.
/// <para/>
/// Localized texts are part of the document too (<see cref="SetText"/>): the
/// form shows a name in every language of the mod, and an undo step restores
/// texts and data together. Undo works on whole states, as in the map editor.
/// </summary>
public class EntityDocument
{
private const int HistoryLimit = 200;
private static readonly JsonSerializerOptions WriteOptions = new()
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
private readonly Dictionary<string, JsonObject> files = new(StringComparer.Ordinal);
private readonly Dictionary<string, string> savedText = new(StringComparer.Ordinal);
private readonly List<EntityRecord> records = new();
private readonly List<string> undo = new();
private readonly List<string> redo = new();
private Dictionary<string, string> texts = new(StringComparer.Ordinal);
private Dictionary<string, string> savedTexts = new(StringComparer.Ordinal);
/// <summary>Entity kind of the document.</summary>
public string Kind { get; }
/// <summary>File new entities are added to.</summary>
public string NewFile { get; }
/// <summary>The entities in file order (files sorted by path).</summary>
public IReadOnlyList<EntityRecord> Records => records;
/// <summary>True when there is a step to undo.</summary>
public bool CanUndo => undo.Count > 0;
/// <summary>True when there is a step to redo.</summary>
public bool CanRedo => redo.Count > 0;
private EntityDocument(string kind, string newFile)
{
Kind = kind;
NewFile = newFile;
}
/// <summary>
/// Builds the document from the mod's JSON files (path, text). Files of
/// another kind, without a kind or with broken JSON are skipped; their
/// paths are returned in <paramref name="skipped"/> only when they declare
/// this kind but cannot be read, so the window can name them.
/// </summary>
public static EntityDocument Load(string kind, string newFile, IEnumerable<(string Path, string Text)> sources, out List<string> skipped)
{
var document = new EntityDocument(kind, newFile);
skipped = new List<string>();
foreach (var (path, text) in sources.OrderBy(s => s.Path, StringComparer.Ordinal))
{
JsonObject root;
try
{
root = JsonNode.Parse(text ?? string.Empty) as JsonObject;
}
catch (JsonException)
{
if (text != null && text.Contains($"\"{kind}\""))
skipped.Add(path);
continue;
}
if (root == null || !string.Equals(root["Kind"]?.ToString(), kind, StringComparison.OrdinalIgnoreCase))
continue;
if (root["Items"] is not JsonArray)
root["Items"] = new JsonArray();
document.files[path] = root;
document.savedText[path] = Serialize(root);
}
document.Rebuild();
return document;
}
/// <summary>Paths of the document's files.</summary>
public IEnumerable<string> Files => files.Keys;
/// <summary>Adds an entity to <see cref="NewFile"/> (the file is created when missing) and returns it.</summary>
public EntityRecord Add(JsonObject entity)
{
if (!files.TryGetValue(NewFile, out var root))
{
root = new JsonObject { ["Kind"] = Kind, ["Items"] = new JsonArray() };
files[NewFile] = root;
}
((JsonArray)root["Items"]).Add(entity);
Rebuild();
return records.First(r => r.Data == entity);
}
/// <summary>A copy of <paramref name="record"/> with id <paramref name="newId"/>, placed right after it in its file.</summary>
public EntityRecord Duplicate(EntityRecord record, string newId)
{
var copy = (JsonObject)record.Data.DeepClone();
copy["Id"] = newId;
var items = (JsonArray)files[record.File]["Items"];
items.Insert(items.IndexOf(record.Data) + 1, copy);
Rebuild();
return records.First(r => r.Data == copy);
}
/// <summary>Removes an entity from its file (the file stays, maybe with no items).</summary>
public void Remove(EntityRecord record)
{
((JsonArray)files[record.File]["Items"]).Remove(record.Data);
Rebuild();
}
/// <summary><paramref name="wanted"/> when no entity has it, else <c>wanted_2</c>, <c>wanted_3</c>…</summary>
public string FreeId(string wanted)
{
var id = wanted;
for (var number = 2; records.Any(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase)); number++)
id = wanted + "_" + number;
return id;
}
/// <summary>The record with this id, or null.</summary>
public EntityRecord Find(string id) => records.FirstOrDefault(r => r.Id == id);
/// <summary>A localized text the document holds (see <see cref="SetText"/>), or null when it was never set or loaded.</summary>
public string GetText(string language, string key)
=> texts.TryGetValue(TextKey(language, key), out var value) ? value : null;
/// <summary>Remembers the text the mod's files have now (loading, not an edit).</summary>
public void LoadText(string language, string key, string value)
{
var textKey = TextKey(language, key);
texts[textKey] = value;
savedTexts[textKey] = value;
}
/// <summary>Changes a localized text; it is written on save.</summary>
public void SetText(string language, string key, string value) => texts[TextKey(language, key)] = value ?? string.Empty;
/// <summary>Remembers the current state as one undo step and forgets the redo branch.</summary>
public void BeginChange()
{
undo.Add(Snapshot());
if (undo.Count > HistoryLimit)
undo.RemoveAt(0);
redo.Clear();
}
/// <summary>Steps back; false when there is nothing to undo.</summary>
public bool Undo() => Swap(undo, redo);
/// <summary>Steps forward again; false when there is nothing to redo.</summary>
public bool Redo() => Swap(redo, undo);
/// <summary>Files whose text changed since loading or the last <see cref="MarkSaved"/>.</summary>
public List<(string Path, string Text)> ChangedFiles()
=> files.Select(f => (f.Key, Serialize(f.Value)))
.Where(f => !savedText.TryGetValue(f.Key, out var saved) || saved != f.Item2)
.ToList();
/// <summary>Texts changed since loading or the last <see cref="MarkSaved"/>: language, key, value.</summary>
public List<(string Language, string Key, string Value)> ChangedTexts()
=> texts.Where(t => !savedTexts.TryGetValue(t.Key, out var saved) || saved != t.Value)
.Select(t => (t.Key.Substring(0, t.Key.IndexOf('\n')), t.Key.Substring(t.Key.IndexOf('\n') + 1), t.Value))
.ToList();
/// <summary>The current files and texts are what is on disk now.</summary>
public void MarkSaved()
{
foreach (var (path, text) in ChangedFiles())
savedText[path] = text;
savedTexts = new Dictionary<string, string>(texts, StringComparer.Ordinal);
}
/// <summary>Problems that would break the game's data, as locale keys with an argument; empty when it can be saved.</summary>
public List<(string Key, string Argument)> Validate()
{
var problems = new List<(string, string)>();
foreach (var record in records.Where(r => string.IsNullOrWhiteSpace(r.Id)))
problems.Add(("tools/entity_editor/problem_id", record.File));
foreach (var group in records.Where(r => !string.IsNullOrWhiteSpace(r.Id)).GroupBy(r => r.Id, StringComparer.OrdinalIgnoreCase))
{
if (group.Count() > 1)
problems.Add(("tools/entity_editor/problem_duplicate", group.Key));
}
return problems;
}
/// <summary>A file's text as the document writes it: two-space indented, letters unescaped, LF line breaks (the writer would use CRLF on Windows), a final one.</summary>
public static string Serialize(JsonObject root) => root.ToJsonString(WriteOptions).Replace("\r\n", "\n") + "\n";
private static string TextKey(string language, string key) => language + "\n" + key;
private void Rebuild()
{
records.Clear();
foreach (var file in files.OrderBy(f => f.Key, StringComparer.Ordinal))
{
foreach (var item in (JsonArray)file.Value["Items"])
{
if (item is JsonObject entity)
records.Add(new EntityRecord(file.Key, entity));
}
}
}
private string Snapshot()
{
var state = new JsonObject
{
["files"] = new JsonObject(files.Select(f => new KeyValuePair<string, JsonNode>(f.Key, f.Value.DeepClone()))),
["texts"] = new JsonObject(texts.Select(t => new KeyValuePair<string, JsonNode>(t.Key, t.Value)))
};
return state.ToJsonString();
}
private bool Swap(List<string> from, List<string> to)
{
if (from.Count == 0)
return false;
to.Add(Snapshot());
var state = JsonNode.Parse(from[^1]).AsObject();
from.RemoveAt(from.Count - 1);
files.Clear();
foreach (var file in state["files"].AsObject())
files[file.Key] = file.Value.DeepClone().AsObject();
texts = state["texts"].AsObject().ToDictionary(t => t.Key, t => t.Value?.ToString() ?? string.Empty, StringComparer.Ordinal);
Rebuild();
return true;
}
}
}
+740
View File
@@ -0,0 +1,740 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using Cthangover.Core.Mods;
using Cthangover.Core.Mods.Resolvers;
using Cthangover.Core.UI.Theming;
using Cthangover.Core.Utils;
using Cthangover.Mods.Interface.Theming;
using Cthangover.Mods.Interface.Tool;
using Cthangover.Mods.Schemas;
using Cthangover.Tools.Common;
using Cthangover.Tools.Schemas;
using Cthangover.Tools.Services;
using Godot;
namespace Cthangover.Tools.EntityEditor
{
/// <summary>
/// Base of the data editors (items, characters…): every entity of one kind in
/// one writable mod, a search list on the left and a form on the right built
/// from the editor's <see cref="EntityField"/> list. Layout and behaviour are
/// shared by all such editors, so they look and work alike; a concrete editor
/// only names its kind, its fields and how a new entity starts out.
/// <para/>
/// For a beginner the form is all there is: a name is typed in each language
/// of the mod (the locale key is made from the id), a picture is picked from a
/// grid, lists are check boxes. Save writes the mod's data and locale files and
/// makes the running game re-read them (entities, then texts), so the change is
/// visible at once. For a professional nothing is hidden: the locale key stays
/// editable, and every property the form does not know is shown as JSON under
/// "other fields", edited there and written back as it is.
/// <para/>
/// Fields other mods describe for the kind in their schemas (<c>schemas/*.json</c>: the
/// equipment mod adds <c>Equip</c> and <c>Traits</c> to items, the stats add <c>Archetype</c>
/// and <c>Stats</c> to characters) get their own block, "fields of other mods", with the
/// schema editors, so a data editor grows with the mods loaded next to it without
/// knowing them.
/// <para/>
/// Every change is undoable (Ctrl+Z / Ctrl+Y): one undo step per editing
/// session of one entity, and one per add, duplicate or delete.
/// </summary>
public abstract partial class EntityEditorWindow : ToolWindow
{
private readonly string kind;
private readonly string folder;
private readonly List<EntityField> fields;
private readonly Dictionary<EntityField, Control> controls = new();
private readonly Dictionary<EntityField, LineEdit> keyEdits = new();
private readonly Dictionary<EntityField, Dictionary<string, LineEdit>> textEdits = new();
private readonly Dictionary<EntityField, TextureRect> previews = new();
private readonly Dictionary<string, Texture2D> icons = new();
private readonly List<string> mods = new();
private readonly List<ISchemaFieldEditor> schemaEditors = new();
private OptionButton modPicker;
private FilteredList list;
private VBoxContainer form;
private Label emptyHint;
private Label fileLabel;
private LineEdit idEdit;
private CodeEdit otherEdit;
private EntityDocument document;
private EntityRecord selected;
private List<string> languages = new();
private string modId;
private bool suppress;
private bool editing;
/// <summary>Builds the window for entities of <paramref name="entityKind"/>; new ones go to <c>&lt;folder&gt;/&lt;mod&gt;.json</c>.</summary>
protected EntityEditorWindow(string titleKey, string entityKind, string entityFolder, List<EntityField> entityFields) : base(titleKey)
{
kind = entityKind;
folder = entityFolder;
fields = entityFields;
CthangoverTheme.Apply(this);
BuildUi();
SetHint(ToolUi.Tr("tools/entity_editor/hint"));
FillMods();
}
/// <summary>A new entity's JSON: the kind's required fields with starting values.</summary>
protected abstract JsonObject CreateEntity(string id);
/// <summary>Id a new entity starts from (numbered when taken), e.g. <c>&lt;mod&gt;/new_item</c>.</summary>
protected abstract string NewIdBase(string mod);
/// <summary>The edited mod, or null.</summary>
public string ModId => modId;
/// <summary>The selected entity's id, or null.</summary>
public string SelectedId => selected?.Id;
/// <summary>The edited entities (null before a mod is opened).</summary>
public EntityDocument Document => document;
// ── layout ─────────────────────────────────────────────────
private void BuildUi()
{
var outer = CreateFillContainer();
AddChild(outer);
outer.AddChild(BuildToolbar());
var main = new HSplitContainer { SizeFlagsHorizontal = Control.SizeFlags.ExpandFill, SizeFlagsVertical = Control.SizeFlags.ExpandFill };
outer.AddChild(main);
var listColumn = new VBoxContainer { SizeFlagsVertical = Control.SizeFlags.ExpandFill };
listColumn.AddChild(ToolUi.Section("tools/entity_editor/section_list"));
list = new FilteredList(320f, "tools/entity_editor/search");
list.SizeFlagsVertical = Control.SizeFlags.ExpandFill;
list.FitIcons(64);
list.Chosen += OnChosen;
listColumn.AddChild(list);
var listPanel = CreateSidebar(listColumn, 320);
main.AddChild(listPanel);
var scroll = new ScrollContainer { HorizontalScrollMode = ScrollContainer.ScrollMode.Disabled, SizeFlagsHorizontal = Control.SizeFlags.ExpandFill };
main.AddChild(scroll);
var column = new VBoxContainer { SizeFlagsHorizontal = Control.SizeFlags.ExpandFill };
column.AddThemeConstantOverride("separation", UiTokens.Space2);
scroll.AddChild(column);
emptyHint = ToolUi.Hint(ToolUi.Tr("tools/entity_editor/no_entity"));
column.AddChild(emptyHint);
form = new VBoxContainer { SizeFlagsHorizontal = Control.SizeFlags.ExpandFill };
form.AddThemeConstantOverride("separation", UiTokens.Space2);
column.AddChild(form);
BuildForm();
}
private HBoxContainer BuildToolbar()
{
var toolbar = CreateToolbar();
modPicker = new OptionButton { TooltipText = ToolUi.Tr("tools/entity_editor/mod_tip") };
modPicker.ItemSelected += OnModPicked;
toolbar.AddChild(modPicker);
toolbar.AddChild(ToolUi.Button("tools/common/save", OnSavePressed, ButtonKind.Primary, "tools/common/save_tip"));
toolbar.AddChild(ToolUi.Divider());
toolbar.AddChild(ToolUi.Button("tools/entity_editor/new", AddNew, ButtonKind.Secondary, "tools/entity_editor/new_tip"));
toolbar.AddChild(ToolUi.Button("tools/entity_editor/duplicate", DuplicateSelected, ButtonKind.Secondary, "tools/entity_editor/duplicate_tip"));
toolbar.AddChild(ToolUi.Button("tools/entity_editor/delete", DeleteSelected, ButtonKind.Secondary, "tools/entity_editor/delete_tip"));
toolbar.AddChild(ToolUi.Divider());
toolbar.AddChild(ToolUi.Button("tools/entity_editor/undo", Undo, ButtonKind.Secondary, "tools/entity_editor/undo_tip"));
toolbar.AddChild(ToolUi.Button("tools/entity_editor/redo", Redo, ButtonKind.Secondary, "tools/entity_editor/redo_tip"));
return toolbar;
}
private void BuildForm()
{
fileLabel = ToolUi.Hint(string.Empty);
form.AddChild(fileLabel);
idEdit = ToolUi.Field(form, "tools/entity_editor/id", ToolUi.Text("", OnTextChanged), "tools/entity_editor/id_tip");
idEdit.AddThemeFontOverride("font", ToolUi.Mono());
foreach (var field in fields)
BuildField(field);
BuildSchemaFields();
form.AddChild(ToolUi.Section("tools/entity_editor/section_other"));
form.AddChild(ToolUi.Hint(ToolUi.Tr("tools/entity_editor/other_hint")));
otherEdit = ToolUi.Code();
otherEdit.CustomMinimumSize = new Vector2(0, 160);
otherEdit.SizeFlagsVertical = Control.SizeFlags.Fill;
otherEdit.TextChanged += OnOtherChanged;
form.AddChild(otherEdit);
}
private void BuildField(EntityField field)
{
switch (field.Kind)
{
case EntityFieldKind.Text:
controls[field] = ToolUi.Field(form, field.LabelKey, ToolUi.Text("", OnTextChanged), field.TooltipKey);
break;
case EntityFieldKind.Integer:
controls[field] = ToolUi.Field(form, field.LabelKey, ToolUi.Number(-999999, 999999, 1, OnNumberChanged), field.TooltipKey);
break;
case EntityFieldKind.Choice:
var choice = ToolUi.Field(form, field.LabelKey, new OptionButton(), field.TooltipKey);
choice.ItemSelected += OnChoicePicked;
controls[field] = choice;
break;
case EntityFieldKind.Flags:
case EntityFieldKind.List:
controls[field] = ToolUi.Field(form, field.LabelKey, new HFlowContainer(), field.TooltipKey);
break;
case EntityFieldKind.Picture:
BuildPicture(field);
break;
case EntityFieldKind.LocaleText:
// The texts get their rows when a mod is opened: its languages decide them.
keyEdits[field] = ToolUi.Field(form, field.LabelKey, ToolUi.Text("", OnTextChanged), "tools/entity_editor/locale_key_tip");
keyEdits[field].AddThemeFontOverride("font", ToolUi.Mono());
var texts = new VBoxContainer();
form.AddChild(texts);
controls[field] = texts;
textEdits[field] = new Dictionary<string, LineEdit>();
break;
}
}
private void BuildSchemaFields()
{
var described = Cthangover.Mods.Schemas.SchemaCatalog.Current.FieldsOf(kind)
.Where(f => !string.Equals(f.Key, "Id", StringComparison.OrdinalIgnoreCase) && !fields.Any(e => string.Equals(e.Key, f.Key, StringComparison.OrdinalIgnoreCase)))
.ToList();
if (described.Count == 0)
return;
form.AddChild(ToolUi.Section("tools/entity_editor/section_schema"));
var sources = string.Join(", ", described.Select(f => f.SourceMod).Where(m => m != null).Distinct());
form.AddChild(ToolUi.Hint(string.Format(ToolUi.Tr("tools/entity_editor/schema_hint"), sources)));
foreach (var field in described)
schemaEditors.Add(SchemaFieldEditors.Create(form, field, WriteForm));
}
private void BuildPicture(EntityField field)
{
var row = new HBoxContainer();
var path = ToolUi.Text(field.Group + "/…", OnPictureTyped);
path.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
row.AddChild(path);
var pick = UiKit.IconButton("…", ToolUi.Tr("tools/entity_editor/picture_pick_tip"));
pick.Pressed += () => OpenPicker(field);
row.AddChild(pick);
ToolUi.Field(form, field.LabelKey, row, field.TooltipKey);
controls[field] = path;
var preview = new TextureRect
{
CustomMinimumSize = new Vector2(96, 96),
ExpandMode = TextureRect.ExpandModeEnum.IgnoreSize,
StretchMode = TextureRect.StretchModeEnum.KeepAspectCentered,
TextureFilter = CanvasItem.TextureFilterEnum.Nearest,
SizeFlagsHorizontal = Control.SizeFlags.ShrinkBegin
};
var margin = new MarginContainer();
margin.AddThemeConstantOverride("margin_left", ToolUi.LabelWidth + UiTokens.Space3);
margin.AddChild(preview);
form.AddChild(margin);
previews[field] = preview;
}
// ── mods and the list ──────────────────────────────────────
private void FillMods()
{
mods.Clear();
mods.AddRange(ModFileService.ListWritableMods());
modPicker.Clear();
foreach (var mod in mods)
modPicker.AddItem(mod);
if (mods.Count == 0)
{
SetStatus(ToolUi.Tr("tools/common/no_writable_mods"), ToolStatusKind.Error);
return;
}
// Start in the first mod that already has such entities: usually the game's own content.
var start = mods.FindIndex(m => LoadDocument(m).Records.Count > 0);
OpenMod(mods[Math.Max(0, start)]);
}
private EntityDocument LoadDocument(string mod)
{
var sources = ModFileService.ListFiles(mod, string.Empty, ".json")
.Select(path => (path, ModFileService.ReadText(mod, path)));
var loaded = EntityDocument.Load(kind, $"{folder}/{mod}.json", sources, out var skipped);
if (skipped.Count > 0)
SetStatus(string.Format(ToolUi.Tr("tools/entity_editor/broken_files"), string.Join(", ", skipped)), ToolStatusKind.Error);
return loaded;
}
/// <summary>Opens the entities of a mod (asks first about unsaved changes).</summary>
public void OpenMod(string mod)
{
RunDiscardingChanges(() => DoOpenMod(mod), () => modPicker.Select(mods.IndexOf(modId)));
}
private void DoOpenMod(string mod)
{
modId = mod;
modPicker.Select(mods.IndexOf(mod));
document = LoadDocument(mod);
languages = LocaleFileService.Languages(mod);
foreach (var field in fields.Where(f => f.Kind == EntityFieldKind.LocaleText))
BuildTextRows(field);
foreach (var locale in schemaEditors.OfType<LocaleFieldEditor>())
locale.Open(document, mod, languages);
MarkClean();
selected = null;
RefreshList();
Select(document.Records.FirstOrDefault());
SetStatus(string.Format(ToolUi.Tr("tools/entity_editor/opened"), document.Records.Count, mod));
}
private void BuildTextRows(EntityField field)
{
var box = (VBoxContainer)controls[field];
ClearChildren(box);
textEdits[field].Clear();
foreach (var language in languages)
{
var label = string.Format(ToolUi.Tr("tools/entity_editor/text_in"), language);
var edit = ToolUi.Text("", OnTextChanged);
var row = new HBoxContainer();
row.AddThemeConstantOverride("separation", UiTokens.Space3);
row.AddChild(new Label { Text = label, CustomMinimumSize = new Vector2(ToolUi.LabelWidth, 0), HorizontalAlignment = HorizontalAlignment.Right });
edit.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
row.AddChild(edit);
box.AddChild(row);
textEdits[field][language] = edit;
}
}
private void RefreshList()
{
var first = fields.FirstOrDefault(f => f.Kind == EntityFieldKind.Picture);
var name = fields.FirstOrDefault(f => f.Kind == EntityFieldKind.LocaleText);
list.SetItems(document.Records.Select(r => (r.Id, RowText(r, name), first == null ? null : Icon(first.Group, Str(r.Data, first.Key)))));
list.Select(selected?.Id);
}
private string RowText(EntityRecord record, EntityField nameField)
{
var key = nameField == null ? null : Str(record.Data, nameField.Key);
var text = key == null ? null : document.GetText(languages.FirstOrDefault() ?? "ru", key) ?? LocaleFileService.Read(modId, languages.FirstOrDefault() ?? "ru", key);
return string.IsNullOrWhiteSpace(text) ? record.Id : $"{text} · {record.Id}";
}
private Texture2D Icon(string group, string id)
{
if (string.IsNullOrEmpty(id))
return null;
var cacheKey = group + "|" + id;
if (!icons.TryGetValue(cacheKey, out var texture))
{
texture = ModResourceService.LoadGroupImage(group, id);
icons[cacheKey] = texture;
}
return texture;
}
private void OnChosen(string id) => Select(document?.Find(id));
private void OnModPicked(long index)
{
if (index >= 0 && index < mods.Count && mods[(int)index] != modId)
OpenMod(mods[(int)index]);
}
// ── form ───────────────────────────────────────────────────
/// <summary>Shows an entity in the form (null: none).</summary>
public void Select(EntityRecord record)
{
selected = record;
editing = false;
emptyHint.Visible = record == null;
form.Visible = record != null;
list.Select(record?.Id);
if (record == null)
return;
suppress = true;
fileLabel.Text = string.Format(ToolUi.Tr("tools/entity_editor/file"), modId, record.File);
idEdit.Text = record.Id;
foreach (var field in fields)
ShowField(field, record.Data);
foreach (var editor in schemaEditors)
editor.Load(record.Data.FirstOrDefault(p => string.Equals(p.Key, editor.Field.Key, StringComparison.OrdinalIgnoreCase)).Value);
otherEdit.Text = OtherJson(record.Data);
suppress = false;
}
private void ShowField(EntityField field, JsonObject data)
{
switch (field.Kind)
{
case EntityFieldKind.Text:
((LineEdit)controls[field]).Text = Str(data, field.Key) ?? string.Empty;
break;
case EntityFieldKind.Integer:
((SpinBox)controls[field]).Value = data[field.Key] is JsonValue v && v.TryGetValue(out int number) ? number : 0;
break;
case EntityFieldKind.Choice:
FillChoice((OptionButton)controls[field], field, Str(data, field.Key));
break;
case EntityFieldKind.Flags:
FillChecks((HFlowContainer)controls[field], field, (Str(data, field.Key) ?? string.Empty).Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries));
break;
case EntityFieldKind.List:
FillChecks((HFlowContainer)controls[field], field, (data[field.Key] as JsonArray)?.Select(n => n?.ToString()).Where(s => s != null).ToArray() ?? Array.Empty<string>());
break;
case EntityFieldKind.Picture:
var path = Str(data, field.Key) ?? string.Empty;
((LineEdit)controls[field]).Text = path;
previews[field].Texture = ModResourceService.LoadGroupImage(field.Group, path);
break;
case EntityFieldKind.LocaleText:
var key = Str(data, field.Key) ?? string.Empty;
keyEdits[field].Text = key;
foreach (var pair in textEdits[field])
pair.Value.Text = TextOf(pair.Key, key);
break;
}
}
private string TextOf(string language, string key)
{
if (string.IsNullOrEmpty(key))
return string.Empty;
var text = document.GetText(language, key);
if (text != null)
return text;
text = LocaleFileService.Read(modId, language, key) ?? string.Empty;
document.LoadText(language, key, text);
return text;
}
private static void FillChoice(OptionButton picker, EntityField field, string value)
{
var options = (field.Options?.Invoke() ?? Enumerable.Empty<string>()).ToList();
if (!string.IsNullOrEmpty(value) && !options.Contains(value))
options.Insert(0, value);
picker.Clear();
foreach (var option in options)
picker.AddItem(option);
picker.Select(options.IndexOf(value ?? string.Empty));
}
private void FillChecks(HFlowContainer box, EntityField field, string[] values)
{
ClearChildren(box);
var options = (field.Options?.Invoke() ?? Enumerable.Empty<string>()).ToList();
options.AddRange(values.Where(v => !options.Contains(v)));
foreach (var option in options)
{
var check = new CheckBox { Text = option, ButtonPressed = values.Contains(option) };
check.Toggled += OnChecked;
box.AddChild(check);
}
}
private void OnTextChanged(string text) => WriteForm();
private void OnNumberChanged(double value) => WriteForm();
private void OnChoicePicked(long index) => WriteForm();
private void OnChecked(bool on) => WriteForm();
private void OnPictureTyped(string text)
{
WriteForm();
foreach (var pair in previews)
pair.Value.Texture = ModResourceService.LoadGroupImage(pair.Key.Group, ((LineEdit)controls[pair.Key]).Text);
}
private void OpenPicker(EntityField field)
{
if (selected == null)
return;
var dialog = new PicturePickerDialog(field.Group, ((LineEdit)controls[field]).Text);
dialog.Picked += id => ((LineEdit)controls[field]).Text = id;
dialog.Picked += OnPictureTyped;
AddChild(dialog);
dialog.PopupCentered();
}
/// <summary>Copies the form into the selected entity; the first change of a session records an undo step.</summary>
private void WriteForm()
{
if (suppress || selected == null)
return;
if (!editing)
{
document.BeginChange();
editing = true;
}
var data = selected.Data;
data["Id"] = idEdit.Text.Trim();
foreach (var field in fields)
WriteField(field, data);
foreach (var editor in schemaEditors)
{
var key = data.FirstOrDefault(p => string.Equals(p.Key, editor.Field.Key, StringComparison.OrdinalIgnoreCase)).Key ?? editor.Field.Key;
var value = editor.Save();
if (value == null)
data.Remove(key);
else
data[key] = value;
}
SetDirty();
RefreshList();
}
private void WriteField(EntityField field, JsonObject data)
{
switch (field.Kind)
{
case EntityFieldKind.Text:
case EntityFieldKind.Picture:
SetOrRemove(data, field.Key, ((LineEdit)controls[field]).Text.Trim());
break;
case EntityFieldKind.Integer:
var number = (int)((SpinBox)controls[field]).Value;
if (number != 0 || data.ContainsKey(field.Key))
data[field.Key] = number;
break;
case EntityFieldKind.Choice:
var picker = (OptionButton)controls[field];
if (picker.Selected >= 0)
data[field.Key] = picker.GetItemText(picker.Selected);
break;
case EntityFieldKind.Flags:
var flags = Checked((HFlowContainer)controls[field]);
if (flags.Count > 0 || data.ContainsKey(field.Key))
data[field.Key] = flags.Count == 0 ? "None" : string.Join(",", flags);
break;
case EntityFieldKind.List:
var values = Checked((HFlowContainer)controls[field]);
if (values.Count > 0 || data.ContainsKey(field.Key))
data[field.Key] = new JsonArray(values.Select(v => (JsonNode)v).ToArray());
break;
case EntityFieldKind.LocaleText:
var key = keyEdits[field].Text.Trim();
SetOrRemove(data, field.Key, key);
if (key.Length == 0)
break;
foreach (var pair in textEdits[field])
document.SetText(pair.Key, key, pair.Value.Text);
break;
}
}
private static List<string> Checked(HFlowContainer box)
=> box.GetChildren().OfType<CheckBox>().Where(c => c.ButtonPressed).Select(c => c.Text).ToList();
/// <summary>Takes the children out of the tree at once (a search of the tree must not find them) and frees them.</summary>
private static void ClearChildren(Node box)
{
foreach (var child in box.GetChildren())
{
box.RemoveChild(child);
child.QueueFree();
}
}
private static void SetOrRemove(JsonObject data, string key, string value)
{
if (value.Length > 0)
data[key] = value;
else
data.Remove(key);
}
/// <summary>The properties no field shows, as an indented JSON object.</summary>
private string OtherJson(JsonObject data)
{
var other = new JsonObject();
foreach (var pair in data.Where(p => !IsFormKey(p.Key)))
other[pair.Key] = pair.Value?.DeepClone();
return EntityDocument.Serialize(other).TrimEnd();
}
private bool IsFormKey(string key)
=> key == "Id" || fields.Any(f => f.Key == key) || schemaEditors.Any(e => string.Equals(e.Field.Key, key, StringComparison.OrdinalIgnoreCase));
private void OnOtherChanged()
{
if (suppress || selected == null)
return;
JsonObject other;
try
{
other = JsonNode.Parse(otherEdit.Text) as JsonObject;
}
catch (JsonException ex)
{
SetStatus(string.Format(ToolUi.Tr("tools/entity_editor/other_invalid"), ex.Message), ToolStatusKind.Error);
return;
}
if (other == null)
return;
if (!editing)
{
document.BeginChange();
editing = true;
}
var data = selected.Data;
foreach (var key in data.Select(p => p.Key).Where(k => !IsFormKey(k)).ToList())
data.Remove(key);
foreach (var pair in other.Where(p => !IsFormKey(p.Key)).ToList())
data[pair.Key] = pair.Value?.DeepClone();
SetDirty();
SetStatus(ToolUi.Tr("tools/entity_editor/other_applied"));
}
// ── commands ───────────────────────────────────────────────
private void AddNew()
{
if (document == null)
return;
document.BeginChange();
var id = document.FreeId(NewIdBase(modId));
var entity = CreateEntity(id);
foreach (var field in fields.Where(f => f.Kind == EntityFieldKind.LocaleText && f.KeyPattern != null))
{
var key = string.Format(field.KeyPattern, id);
entity[field.Key] = key;
foreach (var language in languages)
document.SetText(language, key, field.Key == "Name" ? id.Substring(id.LastIndexOf('/') + 1) : string.Empty);
}
var record = document.Add(entity);
SetDirty();
RefreshList();
Select(record);
SetStatus(string.Format(ToolUi.Tr("tools/entity_editor/added"), id, record.File));
}
private void DuplicateSelected()
{
if (selected == null)
return;
document.BeginChange();
var record = document.Duplicate(selected, document.FreeId(selected.Id));
SetDirty();
RefreshList();
Select(record);
}
private void DeleteSelected()
{
if (selected == null)
return;
ShowConfirm("tools/entity_editor/delete_title", "tools/entity_editor/delete_text", ConfirmDelete);
}
private void ConfirmDelete()
{
if (selected == null)
return;
document.BeginChange();
var id = selected.Id;
document.Remove(selected);
SetDirty();
Select(null);
RefreshList();
SetStatus(string.Format(ToolUi.Tr("tools/entity_editor/deleted"), id));
}
private void Undo() => StepHistory(document != null && document.Undo());
private void Redo() => StepHistory(document != null && document.Redo());
private void StepHistory(bool stepped)
{
if (!stepped)
return;
// The document holds new JSON objects now: find the selection again by id.
var id = selected?.Id;
SetDirty();
RefreshList();
Select(document.Find(id) ?? document.Records.FirstOrDefault());
}
/// <summary>Ctrl+S.</summary>
protected override void OnSaveShortcut() => Save();
private void OnSavePressed() => Save();
/// <summary>
/// Writes the changed data and locale files and makes the game re-read
/// them: the entity cache is dropped (factories rebuild from the new
/// snapshot) and the current language's texts are reloaded.
/// </summary>
public bool Save()
{
if (document == null)
return false;
var problems = document.Validate();
if (problems.Count > 0)
{
SetStatus(string.Format(ToolUi.Tr(problems[0].Key), problems[0].Argument), ToolStatusKind.Error);
return false;
}
var changedFiles = document.ChangedFiles();
var changedTexts = document.ChangedTexts();
foreach (var (path, text) in changedFiles)
{
if (!ModFileService.WriteText(modId, path, text))
{
SetStatus(ToolUi.Tr("tools/common/readonly"), ToolStatusKind.Error);
return false;
}
}
foreach (var (language, key, value) in changedTexts)
LocaleFileService.Write(modId, language, key, value);
document.MarkSaved();
MarkClean();
editing = false;
ModManager.Instance.GetResolver<EntityResolver>()?.ClearCache();
ModManager.Instance.GetResolver<FileListResolver>()?.ClearCache();
icons.Clear();
if (changedTexts.Count > 0)
LocaleFileService.ReloadGame();
GameLogger.Log("TOOLS", $"entity editor: saved {changedFiles.Count} {kind} file(s), {changedTexts.Count} text(s) of '{modId}'");
SetStatus(string.Format(ToolUi.Tr("tools/entity_editor/saved"), changedFiles.Count, changedTexts.Count, modId), ToolStatusKind.Ok);
return true;
}
/// <summary>Shortcuts: Ctrl+Z / Ctrl+Y undo and redo, Ctrl+N adds, Ctrl+D duplicates.</summary>
protected override bool HandleKey(InputEventKey key)
{
if (!key.CtrlPressed)
return false;
switch (key.Keycode)
{
case Key.Z when key.ShiftPressed:
case Key.Y:
Redo();
return true;
case Key.Z:
Undo();
return true;
case Key.N:
AddNew();
return true;
case Key.D:
DuplicateSelected();
return true;
}
return false;
}
private static string Str(JsonObject data, string key) => data[key]?.ToString();
}
}
+35
View File
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
namespace Cthangover.Tools.EntityEditor
{
/// <summary>
/// One property of an entity as an <see cref="EntityEditorWindow"/> form shows
/// it. A concrete editor lists its fields; properties no field names are
/// shown and edited as raw JSON ("other fields"), so the form never hides or
/// drops data a mod added.
/// </summary>
public class EntityField
{
/// <summary>JSON property name.</summary>
public string Key { get; init; }
/// <summary>Locale key of the label.</summary>
public string LabelKey { get; init; }
/// <summary>Locale key of the tooltip, or null.</summary>
public string TooltipKey { get; init; }
/// <summary>How the value is edited.</summary>
public EntityFieldKind Kind { get; init; }
/// <summary>Picture: the file group of the pictures (<c>items</c>, <c>characters</c>…).</summary>
public string Group { get; init; }
/// <summary>Choice, flags, list: the values offered (read when the form is built).</summary>
public Func<IEnumerable<string>> Options { get; init; }
/// <summary>Locale text: how a new entity's key is made from its id (<c>{0}</c> = id).</summary>
public string KeyPattern { get; init; }
}
}
+21
View File
@@ -0,0 +1,21 @@
namespace Cthangover.Tools.EntityEditor
{
/// <summary>How an <see cref="EntityField"/> is shown and stored.</summary>
public enum EntityFieldKind
{
/// <summary>One line of text.</summary>
Text,
/// <summary>A locale key with its text in every language of the mod.</summary>
LocaleText,
/// <summary>A whole number.</summary>
Integer,
/// <summary>A picture path in a file group, with a preview and a picker.</summary>
Picture,
/// <summary>One value of a list (values outside the list stay possible).</summary>
Choice,
/// <summary>Several names of a list written as <c>"A,B"</c> (an enum with flags).</summary>
Flags,
/// <summary>A JSON array of strings picked from a list.</summary>
List
}
}
+24
View File
@@ -0,0 +1,24 @@
using System.Text.Json.Nodes;
namespace Cthangover.Tools.EntityEditor
{
/// <summary>One entity of an <see cref="EntityDocument"/>: its JSON object inside the <c>Items</c> array of its file.</summary>
public class EntityRecord
{
/// <summary>Mod-relative path of the file holding the entity.</summary>
public string File { get; }
/// <summary>The entity itself; unknown properties are kept as they are.</summary>
public JsonObject Data { get; }
/// <summary>The entity's <c>Id</c> (empty when missing).</summary>
public string Id => Data["Id"]?.ToString() ?? string.Empty;
/// <summary>Wraps an entity of a file.</summary>
public EntityRecord(string file, JsonObject data)
{
File = file;
Data = data;
}
}
}
+57
View File
@@ -0,0 +1,57 @@
using System;
using System.Linq;
using Cthangover.Mods.Interface.Tool;
using Cthangover.Tools.Common;
using Cthangover.Tools.Services;
using Godot;
namespace Cthangover.Tools.EntityEditor
{
/// <summary>
/// Grid of every picture of a file group with a search, for a picture field
/// of an entity form: a double click or OK picks one. The pictures are read
/// when the dialog opens, so a file just dropped into a mod folder shows up
/// (after the game re-reads its files: the editor's Save does that).
/// </summary>
public partial class PicturePickerDialog : AcceptDialog
{
private readonly FilteredList list;
/// <summary>A picture was picked: its id in the group.</summary>
public event Action<string> Picked;
/// <summary>Builds the dialog for the pictures of <paramref name="group"/>.</summary>
public PicturePickerDialog(string group, string current)
{
Theme = ToolTheme.Get();
Title = string.Format(ToolUi.Tr("tools/entity_editor/picture_pick_title"), group);
MinSize = new Vector2I(640, 520);
list = new FilteredList(420f);
list.UseIconGrid(64);
list.SetItems(ModResourceService.GetGroupImageIds(group)
.Select(id => (id, id.Substring(id.LastIndexOf('/') + 1), ModResourceService.LoadGroupImage(group, id))));
list.Select(current);
list.Activated += OnActivated;
AddChild(list);
Confirmed += OnConfirmed;
CloseRequested += QueueFree;
Canceled += QueueFree;
}
private void OnActivated(string id)
{
if (id == null)
return;
Picked?.Invoke(id);
Hide();
QueueFree();
}
private void OnConfirmed()
{
if (list.SelectedId != null)
Picked?.Invoke(list.SelectedId);
QueueFree();
}
}
}
-79
View File
@@ -1,79 +0,0 @@
namespace Cthangover.Tools.Hub
{
using Cthangover.Core.Scenes;
using Cthangover.Core.UI.Theming;
using Cthangover.Core.UI.Tool;
using Cthangover.Core.Utils;
using Cthangover.Mods.Interface.Theming;
using Godot;
/// <summary>
/// Launcher screen of the <c>novel_editor</c> collection, entered as its
/// logical entry scene. Lists every registered tool the same way the main
/// menu does — each <see cref="IToolProvider"/> appends its own button via
/// <see cref="IToolProvider.AddMenuButton"/> — so a new editor shows up here
/// without touching this screen. Tool windows open on the tree root and
/// survive scene switches (playing a scenario, returning to the menu).
/// </summary>
public partial class NovelEditorHub : Control
{
/// <summary>Logical scene name; the collection's <c>entry.scene</c>.</summary>
public const string SceneName = "novel_editor";
private const int PanelWidth = 420;
public override void _Ready()
{
CthangoverTheme.Apply(this);
AddChild(new ColorRect
{
Color = UiTokens.Page,
MouseFilter = MouseFilterEnum.Ignore,
AnchorRight = 1f,
AnchorBottom = 1f,
});
var center = new CenterContainer { AnchorRight = 1f, AnchorBottom = 1f };
AddChild(center);
var panel = new PanelContainer { CustomMinimumSize = new Vector2(PanelWidth, 0) };
var frame = UiKit.Frame(UiTokens.RadiusLg, UiTokens.Surface1, UiTokens.Line);
frame.SetContentMarginAll(UiTokens.Space8);
panel.AddThemeStyleboxOverride("panel", frame);
center.AddChild(panel);
var column = UiKit.Column(UiTokens.Space5);
panel.AddChild(column);
column.AddChild(UiKit.Label(Tr("tools/hub/title").ToUpperInvariant(), UiTokens.FontTitle, UiTokens.InkBright));
var hint = UiKit.Hint(Tr("tools/hub/hint"));
hint.AutowrapMode = TextServer.AutowrapMode.WordSmart;
column.AddChild(hint);
column.AddChild(new HSeparator());
column.AddChild(UiKit.SectionTitle(Tr("tools/hub/editors"), UiTokens.FontSmall));
var tools = UiKit.Column(UiTokens.Space2);
tools.MouseFilter = MouseFilterEnum.Pass;
column.AddChild(tools);
foreach (var tool in ToolFactory.Instance.GetAll())
tool.AddMenuButton(tools);
if (tools.GetChildCount() == 0)
column.AddChild(UiKit.EmptyState(Tr("tools/hub/empty")));
column.AddChild(new HSeparator());
var menu = UiKit.Button(Tr("tools/hub/main_menu"));
menu.Pressed += OnMainMenuPressed;
column.AddChild(menu);
GameLogger.Log("TOOLS", $"Novel editor hub: {tools.GetChildCount()} tool button(s)");
}
private void OnMainMenuPressed()
{
GetNodeOrNull<GodotSceneService>("/root/GodotSceneService")?.SwitchToMenu();
}
}
}
-26
View File
@@ -1,26 +0,0 @@
namespace Cthangover.Tools.Hub
{
using Cthangover.Core.Mods;
using Cthangover.Core.Scenes;
/// <summary>
/// Lifecycle entry point of the tools mod. Publishes the logical scene
/// <see cref="NovelEditorHub.SceneName"/>, the entry scene of the
/// <c>novel_editor</c> collection: launching that collection from the game
/// picker goes through the regular new-game flow and lands on the editor
/// launcher instead of a story scene.
/// </summary>
public class ToolsInitializer : IModInitializer
{
private const string HubScenePath = "scenes/novel_editor_hub.tscn";
public void OnModLoaded(string modId)
{
}
public void OnModResourcesReady()
{
SceneRouteRegistry.Register(NovelEditorHub.SceneName, HubScenePath, fade: false);
}
}
}
+1
View File
@@ -110,6 +110,7 @@ namespace Cthangover.Core.UI.Tool.LightEditor
canvasPanel.AddChild(_canvas);
_lineEdit = ToolUi.Code(editable: false);
_lineEdit.SyntaxHighlighter = ScenarioSyntaxService.CreateHighlighter();
_lineEdit.CustomMinimumSize = new Vector2(0, 70);
_lineEdit.WrapMode = TextEdit.LineWrappingMode.Boundary;
_lineEdit.Visible = false;
+2 -2
View File
@@ -321,8 +321,8 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor
Visible = false;
// The screen the editor was opened from (the novel_editor hub, a
// mod menu) is freed by the shell load below; remember its route so
// The screen the editor was opened from (a mod menu, a mod
// route) is freed by the shell load below; remember its route so
// the play-test returns there instead of to the engine's main menu.
var sceneService = GetNodeOrNull<GodotSceneService>("/root/GodotSceneService");
var current = sceneService?.CurrentGodotSceneName;
+1
View File
@@ -249,6 +249,7 @@ namespace Cthangover.Core.UI.Tool.SceneBuilder
_sideTabs.SetTabTitle(1, ToolUi.Tr("tools/scene_builder/tab_tscn"));
_tscnEditor = ToolUi.Code();
_tscnEditor.SyntaxHighlighter = SourceSyntax.CreateTscnHighlighter();
_tscnEditor.TextChanged += OnTscnTextChanged;
tscnTab.AddChild(_tscnEditor);
tscnTab.AddChild(ToolUi.Hint(ToolUi.Tr("tools/scene_builder/tscn_hint")));
+1 -1
View File
@@ -4,7 +4,7 @@ using Godot;
namespace Cthangover.Tools.SceneEditor
{
/// <summary>Registers the scene editor as a tool window (hub, main menu tools, in-game toolbox).</summary>
/// <summary>Registers the scene editor as a tool window (main menu tools, in-game toolbox).</summary>
public class SceneEditorToolProvider : IToolProvider
{
/// <summary>Unique tool identifier.</summary>
+20
View File
@@ -0,0 +1,20 @@
namespace Cthangover.Tools.Schemas
{
using Cthangover.Mods.Schemas;
using SchemaFieldDefinition = Cthangover.Mods.Schemas.FieldSchema;
using SchemaJsonValues = Cthangover.Mods.Schemas.SchemaJson;
using System.Text.Json.Nodes;
/// <summary>One field's editor in the data editor form: shows the field's JSON and gives it back edited.</summary>
public interface ISchemaFieldEditor
{
/// <summary>The described field.</summary>
SchemaFieldDefinition Field { get; }
/// <summary>Shows <paramref name="node"/> (null: the field is absent).</summary>
void Load(JsonNode node);
/// <summary>The edited value, or null to remove the key.</summary>
JsonNode Save();
}
}
+175
View File
@@ -0,0 +1,175 @@
namespace Cthangover.Tools.Schemas
{
using Cthangover.Mods.Schemas;
using SchemaFieldDefinition = Cthangover.Mods.Schemas.FieldSchema;
using SchemaJsonValues = Cthangover.Mods.Schemas.SchemaJson;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json.Nodes;
using Cthangover.Core.UI.Theming;
using Cthangover.Mods.Interface.Theming;
using Cthangover.Tools.Common;
using Godot;
/// <summary>
/// Edits a <c>list</c> field — an array of objects described by <see cref="SchemaFieldDefinition.Of"/>
/// (an item's traits, an archetype's learnings, a shop's goods) — as a stack of small
/// cards with an add and a remove button, each card a form of the sub-fields.
/// <para/>
/// The <c>values</c> type is the same editor over the engine's older list of
/// <c>"Key=Value"</c> strings (a battle action's <c>Values</c>): known keys become fields,
/// entries the schema does not know are kept as they were.
/// </summary>
public class ListFieldEditor : ISchemaFieldEditor
{
private readonly SchemaFieldDefinition field;
private readonly Action changed;
private readonly VBoxContainer cards;
private readonly List<List<SchemaFieldControl>> rows = new();
private readonly List<string> unknownValues = new();
private bool suppress;
/// <inheritdoc />
public SchemaFieldDefinition Field => field;
/// <summary>Adds the editor for <paramref name="field"/> to <paramref name="parent"/>.</summary>
public ListFieldEditor(Container parent, SchemaFieldDefinition field, Action changed)
{
this.field = field;
this.changed = changed;
var box = new VBoxContainer { Name = "List_" + field.Key };
box.AddThemeConstantOverride("separation", UiTokens.Space2);
cards = new VBoxContainer();
cards.AddThemeConstantOverride("separation", UiTokens.Space2);
box.AddChild(cards);
var add = ToolUi.Button("tools/schema/list_add", AddEmpty, ButtonKind.Secondary);
add.Name = "Add";
add.SizeFlagsHorizontal = Control.SizeFlags.ShrinkBegin;
box.AddChild(add);
ToolUi.Field(parent, string.IsNullOrEmpty(field.Name) ? field.Key : field.Name, box, field.Description);
}
private bool IsValues => string.Equals(field.Type, "values", StringComparison.OrdinalIgnoreCase);
/// <summary>Shows the array <paramref name="node"/> (null or not an array: empty).</summary>
public void Load(JsonNode node)
{
suppress = true;
Clear();
unknownValues.Clear();
if (node is JsonArray array)
{
if (IsValues)
LoadValues(array);
else
foreach (var item in array.OfType<JsonObject>())
AddCard(item);
}
suppress = false;
}
/// <summary>The edited array, or null when it has no entries (the key is then removed).</summary>
public JsonNode Save()
{
var array = new JsonArray();
if (IsValues)
{
foreach (var card in rows)
{
foreach (var control in card.Where(c => c.Value.Length > 0))
array.Add(control.Field.Key + "=" + control.Value);
}
foreach (var raw in unknownValues)
array.Add(raw);
return array.Count == 0 ? null : array;
}
foreach (var card in rows)
{
var item = new JsonObject();
foreach (var control in card.Where(c => c.Value.Length > 0))
item[control.Field.Key] = SchemaJsonValues.ToNode(control.Field, control.Value);
if (item.Count > 0)
array.Add(item);
}
return array.Count == 0 ? null : array;
}
private void LoadValues(JsonArray array)
{
var known = new JsonObject();
foreach (var raw in array.Select(n => n?.ToString()).Where(s => s != null))
{
var eq = raw.IndexOf('=');
var key = eq > 0 ? raw.Substring(0, eq) : raw;
var def = field.Of?.FirstOrDefault(f => string.Equals(f.Key, key, StringComparison.OrdinalIgnoreCase));
if (def == null || eq <= 0)
unknownValues.Add(raw);
else
known[def.Key] = raw.Substring(eq + 1);
}
AddCard(known);
}
private void AddEmpty()
{
AddCard(new JsonObject());
if (!suppress)
changed?.Invoke();
}
private void AddCard(JsonObject item)
{
if (IsValues && rows.Count > 0)
return;
var card = new PanelContainer { Name = "Card" + rows.Count };
card.AddThemeStyleboxOverride("panel", UiKit.Frame(UiTokens.RadiusSm, UiTokens.Well, UiTokens.LineSoft));
var column = new VBoxContainer();
card.AddChild(column);
var controls = new List<SchemaFieldControl>();
foreach (var sub in field.Of ?? new List<SchemaFieldDefinition>())
{
var control = new SchemaFieldControl(column, sub, OnChanged);
control.Value = SchemaJsonValues.ToText(Find(item, sub.Key));
controls.Add(control);
}
if (!IsValues)
{
var remove = ToolUi.Button("tools/schema/list_remove", () => Remove(card, controls), ButtonKind.Secondary);
remove.Name = "Remove";
remove.SizeFlagsHorizontal = Control.SizeFlags.ShrinkEnd;
column.AddChild(remove);
}
rows.Add(controls);
cards.AddChild(card);
}
private static JsonNode Find(JsonObject item, string key)
=> item.FirstOrDefault(p => string.Equals(p.Key, key, StringComparison.OrdinalIgnoreCase)).Value;
private void Remove(Control card, List<SchemaFieldControl> controls)
{
rows.Remove(controls);
cards.RemoveChild(card);
card.QueueFree();
changed?.Invoke();
}
private void OnChanged()
{
if (!suppress)
changed?.Invoke();
}
private void Clear()
{
rows.Clear();
foreach (var child in cards.GetChildren())
{
cards.RemoveChild(child);
child.QueueFree();
}
}
}
}
+112
View File
@@ -0,0 +1,112 @@
namespace Cthangover.Tools.Schemas
{
using Cthangover.Mods.Schemas;
using SchemaFieldDefinition = Cthangover.Mods.Schemas.FieldSchema;
using SchemaJsonValues = Cthangover.Mods.Schemas.SchemaJson;
using System;
using System.Collections.Generic;
using System.Text.Json.Nodes;
using Cthangover.Core.UI.Theming;
using Cthangover.Tools.Common;
using Cthangover.Tools.EntityEditor;
using Cthangover.Tools.Services;
using Godot;
/// <summary>
/// Edits a <c>locale</c> field: the locale key the JSON holds and, below it, the text of
/// that key in every language of the mod — the way the item and character editors do,
/// so a beginner types names and a professional can still rename the key. Texts go
/// through the <see cref="EntityDocument"/> and are written by the window's Save.
/// </summary>
public class LocaleFieldEditor : ISchemaFieldEditor
{
private readonly SchemaFieldDefinition field;
private readonly Action changed;
private readonly LineEdit keyEdit;
private readonly VBoxContainer texts;
private readonly Dictionary<string, LineEdit> edits = new();
private EntityDocument document;
private string modId;
private bool suppress;
/// <inheritdoc />
public SchemaFieldDefinition Field => field;
/// <summary>Adds the editor for <paramref name="field"/> to <paramref name="parent"/>.</summary>
public LocaleFieldEditor(Container parent, SchemaFieldDefinition field, Action changed)
{
this.field = field;
this.changed = changed;
keyEdit = ToolUi.Field(parent, string.IsNullOrEmpty(field.Name) ? field.Key : field.Name, ToolUi.Text(string.Empty, OnTyped), "tools/entity_editor/locale_key_tip");
keyEdit.Name = "Key_" + field.Key;
keyEdit.AddThemeFontOverride("font", ToolUi.Mono());
texts = new VBoxContainer();
parent.AddChild(texts);
}
/// <summary>Builds one text row per language of <paramref name="mod"/>.</summary>
public void Open(EntityDocument doc, string mod, IEnumerable<string> languages)
{
document = doc;
modId = mod;
foreach (var child in texts.GetChildren())
{
texts.RemoveChild(child);
child.QueueFree();
}
edits.Clear();
foreach (var language in languages)
{
var row = new HBoxContainer();
row.AddThemeConstantOverride("separation", UiTokens.Space3);
row.AddChild(new Label { Text = string.Format(ToolUi.Tr("tools/entity_editor/text_in"), language), CustomMinimumSize = new Vector2(ToolUi.LabelWidth, 0), HorizontalAlignment = HorizontalAlignment.Right });
var edit = ToolUi.Text(string.Empty, OnTyped);
edit.Name = "Text_" + field.Key + "_" + language;
edit.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
row.AddChild(edit);
texts.AddChild(row);
edits[language] = edit;
}
}
/// <summary>Shows the key in <paramref name="node"/> and its texts.</summary>
public void Load(JsonNode node)
{
suppress = true;
var key = node?.ToString() ?? string.Empty;
keyEdit.Text = key;
foreach (var pair in edits)
pair.Value.Text = TextOf(pair.Key, key);
suppress = false;
}
/// <summary>The key for the JSON (null when empty); texts are put into the document.</summary>
public JsonNode Save()
{
var key = keyEdit.Text.Trim();
if (key.Length == 0)
return null;
foreach (var pair in edits)
document?.SetText(pair.Key, key, pair.Value.Text);
return JsonValue.Create(key);
}
private string TextOf(string language, string key)
{
if (string.IsNullOrEmpty(key) || document == null)
return string.Empty;
var text = document.GetText(language, key);
if (text != null)
return text;
text = LocaleFileService.Read(modId, language, key) ?? string.Empty;
document.LoadText(language, key, text);
return text;
}
private void OnTyped(string text)
{
if (!suppress)
changed?.Invoke();
}
}
}
+130
View File
@@ -0,0 +1,130 @@
namespace Cthangover.Tools.Schemas
{
using Cthangover.Mods.Schemas;
using SchemaFieldDefinition = Cthangover.Mods.Schemas.FieldSchema;
using SchemaJsonValues = Cthangover.Mods.Schemas.SchemaJson;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Nodes;
using Cthangover.Core.UI.Theming;
using Cthangover.Mods.Interface.Theming;
using Cthangover.Tools.Common;
using Godot;
/// <summary>
/// Edits a <c>map</c> field — an object whose keys are ids of <see cref="SchemaFieldDefinition.Kind"/>
/// and whose values are numbers or formulas (a character's own <c>Stats</c>, an archetype's
/// growth): one row per key with a key picker, a value box and a remove button. A number
/// is written as a number, anything else as a formula text.
/// </summary>
public class MapFieldEditor : ISchemaFieldEditor
{
private readonly SchemaFieldDefinition field;
private readonly Action changed;
private readonly VBoxContainer lines;
private readonly List<(OptionButton Key, List<string> Keys, LineEdit Value, Control Row)> rows = new();
private bool suppress;
/// <inheritdoc />
public SchemaFieldDefinition Field => field;
/// <summary>Adds the editor for <paramref name="field"/> to <paramref name="parent"/>.</summary>
public MapFieldEditor(Container parent, SchemaFieldDefinition field, Action changed)
{
this.field = field;
this.changed = changed;
var box = new VBoxContainer { Name = "Map_" + field.Key };
lines = new VBoxContainer();
lines.AddThemeConstantOverride("separation", UiTokens.Space1);
box.AddChild(lines);
var add = ToolUi.Button("tools/schema/map_add", AddEmpty, ButtonKind.Secondary);
add.Name = "Add";
add.SizeFlagsHorizontal = Control.SizeFlags.ShrinkBegin;
box.AddChild(add);
ToolUi.Field(parent, string.IsNullOrEmpty(field.Name) ? field.Key : field.Name, box, field.Description);
}
/// <summary>Shows the object <paramref name="node"/>.</summary>
public void Load(JsonNode node)
{
suppress = true;
foreach (var row in rows)
{
lines.RemoveChild(row.Row);
row.Row.QueueFree();
}
rows.Clear();
if (node is JsonObject obj)
{
foreach (var pair in obj)
AddRow(pair.Key, SchemaJsonValues.ToText(pair.Value));
}
suppress = false;
}
/// <summary>The edited object, or null when empty.</summary>
public JsonNode Save()
{
var obj = new JsonObject();
foreach (var row in rows)
{
var key = row.Key.Selected >= 0 && row.Key.Selected < row.Keys.Count ? row.Keys[row.Key.Selected] : null;
if (!string.IsNullOrEmpty(key) && row.Value.Text.Trim().Length > 0)
obj[key] = SchemaJsonValues.NumberOrText(row.Value.Text.Trim());
}
return obj.Count == 0 ? null : obj;
}
private void AddEmpty()
{
AddRow(null, string.Empty);
changed?.Invoke();
}
private void AddRow(string key, string value)
{
var row = new HBoxContainer { Name = "Row" + rows.Count };
row.AddThemeConstantOverride("separation", UiTokens.Space2);
var keys = new List<string> { string.Empty };
if (!string.IsNullOrEmpty(key) && !GameEntityIndex.Of(field.Kind).ContainsKey(key))
keys.Add(key);
keys.AddRange(GameEntityIndex.Of(field.Kind).Keys);
var picker = new OptionButton { CustomMinimumSize = new Vector2(200, 0) };
foreach (var k in keys)
picker.AddItem(k.Length == 0 ? "—" : GameEntityIndex.Label(field.Kind, k));
picker.Select(Math.Max(0, keys.FindIndex(k => string.Equals(k, key, StringComparison.OrdinalIgnoreCase))));
picker.ItemSelected += OnPicked;
row.AddChild(picker);
var edit = ToolUi.Text(field.Default ?? string.Empty, OnTyped);
edit.Text = value ?? string.Empty;
edit.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
edit.AddThemeFontOverride("font", ToolUi.Mono());
row.AddChild(edit);
var entry = (picker, keys, edit, (Control)row);
row.AddChild(ToolUi.Button("tools/schema/list_remove", () => Remove(entry), ButtonKind.Secondary));
rows.Add(entry);
lines.AddChild(row);
}
private void Remove((OptionButton Key, List<string> Keys, LineEdit Value, Control Row) entry)
{
rows.Remove(entry);
lines.RemoveChild(entry.Row);
entry.Row.QueueFree();
changed?.Invoke();
}
private void OnPicked(long index)
{
if (!suppress)
changed?.Invoke();
}
private void OnTyped(string text)
{
if (!suppress)
changed?.Invoke();
}
}
}
+30
View File
@@ -0,0 +1,30 @@
namespace Cthangover.Tools.Schemas
{
using Cthangover.Mods.Schemas;
using SchemaFieldDefinition = Cthangover.Mods.Schemas.FieldSchema;
using SchemaJsonValues = Cthangover.Mods.Schemas.SchemaJson;
using System;
using System.Text.Json.Nodes;
using Godot;
/// <summary>A data editor form field with a single value: a <see cref="SchemaFieldControl"/> plus the JSON conversion.</summary>
public class ScalarFieldEditor : ISchemaFieldEditor
{
private readonly SchemaFieldControl control;
/// <summary>Adds the row for <paramref name="field"/> to <paramref name="parent"/>.</summary>
public ScalarFieldEditor(Container parent, SchemaFieldDefinition field, Action changed)
{
control = new SchemaFieldControl(parent, field, changed);
}
/// <inheritdoc />
public SchemaFieldDefinition Field => control.Field;
/// <inheritdoc />
public void Load(JsonNode node) => control.Value = SchemaJsonValues.ToText(node);
/// <inheritdoc />
public JsonNode Save() => SchemaJsonValues.ToNode(control.Field, control.Value);
}
}
+221
View File
@@ -0,0 +1,221 @@
namespace Cthangover.Tools.Schemas
{
using Cthangover.Mods.Schemas;
using SchemaFieldDefinition = Cthangover.Mods.Schemas.FieldSchema;
using SchemaJsonValues = Cthangover.Mods.Schemas.SchemaJson;
using System;
using System.Collections.Generic;
using System.Linq;
using Cthangover.Mods.Interface.Theming;
using Cthangover.Tools.Common;
using Cthangover.Tools.EntityEditor;
using Godot;
/// <summary>
/// The form control of one scalar <see cref="SchemaFieldDefinition"/>, chosen by its type, as a
/// "label | editor" row. Everything reads and writes a text value, the way the DSL and
/// most JSON fields hold it, so the palette and the data editor window share these rows:
/// <list type="bullet">
/// <item><c>bool</c> — a check box ("1" or empty);</item>
/// <item><c>choice</c>, <c>ref</c>, <c>who</c> — a drop-down of the options, of the entities of
/// the kind (by name), of player/party/characters;</item>
/// <item><c>ref_list</c> — a comma list with a drop-down that appends an entity;</item>
/// <item><c>picture</c> — a path with the picture grid of the editors;</item>
/// <item><c>json</c> — a multiline source editor with syntax highlighting;</item>
/// <item>anything else — a text box (monospace for formulas and conditions).</item>
/// </list>
/// A current value the options do not know is kept and shown first, so opening and
/// saving never loses data the schema did not foresee.
/// </summary>
public class SchemaFieldControl
{
private readonly Action changed;
private readonly LineEdit text;
private readonly CodeEdit json;
private readonly OptionButton picker;
private readonly CheckBox check;
private readonly List<string> pickerValues = new();
private bool suppress;
/// <summary>Adds the row for <paramref name="field"/> to <paramref name="parent"/>; <paramref name="changed"/> runs on every edit.</summary>
public SchemaFieldControl(Container parent, SchemaFieldDefinition field, Action changed)
{
Field = field;
this.changed = changed;
var label = string.IsNullOrEmpty(field.Name) ? field.Key : field.Name;
var tip = string.IsNullOrEmpty(field.Description) ? null : field.Description;
switch (Type)
{
case "bool":
check = new CheckBox();
check.Toggled += OnToggled;
Editor = ToolUi.Field(parent, label, check, tip);
break;
case "choice":
case "ref":
case "who":
picker = new OptionButton { Name = "Picker_" + field.Key };
picker.ItemSelected += OnPicked;
Editor = ToolUi.Field(parent, label, picker, tip);
break;
case "ref_list":
case "picture":
var row = new HBoxContainer();
text = ToolUi.Text(field.Default ?? string.Empty, OnTyped);
text.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
row.AddChild(text);
if (Type == "picture")
{
var pick = UiKit.IconButton("…", ToolUi.Tr("tools/entity_editor/picture_pick_tip"));
pick.Pressed += () => OpenPicture(row);
row.AddChild(pick);
}
else
{
picker = new OptionButton { TooltipText = ToolUi.Tr("tools/schema/add_ref") };
picker.ItemSelected += OnAppend;
row.AddChild(picker);
}
Editor = ToolUi.Field(parent, label, row, tip);
break;
case "json":
json = ToolUi.Code();
json.Name = "Text_" + field.Key;
json.Text = field.Default ?? string.Empty;
json.CustomMinimumSize = new Vector2(0, 90);
json.SizeFlagsVertical = Control.SizeFlags.Fill;
json.TextChanged += OnJsonChanged;
Editor = ToolUi.Field(parent, label, json, tip);
break;
default:
text = ToolUi.Text(field.Default ?? string.Empty, OnTyped);
text.Name = "Text_" + field.Key;
if (Type is "formula" or "condition")
text.AddThemeFontOverride("font", ToolUi.Mono());
Editor = ToolUi.Field(parent, label, text, tip);
break;
}
FillPicker(null);
}
/// <summary>The described field.</summary>
public SchemaFieldDefinition Field { get; }
/// <summary>The editor control (the row's right part).</summary>
public Control Editor { get; }
/// <summary>The field type in lowercase (<c>text</c> when the schema names none).</summary>
public string Type => (Field.Type ?? "text").ToLowerInvariant();
/// <summary>The current value as text; empty when not given.</summary>
public string Value
{
get
{
if (check != null)
return check.ButtonPressed ? "1" : string.Empty;
if (text != null)
return text.Text.Trim();
if (json != null)
return json.Text.Trim();
return picker != null && picker.Selected >= 0 && picker.Selected < pickerValues.Count ? pickerValues[picker.Selected] : string.Empty;
}
set
{
suppress = true;
if (check != null)
check.ButtonPressed = value is "1" or "true" or "True" or "yes";
else if (text != null)
text.Text = value ?? string.Empty;
else if (json != null)
json.Text = value ?? string.Empty;
else
FillPicker(value ?? string.Empty);
suppress = false;
}
}
private void FillPicker(string current)
{
if (picker == null)
return;
picker.Clear();
pickerValues.Clear();
var appendOnly = Type == "ref_list";
Add(string.Empty, appendOnly ? ToolUi.Tr("tools/schema/add_ref") : "—");
if (!appendOnly && !string.IsNullOrEmpty(current) && !Options().Contains(current, StringComparer.OrdinalIgnoreCase))
Add(current, current);
foreach (var option in Options())
Add(option, Type == "choice" ? option : GameEntityIndex.Label(KindOfOptions, option));
var index = appendOnly ? 0 : Math.Max(0, pickerValues.FindIndex(v => string.Equals(v, current, StringComparison.OrdinalIgnoreCase)));
picker.Select(index);
}
private void Add(string value, string label)
{
pickerValues.Add(value);
picker.AddItem(label);
}
private string KindOfOptions => Type == "who" ? "character" : Field.Kind;
private IEnumerable<string> Options()
{
switch (Type)
{
case "choice":
return Field.Options ?? new List<string>();
case "who":
return new[] { "player", "party" }.Concat(GameEntityIndex.Of("character").Keys);
default:
return GameEntityIndex.Of(Field.Kind).Keys;
}
}
private void OnJsonChanged() => OnTyped(json.Text);
private void OnTyped(string value)
{
if (!suppress)
changed?.Invoke();
}
private void OnToggled(bool on)
{
if (!suppress)
changed?.Invoke();
}
private void OnPicked(long index)
{
if (!suppress)
changed?.Invoke();
}
private void OnAppend(long index)
{
if (suppress || index <= 0 || index >= pickerValues.Count)
return;
var items = text.Text.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList();
if (!items.Contains(pickerValues[(int)index]))
items.Add(pickerValues[(int)index]);
text.Text = string.Join(",", items);
picker.Select(0);
changed?.Invoke();
}
private void OpenPicture(Node host)
{
var dialog = new PicturePickerDialog(Field.Group ?? "items", text.Text);
dialog.Picked += OnPicturePicked;
host.AddChild(dialog);
dialog.PopupCentered();
}
private void OnPicturePicked(string id)
{
text.Text = id;
changed?.Invoke();
}
}
}
+34
View File
@@ -0,0 +1,34 @@
namespace Cthangover.Tools.Schemas
{
using System;
using Cthangover.Mods.Schemas;
using SchemaFieldDefinition = Cthangover.Mods.Schemas.FieldSchema;
using SchemaJsonValues = Cthangover.Mods.Schemas.SchemaJson;
using Godot;
/// <summary>
/// Picks the editor of a schema field by its type: a card list for <c>list</c> and
/// <c>values</c>, a key/value table for <c>map</c>, texts per language for <c>locale</c>,
/// a single control for everything else. Shared by the data editors (their "fields of
/// other mods" block) and the Data Editor window, so a field looks the same in both.
/// </summary>
public static class SchemaFieldEditors
{
/// <summary>Adds the editor of <paramref name="field"/> to <paramref name="parent"/>; <paramref name="changed"/> runs on every edit.</summary>
public static ISchemaFieldEditor Create(Container parent, SchemaFieldDefinition field, Action changed)
{
switch (field.Type?.ToLowerInvariant())
{
case "list":
case "values":
return new ListFieldEditor(parent, field, changed);
case "map":
return new MapFieldEditor(parent, field, changed);
case "locale":
return new LocaleFieldEditor(parent, field, changed);
default:
return new ScalarFieldEditor(parent, field, changed);
}
}
}
}
+72
View File
@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Cthangover.Core.Localization;
using Godot;
namespace Cthangover.Tools.Services
{
/// <summary>
/// Texts of one mod's <c>locale/*.properties</c> for the editors that show a
/// localized name instead of its key. A language's files are
/// <c>&lt;lang&gt;.properties</c> and <c>&lt;anything&gt;_&lt;lang&gt;.properties</c>,
/// as <c>LocaleLoader</c> reads them.
/// <para/>
/// A write goes into the file of the language that already declares the key;
/// a new key goes into <c>&lt;lang&gt;.properties</c>, else the first non-scenario
/// file of the language, else a new <c>locale/&lt;lang&gt;.properties</c>.
/// Scenario string files (<c>scenarios_*</c>) never get entity texts.
/// </summary>
public static class LocaleFileService
{
private const string Dir = "locale";
/// <summary>Languages the mod has texts for, plus the game's current one; the current one first.</summary>
public static List<string> Languages(string modId)
{
var current = LanguageCode.Normalize(TranslationServer.GetLocale()) ?? "ru";
var languages = new List<string> { current };
foreach (var file in ModFileService.ListFiles(modId, Dir, ".properties"))
{
var language = LanguageOf(file);
if (language != null && !languages.Contains(language))
languages.Add(language);
}
return languages;
}
/// <summary>The mod's text of <paramref name="key"/> in <paramref name="language"/>, or null.</summary>
public static string Read(string modId, string language, string key)
{
string found = null;
foreach (var file in FilesOf(modId, language))
found = PropertiesText.Get(ModFileService.ReadText(modId, file), key) ?? found;
return found;
}
/// <summary>Sets the text of <paramref name="key"/> in <paramref name="language"/>; false when the mod is not writable.</summary>
public static bool Write(string modId, string language, string key, string value)
{
var files = FilesOf(modId, language);
var target = files.FirstOrDefault(f => PropertiesText.Keys(ModFileService.ReadText(modId, f)).Contains(key))
?? files.FirstOrDefault(f => Path.GetFileName(f) == language + ".properties")
?? files.FirstOrDefault(f => !Path.GetFileName(f).StartsWith("scenarios_", StringComparison.Ordinal))
?? $"{Dir}/{language}.properties";
return ModFileService.WriteText(modId, target, PropertiesText.Set(ModFileService.ReadText(modId, target), key, value));
}
/// <summary>Reloads the game's texts of the current language, so a saved name shows at once.</summary>
public static void ReloadGame() => LocaleLoader.LoadLocale(TranslationServer.GetLocale());
private static List<string> FilesOf(string modId, string language)
=> ModFileService.ListFiles(modId, Dir, ".properties").Where(f => LanguageOf(f) == language && Path.GetDirectoryName(f)?.Replace('\\', '/') == Dir).ToList();
private static string LanguageOf(string file)
{
var name = Path.GetFileNameWithoutExtension(file);
var language = name.Substring(name.LastIndexOf('_') + 1);
return LanguageCode.Supported.Contains(language) ? language : null;
}
}
}
+37
View File
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Cthangover.Core.Factories.Impls;
using Cthangover.Core.Mods;
using Cthangover.Core.Mods.Resolvers;
using Cthangover.Core.Utils;
using Godot;
namespace Cthangover.Tools.Services
@@ -121,6 +123,41 @@ namespace Cthangover.Tools.Services
return result;
}
/// <summary>
/// Pictures of a file group (<c>items</c>, <c>characters</c>, <c>charsets</c>…):
/// group-relative paths without extension, sorted — what an entity's picture
/// field holds (<c>food/ration</c>, <c>village/slime</c>).
/// </summary>
public static List<string> GetGroupImageIds(string group)
{
var ids = new List<string>();
var files = Files.Collect(group);
if (files == null)
return ids;
var exts = GetExt();
foreach (var key in files.Keys)
{
if (exts.Contains(Path.GetExtension(key).ToLower()))
ids.Add(Path.ChangeExtension(key, null));
}
ids.Sort(StringComparer.OrdinalIgnoreCase);
return ids;
}
/// <summary>
/// A picture of a file group by the id <see cref="GetGroupImageIds"/> lists
/// (a <c>group/</c> prefix is allowed, as data files often write it); null
/// without an error log when the group has no such picture, so a form can
/// show a half-typed path.
/// </summary>
public static Texture2D LoadGroupImage(string group, string id)
{
if (string.IsNullOrWhiteSpace(id))
return null;
var path = id.StartsWith(group + "/", StringComparison.OrdinalIgnoreCase) ? id.Substring(group.Length + 1) : id;
return GetGroupImageIds(group).Contains(path, StringComparer.OrdinalIgnoreCase) ? TextureUtils.LoadFromModGroup(group, path) : null;
}
/// <summary>
/// Loads a background texture by ID via <see cref="BackgroundFactory.Instance.Get"/>.
/// Returns <c>null</c> on any error (missing resource, load failure).
+78
View File
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
namespace Cthangover.Tools.Services
{
/// <summary>
/// Reading and changing one key of a <c>.properties</c> text the way
/// <c>LocaleLoader</c> reads it: <c>key = value</c> per line, <c>#</c> comments,
/// no escapes and no multi-line values (a line break in a value becomes a
/// space). Everything else in the text — comments, order, other keys, the
/// line endings — stays byte for byte, so an editor save shows up in a diff
/// as the one changed line.
/// </summary>
public static class PropertiesText
{
/// <summary>The value of <paramref name="key"/>, or null when the text does not declare it (the last line wins, as in the loader).</summary>
public static string Get(string text, string key)
{
string found = null;
foreach (var line in Lines(text))
{
if (KeyOf(line) == key)
found = line.Substring(line.IndexOf('=') + 1).Trim();
}
return found;
}
/// <summary>All keys declared in the text.</summary>
public static HashSet<string> Keys(string text)
{
var keys = new HashSet<string>(StringComparer.Ordinal);
foreach (var line in Lines(text))
{
var key = KeyOf(line);
if (key != null)
keys.Add(key);
}
return keys;
}
/// <summary>
/// The text with <paramref name="key"/> set to <paramref name="value"/>: every
/// line of the key is rewritten in place, or a <c>key = value</c> line is
/// appended (after a line break the text may lack). Line endings follow the text.
/// </summary>
public static string Set(string text, string key, string value)
{
text ??= string.Empty;
value = (value ?? string.Empty).Replace("\r", " ").Replace("\n", " ").Trim();
var newline = text.Contains("\r\n") ? "\r\n" : "\n";
var lines = text.Split('\n');
var replaced = false;
for (var i = 0; i < lines.Length; i++)
{
var hasCr = lines[i].EndsWith("\r");
if (KeyOf(hasCr ? lines[i][..^1] : lines[i]) != key)
continue;
lines[i] = $"{key} = {value}" + (hasCr ? "\r" : string.Empty);
replaced = true;
}
if (replaced)
return string.Join("\n", lines);
var head = text.Length == 0 || text.EndsWith("\n") ? text : text + newline;
return head + $"{key} = {value}" + newline;
}
private static IEnumerable<string> Lines(string text) => (text ?? string.Empty).Replace("\r\n", "\n").Split('\n');
private static string KeyOf(string rawLine)
{
var line = rawLine.Trim();
if (line.Length == 0 || line[0] == '#')
return null;
var eq = line.IndexOf('=');
return eq <= 0 ? null : line.Substring(0, eq).Trim();
}
}
}
+3 -26
View File
@@ -3,6 +3,7 @@ using Cthangover.Mods.Dialog.Scenarios;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Factories.Impls;
using Cthangover.Core.UI.Theming;
using Cthangover.Mods.Interface.Theming;
using Godot;
namespace Cthangover.Tools.Services
@@ -20,36 +21,12 @@ namespace Cthangover.Tools.Services
{
/// <summary>
/// Builds a <see cref="CodeHighlighter"/> for scenario DSL syntax.
/// Comments (lines starting with <c>#</c>) are green, strings are bright
/// Comments (lines starting with <c>#</c>) are faint, strings are bright
/// gold, command names are blue, and argument keywords (e.g. <c>key</c>,
/// <c>wait</c>, <c>quest_id</c>) are amber — all from <see cref="UiTokens"/>.
/// </summary>
public static CodeHighlighter CreateHighlighter()
{
var hl = new CodeHighlighter();
hl.AddColorRegion("#", null, UiTokens.Success, true);
hl.AddColorRegion("\"", "\"", UiTokens.AccentBright, false);
hl.NumberColor = UiTokens.Info;
hl.SymbolColor = UiTokens.Muted;
var cmdColor = UiTokens.Info;
hl.KeywordColors = new Godot.Collections.Dictionary();
foreach (var cmd in ScenarioCommandStrategyFactory.GetAllCommandNames())
hl.KeywordColors[cmd] = cmdColor;
foreach (var cmd in new[] {
"option", "select"
}) hl.KeywordColors[cmd] = cmdColor;
var argColor = UiTokens.Accent;
foreach (var kw in new[] {
"key", "first", "second", "hide_color", "wait", "speed", "hidden",
"sprites", "loop", "target", "quest_id", "show", "hide", "duration"
}) hl.KeywordColors[kw] = argColor;
return hl;
}
=> SourceSyntax.CreateScenarioHighlighter(ScenarioCommandStrategyFactory.GetAllCommandNames());
/// <summary>
/// Splits a line into tokens, handling quoted strings (with backslash
@@ -25,7 +25,7 @@ namespace Cthangover.Tools.Tests.InGamePlay
driverQueued = true;
GameLogger.Log("TEST", "InGamePlayTestInitializer: activating in-game play-test test");
(Godot.Engine.GetMainLoop() as SceneTree)?.Root?.CallDeferred("add_child", new InGamePlayTestDriverNode());
ModRoot.AddDeferred(new InGamePlayTestDriverNode());
}
}
}
@@ -31,7 +31,7 @@ namespace Cthangover.Tools.Tests.NovelFlow
.Select(a => a.Substring("--shots=".Length))
.FirstOrDefault();
GameLogger.Log("TEST", $"NovelFlowTestInitializer: activating novel_flow, shots={shots ?? "off"}");
(Godot.Engine.GetMainLoop() as SceneTree)?.Root?.CallDeferred("add_child", new NovelFlowTestDriver(shots));
ModRoot.AddDeferred(new NovelFlowTestDriver(shots));
}
}
}
@@ -1,105 +0,0 @@
using System;
using System.Linq;
using Cthangover.Core.Scenes;
using Cthangover.Core.UI.Tool.ScenarioEditor;
using Cthangover.Mods.Interface.Tool;
using Cthangover.Tools.Common;
using Cthangover.Tools.Hub;
using Cthangover.Tools.Services;
using Godot;
namespace Cthangover.Tools.Tests.Windows
{
/// <summary>
/// Play-test round trip of the scenario editor opened from the
/// <see cref="NovelEditorHub"/>: plays this suite's own scenario (so the
/// editor's save-before-play rewrites a test file, not game content), lets
/// the play end the way a finished dialog ends it
/// (<see cref="SceneManager.IsTestPlayActive"/> drops), then plays again and
/// presses Stop. Both times the game must come back to the hub with the
/// editor visible — not to the engine's main menu. Advanced one step per
/// driver tick; <see cref="Tick"/> returns true when done.
/// </summary>
public class ScenarioPlayReturnCheck
{
private const string SuiteModId = "editor_tools__windows";
private const string ScenarioPath = "scenarios/playtest_return.scenario";
private const int ShellSettleTicks = 3;
private readonly Action<bool, string> _check;
private ScenarioEditorWindow _window;
private int _step;
private int _settle;
/// <summary>Creates the check; <paramref name="check"/> records one assertion.</summary>
public ScenarioPlayReturnCheck(Action<bool, string> check)
{
_check = check;
}
/// <summary>Advances the round trip by one step; true once finished.</summary>
public bool Tick(SceneTree tree)
{
switch (_step)
{
case 0:
_window = (ScenarioEditorWindow)ToolWindow.ShowWindow(new ScenarioEditorWindow());
var tabs = _window.FindChildren("*", "", true, false).OfType<ScenarioEditTabs>().FirstOrDefault();
tabs?.OpenFile(ScenarioFileService.MakeKey(SuiteModId, ScenarioPath));
_check(tabs?.HasActiveFile == true, "scenario_editor: test scenario opened");
break;
case 1:
case 4:
PressButton(_window, "tools/scenario_editor/play");
_settle = ShellSettleTicks;
break;
case 2:
if (!InGame(tree))
return false;
SceneManager.IsTestPlayActive = false;
break;
case 5:
if (!InGame(tree))
return false;
PressButton(tree.Root, "tools/scenario_editor/stop");
break;
case 3:
case 6:
if (tree.CurrentScene is not NovelEditorHub || !_window.Visible)
return false;
_check(true, _step == 3
? "scenario_editor: the scenario's end returns to the editor hub"
: "scenario_editor: Stop returns to the editor hub");
if (_step == 6)
{
_window.QueueFree();
return true;
}
break;
}
_step++;
return false;
}
/// <summary>The play-test left the hub for the game shell and the shell settled.</summary>
private bool InGame(SceneTree tree)
{
if (tree.CurrentScene is NovelEditorHub || GodotSceneService.IsTransitioning || --_settle > 0)
return false;
_check(!_window.Visible, "scenario_editor: play hides the editor and enters the game");
return true;
}
private static void PressButton(Node root, string textKey)
{
var text = ToolUi.Tr(textKey);
var button = root.FindChildren("*", nameof(Button), true, false).OfType<Button>().FirstOrDefault(b => b.Text == text);
button?.EmitSignal(BaseButton.SignalName.Pressed);
}
}
}
@@ -25,6 +25,8 @@ namespace Cthangover.Tools.Tests.Windows
/// <summary>Runs the checks for <paramref name="toolId"/>; <paramref name="check"/> records each result.</summary>
public static void Run(string toolId, Window window, Action<bool, string> check)
{
foreach (var editor in window.FindChildren("*", "", true, false).OfType<CodeEdit>())
check(editor.SyntaxHighlighter != null, toolId + ": source field has syntax highlighting");
switch (toolId)
{
case "map_editor":
@@ -1,12 +1,9 @@
using System.Collections.Generic;
using System.IO;
using Cthangover.Core.Bootstrap;
using Cthangover.Core.Games;
using Cthangover.Core.UI.Tool;
using Cthangover.Core.Utils;
using Cthangover.Mods.Interface.Menu;
using Cthangover.Mods.Interface.Tool;
using Cthangover.Tools.Hub;
using Cthangover.Mods.Interface.View;
using Godot;
@@ -20,13 +17,6 @@ namespace Cthangover.Tools.Tests.Windows
/// closes it. A window that throws while constructing or laying out fails
/// only its own check, the others still run. Ends with the runner contract
/// <c>[TEST_RESULT] name=tools_windows passed=N failed=M</c> and the exit code.
/// <para/>
/// Launched as the <c>novel_editor</c> game (<c>--game=novel_editor --new</c>)
/// it first waits for the collection's entry scene, the
/// <see cref="NovelEditorHub"/>, and checks it lists a launcher button for
/// every registered tool before opening the windows; after the windows it
/// plays a scenario from the editor and checks that both the scenario's
/// end and Stop return to the hub (<see cref="ScenarioPlayReturnCheck"/>).
/// </summary>
public partial class ToolWindowsTestDriverNode : Node
{
@@ -45,8 +35,6 @@ namespace Cthangover.Tools.Tests.Windows
private double _elapsed;
private bool _finished;
private Window _window;
private bool _inHub;
private ScenarioPlayReturnCheck _playCheck;
private readonly ToolWindowsErrorLogger _errors = new();
/// <summary>Creates the driver; <paramref name="shotsDir"/> null or empty disables screenshots.</summary>
@@ -78,25 +66,12 @@ namespace Cthangover.Tools.Tests.Windows
if (!Boot.IsReady || tree?.CurrentScene == null)
return;
_inHub = ActiveGame.Id == NovelEditorHub.SceneName;
if (_inHub && tree.CurrentScene is not NovelEditorHub)
return;
_tools.AddRange(ToolFactory.Instance.GetAll());
Check(_tools.Count > 0, $"tools registered ({_tools.Count})");
if (_inHub)
CheckHub(tree.CurrentScene);
OpenNext();
return;
}
if (_playCheck != null)
{
if (_playCheck.Tick(GetTree()))
Finish();
return;
}
if (_window == null)
return;
@@ -124,10 +99,7 @@ namespace Cthangover.Tools.Tests.Windows
_toolIndex++;
if (_toolIndex >= _tools.Count)
{
if (_inHub)
_playCheck = new ScenarioPlayReturnCheck(Check);
else
Finish();
Finish();
return;
}
@@ -146,24 +118,6 @@ namespace Cthangover.Tools.Tests.Windows
}
}
/// <summary>Every registered tool has its launcher button on the hub screen.</summary>
private void CheckHub(Node hub)
{
var keys = new HashSet<string>();
CollectButtonKeys(hub, keys);
foreach (var tool in _tools)
Check(keys.Contains(tool.LocaleKey), $"hub: launcher button for {tool.Id}");
SaveShot("novel_editor_hub");
}
private static void CollectButtonKeys(Node node, HashSet<string> keys)
{
if (node is Button button && button.HasMeta(MenuBuilder.LocaleMetaKey))
keys.Add(button.GetMeta(MenuBuilder.LocaleMetaKey).AsString());
foreach (var child in node.GetChildren())
CollectButtonKeys(child, keys);
}
private void InspectAndClose()
{
var id = _tools[_toolIndex].Id;
@@ -37,7 +37,7 @@ namespace Cthangover.Tools.Tests.Windows
driverQueued = true;
GameLogger.Log("TEST", $"ToolWindowsTestInitializer: activating tool windows test, shots='{shots ?? ""}'");
(Godot.Engine.GetMainLoop() as SceneTree)?.Root?.CallDeferred("add_child", new ToolWindowsTestDriverNode(shots));
ModRoot.AddDeferred(new ToolWindowsTestDriverNode(shots));
}
}
}
+119
View File
@@ -0,0 +1,119 @@
namespace Cthangover.Tools.EntityEditor.Tests
{
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Nodes;
using Cthangover.Core.Mods.Testing;
/// <summary>The data editors' document: files of one kind, add/copy/delete, texts, undo and what gets written.</summary>
public class EntityDocumentTests
{
private const string Items = "{\n \"Kind\": \"item\",\n \"Items\": [\n {\n \"Id\": \"food/bread\",\n \"Name\": \"items/food/bread/name\",\n \"Cost\": 2,\n \"heal\": 5\n }\n ]\n}\n";
private static EntityDocument Load(out List<string> skipped) => EntityDocument.Load("item", "items/my_mod.json", new[]
{
("items/food/bread.json", Items),
("characters/hero.json", "{\"Kind\": \"character\", \"Items\": [{\"Id\": \"hero\"}]}"),
("items/broken.json", "{\"Kind\": \"item\", \"Items\": [ {"),
("scenes/town.json", "{\"Name\": \"town\"}")
}, out skipped);
[ModTest("entity_document/load_takes_only_its_kind")]
static void LoadTakesOnlyItsKind(ModTestContext ctx)
{
var document = Load(out var skipped);
ModAssert.Equal(1, document.Records.Count, "only the item file");
ModAssert.Equal("food/bread", document.Records[0].Id, "its entity");
ModAssert.Equal("items/food/bread.json", document.Records[0].File, "its file");
ModAssert.Equal(1, skipped.Count, "the broken file of this kind is reported");
ModAssert.Equal("items/broken.json", skipped[0], "by path");
ModAssert.Equal(0, document.ChangedFiles().Count, "nothing to write after loading");
}
[ModTest("entity_document/unknown_fields_survive")]
static void UnknownFieldsSurvive(ModTestContext ctx)
{
var document = Load(out _);
document.Records[0].Data["Cost"] = 3;
var changed = document.ChangedFiles();
ModAssert.Equal(1, changed.Count, "the changed file");
var root = JsonNode.Parse(changed[0].Text).AsObject();
var item = root["Items"][0].AsObject();
ModAssert.Equal("3", item["Cost"].ToString(), "the edit");
ModAssert.Equal("5", item["heal"].ToString(), "another mod's field is kept");
ModAssert.Equal("item", root["Kind"].ToString(), "the kind is kept");
ModAssert.True(changed[0].Text.EndsWith("}\n"), "LF line break at the end");
ModAssert.Equal(Items.Replace("\"Cost\": 2", "\"Cost\": 3"), changed[0].Text, "the rest of the file is byte for byte the same");
}
[ModTest("entity_document/add_copy_delete")]
static void AddCopyDelete(ModTestContext ctx)
{
var document = Load(out _);
var added = document.Add(new JsonObject { ["Id"] = document.FreeId("food/bread") });
ModAssert.Equal("food/bread_2", added.Id, "a taken id gets a number");
ModAssert.Equal("items/my_mod.json", added.File, "new entries go to the new file");
var copy = document.Duplicate(document.Find("food/bread"), document.FreeId("food/bread"));
ModAssert.Equal("food/bread_3", copy.Id, "the copy gets a free id");
ModAssert.Equal("items/food/bread.json", copy.File, "and stays in the original's file");
ModAssert.Equal("5", copy.Data["heal"].ToString(), "with all fields");
ModAssert.Equal("food/bread", document.Records[0].Id, "the original first");
ModAssert.Equal("food/bread_3", document.Records[1].Id, "the copy right after it");
document.Remove(document.Find("food/bread"));
ModAssert.Null(document.Find("food/bread"), "removed");
var files = document.ChangedFiles().Select(f => f.Path).OrderBy(p => p).ToList();
ModAssert.Equal(2, files.Count, "both files are written");
ModAssert.Equal("items/food/bread.json", files[0], "the changed one");
ModAssert.Equal("items/my_mod.json", files[1], "the new one");
ModAssert.True(document.ChangedFiles().First(f => f.Path == "items/my_mod.json").Text.Contains("\"Kind\": \"item\""), "a new file declares the kind");
}
[ModTest("entity_document/undo_restores_data_and_texts")]
static void UndoRestoresDataAndTexts(ModTestContext ctx)
{
var document = Load(out _);
document.LoadText("ru", "items/food/bread/name", "Хлеб");
document.BeginChange();
document.Records[0].Data["Cost"] = 9;
document.SetText("ru", "items/food/bread/name", "Каравай");
document.BeginChange();
document.Add(new JsonObject { ["Id"] = "food/cake" });
ModAssert.True(document.Undo(), "undo the add");
ModAssert.Null(document.Find("food/cake"), "the new entry is gone");
ModAssert.True(document.Undo(), "undo the edit");
ModAssert.Equal("2", document.Find("food/bread").Data["Cost"].ToString(), "the cost is back");
ModAssert.Equal("Хлеб", document.GetText("ru", "items/food/bread/name"), "the text is back");
ModAssert.False(document.Undo(), "nothing more");
ModAssert.Equal(0, document.ChangedFiles().Count + document.ChangedTexts().Count, "back to what is on disk");
ModAssert.True(document.Redo(), "redo the edit");
var texts = document.ChangedTexts();
ModAssert.Equal(1, texts.Count, "one changed text");
ModAssert.Equal("ru", texts[0].Language, "its language");
ModAssert.Equal("items/food/bread/name", texts[0].Key, "its key");
ModAssert.Equal("Каравай", texts[0].Value, "its value");
document.MarkSaved();
ModAssert.Equal(0, document.ChangedFiles().Count + document.ChangedTexts().Count, "nothing left after saving");
}
[ModTest("entity_document/validate_reports_ids")]
static void ValidateReportsIds(ModTestContext ctx)
{
var document = Load(out _);
ModAssert.Equal(0, document.Validate().Count, "a loaded mod is valid");
document.Add(new JsonObject { ["Id"] = "FOOD/BREAD" });
document.Add(new JsonObject { ["Name"] = "x" });
var problems = document.Validate();
ModAssert.Equal(2, problems.Count, "an empty id and a duplicate");
ModAssert.Equal("tools/entity_editor/problem_id", problems[0].Key, "empty id first");
ModAssert.Equal("tools/entity_editor/problem_duplicate", problems[1].Key, "a duplicate ignores case");
}
}
}
+32
View File
@@ -0,0 +1,32 @@
namespace Cthangover.Tools.Services.Tests
{
using Cthangover.Core.Mods.Testing;
/// <summary>Changing one key of a .properties text without touching the rest.</summary>
public class PropertiesTextTests
{
private const string Text = "# items\nitems/bread/name = Хлеб\nitems/bread/desc=Свежий\n\nitems/cake/name = Торт\n";
[ModTest("properties_text/get_reads_like_the_loader")]
static void GetReadsLikeTheLoader(ModTestContext ctx)
{
ModAssert.Equal("Хлеб", PropertiesText.Get(Text, "items/bread/name"), "spaces around '=' are trimmed");
ModAssert.Equal("Свежий", PropertiesText.Get(Text, "items/bread/desc"), "no spaces work too");
ModAssert.Null(PropertiesText.Get(Text, "items/pie/name"), "a missing key");
ModAssert.Null(PropertiesText.Get(null, "x"), "no text");
ModAssert.Equal("b", PropertiesText.Get("x = a\nx = b\n", "x"), "the last line wins");
ModAssert.Equal(3, PropertiesText.Keys(Text).Count, "three keys, the comment is not one");
}
[ModTest("properties_text/set_changes_one_line")]
static void SetChangesOneLine(ModTestContext ctx)
{
ModAssert.Equal(Text.Replace("= Хлеб", "= Булка"), PropertiesText.Set(Text, "items/bread/name", "Булка"), "only that line changes");
ModAssert.Equal(Text + "items/pie/name = Пирог\n", PropertiesText.Set(Text, "items/pie/name", "Пирог"), "a new key is appended");
ModAssert.Equal("a = 1\nb = 2\n", PropertiesText.Set("a = 1", "b", "2"), "a missing final line break is added first");
ModAssert.Equal("a = 1\r\nb = 2\r\n", PropertiesText.Set("a = 1\r\n", "b", "2"), "CRLF text stays CRLF");
ModAssert.Equal("a = x\r\n", PropertiesText.Set("a = 1\r\n", "a", "x"), "a CRLF line is rewritten with its CR");
ModAssert.Equal("a = one two\n", PropertiesText.Set("", "a", "one\ntwo"), "a line break in a value becomes a space");
}
}
}
+42
View File
@@ -0,0 +1,42 @@
namespace Cthangover.Tools.Tests
{
using System.Text.Json;
using System.Text.Json.Nodes;
using Cthangover.Core.Mods.Testing;
using Cthangover.Mods.Schemas;
using Cthangover.Tools.Schemas;
using Godot;
/// <summary>JSON schema fields preserve structured values through their highlighted multiline editor.</summary>
public class SchemaFieldControlTests
{
[ModTest("schema/json_field_roundtrip")]
static void JsonFieldRoundtrip(ModTestContext ctx)
{
var parent = new VBoxContainer();
((SceneTree)Engine.GetMainLoop()).Root.AddChild(parent);
try
{
var field = new SchemaFieldControl(parent, new FieldSchema { Key = "Extra", Type = "json" }, null);
ModAssert.True(field.Editor is CodeEdit, "JSON field has a multiline code editor");
var code = (CodeEdit)field.Editor;
ModAssert.True(code.SyntaxHighlighter != null, "JSON field has syntax highlighting");
var original = new JsonObject
{
["text"] = "Unicode text: \u041f\u0440\u0438\u0432\u0435\u0442",
["active"] = true,
["nested"] = new JsonArray(1, null, 3),
};
field.Value = JsonSerializer.Serialize(original, new JsonSerializerOptions { WriteIndented = true });
var restored = JsonNode.Parse(field.Value);
ModAssert.Equal(original["text"].GetValue<string>(), restored["text"].GetValue<string>(), "text survives the field");
ModAssert.True(restored["active"].GetValue<bool>(), "boolean survives the field");
ModAssert.Equal(3, restored["nested"].AsArray().Count, "array survives the field");
ModAssert.True(restored["nested"][1] == null, "null survives the field");
code.Text = "{\n \"number\": 42\n}";
ModAssert.Equal(42, JsonNode.Parse(field.Value)["number"].GetValue<int>(), "edited JSON is returned by the form");
}
finally { parent.Free(); }
}
}
}
+44
View File
@@ -0,0 +1,44 @@
namespace Cthangover.Tools.Tests
{
using Cthangover.Core.Mods.Testing;
using Cthangover.Core.UI.Theming;
using Godot;
/// <summary>A checked box or toggle under the mouse keeps the margins of its other states, so its label never jumps.</summary>
public class ThemeToggleStatesTests
{
private static readonly string[] States = { "normal", "hover", "pressed", "hover_pressed", "disabled" };
[ModTest("theme/toggle_states_keep_their_margins")]
static void ToggleStatesKeepTheirMargins(ModTestContext ctx)
{
// In the tree: styles are looked up through the theme owner only there.
var theme = CthangoverTheme.Build();
var root = ((SceneTree)Engine.GetMainLoop()).Root;
var controls = new (string What, Button Control)[]
{
("CheckBox", new CheckBox()),
("toggle Button", new Button { ToggleMode = true }),
("tab", new Button { ToggleMode = true, ThemeTypeVariation = CthangoverTheme.TabVariation }),
("primary", new Button { ToggleMode = true, ThemeTypeVariation = CthangoverTheme.PrimaryVariation }),
("danger", new Button { ToggleMode = true, ThemeTypeVariation = CthangoverTheme.DangerVariation }),
("icon", new Button { ToggleMode = true, ThemeTypeVariation = CthangoverTheme.IconVariation }),
("ribbon", new Button { ToggleMode = true, ThemeTypeVariation = CthangoverTheme.RibbonVariation }),
("OptionButton", new OptionButton()),
};
foreach (var (what, control) in controls)
{
control.Theme = theme;
root.AddChild(control);
var normal = control.GetThemeStylebox("normal");
foreach (var state in States)
{
var box = control.GetThemeStylebox(state);
ModAssert.CloseTo(normal.ContentMarginLeft, box.ContentMarginLeft, what: $"{what} {state}: left margin");
ModAssert.CloseTo(normal.ContentMarginRight, box.ContentMarginRight, what: $"{what} {state}: right margin");
}
control.Free();
}
}
}
}