From 36f27038d55ffc04c62e9e1224660eb081413a77 Mon Sep 17 00:00:00 2001 From: xiaoxustudio Date: Sun, 5 Jul 2026 01:19:15 +0800 Subject: [PATCH 01/13] fix: trash binary files is missing --- .../AppSettings/AppSettingsDialog.tsx | 11 ++++++++ .../origine2/src/components/Assets/Assets.tsx | 2 +- .../src/pages/dashboard/GameElement.tsx | 2 +- .../src/pages/dashboard/TemplateElement.tsx | 2 +- .../Modules/webgal-fs/webgal-fs.service.ts | 27 ++++++++++++++++++- release-mac-x64.sh | 4 +++ release-mac.sh | 5 ++++ release.sh | 3 +++ 8 files changed, 52 insertions(+), 4 deletions(-) diff --git a/packages/origine2/src/components/AppSettings/AppSettingsDialog.tsx b/packages/origine2/src/components/AppSettings/AppSettingsDialog.tsx index 7dcff680..cdbe8279 100644 --- a/packages/origine2/src/components/AppSettings/AppSettingsDialog.tsx +++ b/packages/origine2/src/components/AppSettings/AppSettingsDialog.tsx @@ -36,6 +36,8 @@ import { NavigationFilled, NavigationRegular, Settings20Regular, + ArchiveFilled, + ArchiveRegular, } from '@fluentui/react-icons'; import { Platte } from '@icon-park/react'; import { ReactNode, useEffect, useState } from 'react'; @@ -132,6 +134,8 @@ export function SettingsShortcutGrid({ onOpenSettings }: { onOpenSettings?: () = const isCascaderDelimitersCustomizable = useEditorStore.use.isCascaderDelimitersCustomizable(); const updateIsCascaderDelimitersCustomizable = useEditorStore.use.updateIsCascaderDelimitersCustomizable(); const updateCascaderDelimiters = useEditorStore.use.updateCascaderDelimiters(); + const isTrash = useEditorStore.use.isTrash(); + const updateIsTrash = useEditorStore.use.updateIsTrash(); return (
@@ -211,6 +215,13 @@ export function SettingsShortcutGrid({ onOpenSettings }: { onOpenSettings?: () = if (!next) updateCascaderDelimiters(['/']); }} /> + : } + title={t`回收站`} + description={isTrash ? t`已启用,删除操作会移动到回收站` : t`已禁用,删除操作会永久删除`} + active={isTrash} + onClick={() => updateIsTrash(!isTrash)} + />
); } diff --git a/packages/origine2/src/components/Assets/Assets.tsx b/packages/origine2/src/components/Assets/Assets.tsx index 8518b28f..fc9b9e59 100644 --- a/packages/origine2/src/components/Assets/Assets.tsx +++ b/packages/origine2/src/components/Assets/Assets.tsx @@ -133,7 +133,7 @@ export default function Assets({ const updateSortBy = useEditorStore.use.updateSortBy(); const sortOrder = useEditorStore.use.sortOrder(); const updateSortOrder = useEditorStore.use.updateSortOrder(); - const isTrash = false; + const isTrash = useEditorStore.use.isTrash(); const currentPath = useValue([...basePath, ...selectedFilePath.slice(0, -1)]); const currentFullPath = useMemo(() => [...rootPath, ...currentPath.value], [currentPath.value]); diff --git a/packages/origine2/src/pages/dashboard/GameElement.tsx b/packages/origine2/src/pages/dashboard/GameElement.tsx index db75784d..1c49d53e 100644 --- a/packages/origine2/src/pages/dashboard/GameElement.tsx +++ b/packages/origine2/src/pages/dashboard/GameElement.tsx @@ -71,7 +71,7 @@ export default function GameElement(props: IGameElementProps) { const isShowRenameDialog = useValue(false); const newGameName = useValue(props.gameInfo.dir); const deleteChecked = useValue(false); - const isTrash = false; + const isTrash = useEditorStore.use.isTrash(); const openInFileExplorer = () => { api.manageGameControllerOpenGameDict(props.gameInfo.dir); diff --git a/packages/origine2/src/pages/dashboard/TemplateElement.tsx b/packages/origine2/src/pages/dashboard/TemplateElement.tsx index 827090a0..9e45b13d 100644 --- a/packages/origine2/src/pages/dashboard/TemplateElement.tsx +++ b/packages/origine2/src/pages/dashboard/TemplateElement.tsx @@ -72,7 +72,7 @@ export default function TemplateElement(props: ITemplateElementProps) { const isShowDeleteDialog = useValue(false); const isShowRenameDialog = useValue(false); const newTemplateName = useValue(props.templateInfo.dir); - const isTrash = false; + const isTrash = useEditorStore.use.isTrash(); const openInFileExplorer = () => { api.assetsControllerOpenDict(`templates/${props.templateInfo.dir}`); diff --git a/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts b/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts index bda8aecb..242b7773 100644 --- a/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts +++ b/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts @@ -3,9 +3,13 @@ import * as fs from 'fs/promises'; import archiver = require('archiver'); import AdmZip = require('adm-zip'); import { basename, dirname, extname, isAbsolute, join } from 'path'; +import { execFile } from 'child_process'; +import { promisify } from 'util'; import { UserDataService } from '../user-data/user-data.service'; import trash from 'trash'; +const pExecFile = promisify(execFile); + export interface IFileInfo { name: string; isDir: boolean; @@ -282,7 +286,28 @@ export class WebgalFsService { } else { this.logger.log(`丢弃文件: ${path}`); } - await trash(path, { glob: false }); + + try { + const binnaryMap = { + darwin: 'macos-trash', + win32: 'windows-trash', + }; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const isPackaged = typeof process.pkg !== 'undefined'; + if (isPackaged && Object.keys(binnaryMap).includes(process.platform)) { + const windowsTrashExe = join( + dirname(process.execPath), + 'lib', + binnaryMap[process.platform], + ); + await pExecFile(windowsTrashExe, [path]); + return true; + } + await trash(path, { glob: false }); + } catch (trashError: any) { + throw trashError; + } return true; } catch (error) { this.logger.error(`丢弃失败: ${decodeURI(_path)}, ${String(error)}`); diff --git a/release-mac-x64.sh b/release-mac-x64.sh index 5b1bf1f2..9dba8af8 100644 --- a/release-mac-x64.sh +++ b/release-mac-x64.sh @@ -19,6 +19,10 @@ mkdir Exported_Games cp -r public assets Exported_Games ../../release cd ../../ +# 复制 trash 库的原生二进制 +mkdir release/lib +cp node_modules/trash/lib/macos-trash release/lib + # 进入 Origine 目录 cd packages/origine2 #npm install esbuild-darwin-arm64 diff --git a/release-mac.sh b/release-mac.sh index 24ef0144..56fd3a91 100644 --- a/release-mac.sh +++ b/release-mac.sh @@ -19,6 +19,11 @@ mkdir Exported_Games cp -r public assets Exported_Games ../../release cd ../../ +# 复制 trash 库的原生二进制 +mkdir release/lib +cp node_modules/trash/lib/macos-trash release/lib + + # 进入 Origine 目录 cd packages/origine2 #npm install esbuild-darwin-arm64 diff --git a/release.sh b/release.sh index f4f89425..c97ff75e 100644 --- a/release.sh +++ b/release.sh @@ -24,6 +24,9 @@ cd ../../ mkdir release/lib curl -L https://github.com/electron/rcedit/releases/latest/download/rcedit-x64.exe -o release/lib/rcedit-x64.exe +# 复制 trash 库的原生二进制 +cp node_modules/trash/lib/windows-trash.exe release/lib + # 进入 Origine 目录 cd packages/origine2 yarn run build From fc4023d33657b9377a46946d54c364a7b2a9bfde Mon Sep 17 00:00:00 2001 From: xiaoxustudio Date: Sun, 12 Jul 2026 14:21:20 +0800 Subject: [PATCH 02/13] fix: the possibility of failed execution for windows --- .../Modules/webgal-fs/webgal-fs.service.ts | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts b/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts index 242b7773..9f8de5c5 100644 --- a/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts +++ b/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts @@ -278,7 +278,6 @@ export class WebgalFsService { async trashFileOrDirectory(_path: string): Promise { try { const path = decodeURI(_path); - const stat = await fs.stat(path); if (stat.isDirectory()) { @@ -287,28 +286,23 @@ export class WebgalFsService { this.logger.log(`丢弃文件: ${path}`); } - try { - const binnaryMap = { - darwin: 'macos-trash', - win32: 'windows-trash', - }; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const isPackaged = typeof process.pkg !== 'undefined'; - if (isPackaged && Object.keys(binnaryMap).includes(process.platform)) { - const windowsTrashExe = join( - dirname(process.execPath), - 'lib', - binnaryMap[process.platform], - ); - await pExecFile(windowsTrashExe, [path]); - return true; - } - await trash(path, { glob: false }); - } catch (trashError: any) { - throw trashError; + const binaryMap = { + darwin: 'macos-trash', + win32: 'windows-trash.exe', + }; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const isPackaged = typeof process.pkg !== 'undefined'; + if (isPackaged && Object.keys(binaryMap).includes(process.platform)) { + const trashBinaryPath = join( + dirname(process.execPath), + 'lib', + binaryMap[process.platform], + ); + await pExecFile(trashBinaryPath, [path]); + return true; } - return true; + await trash(path, { glob: false }); } catch (error) { this.logger.error(`丢弃失败: ${decodeURI(_path)}, ${String(error)}`); return false; From 4e6d61402ba84679ee6fb29f893c4f5b4a6eb4ac Mon Sep 17 00:00:00 2001 From: Mahiru Date: Thu, 23 Jul 2026 21:59:24 +0800 Subject: [PATCH 03/13] feat: add opus support --- .../origine2/src/pages/editor/ChooseFile/chooseFileConfig.ts | 4 ++-- packages/origine2/src/pages/editor/MainArea/EditArea.tsx | 2 +- .../src/pages/editor/ResourceDisplay/ResourceDisplay.tsx | 2 +- .../main/java/com/openwebgal/terre/provider/TerreProvider.kt | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/origine2/src/pages/editor/ChooseFile/chooseFileConfig.ts b/packages/origine2/src/pages/editor/ChooseFile/chooseFileConfig.ts index a22c0bef..9edbb2a3 100644 --- a/packages/origine2/src/pages/editor/ChooseFile/chooseFileConfig.ts +++ b/packages/origine2/src/pages/editor/ChooseFile/chooseFileConfig.ts @@ -4,6 +4,6 @@ export const extNameMap = new Map([]); extNameMap.set('scene', ['.txt']); extNameMap.set('image', ['.png', '.jpg', '.jpeg', '.webp']); extNameMap.set('video', ['.mp4', '.webm', '.mkv']); -extNameMap.set('audio', ['.mp3', '.ogg', '.wav']); +extNameMap.set('audio', ['.mp3', '.ogg', '.wav', '.opus']); extNameMap.set('tex', ['.png', '.webp']); -extNameMap.set('json', ['.json']); \ No newline at end of file +extNameMap.set('json', ['.json']); diff --git a/packages/origine2/src/pages/editor/MainArea/EditArea.tsx b/packages/origine2/src/pages/editor/MainArea/EditArea.tsx index 1bd603d8..24302a9f 100644 --- a/packages/origine2/src/pages/editor/MainArea/EditArea.tsx +++ b/packages/origine2/src/pages/editor/MainArea/EditArea.tsx @@ -66,7 +66,7 @@ export default function EditArea() { const imageTypes = ["png", "jpg", "jpeg", "gif", "webp"]; const videoTypes = ["mp4", "webm", "ogg"]; -const audioTypes = ["mp3", "wav", "aac"]; +const audioTypes = ["mp3", "wav", "aac", "opus"]; const animationTypes = ["json"]; function getFileType(path: string): ResourceType | null { diff --git a/packages/origine2/src/pages/editor/ResourceDisplay/ResourceDisplay.tsx b/packages/origine2/src/pages/editor/ResourceDisplay/ResourceDisplay.tsx index d18b1c57..8dc7af92 100644 --- a/packages/origine2/src/pages/editor/ResourceDisplay/ResourceDisplay.tsx +++ b/packages/origine2/src/pages/editor/ResourceDisplay/ResourceDisplay.tsx @@ -31,7 +31,7 @@ function getComponent(resourceType: ResourceType, resourceUrl: string) { case ResourceType.Audio: return () => ( ); diff --git a/packages/terre-android/app/src/main/java/com/openwebgal/terre/provider/TerreProvider.kt b/packages/terre-android/app/src/main/java/com/openwebgal/terre/provider/TerreProvider.kt index f45b132e..28d77fe1 100644 --- a/packages/terre-android/app/src/main/java/com/openwebgal/terre/provider/TerreProvider.kt +++ b/packages/terre-android/app/src/main/java/com/openwebgal/terre/provider/TerreProvider.kt @@ -78,6 +78,7 @@ class TerreProvider : DocumentsProvider() { "mp3" -> "audio/mpeg" "wav" -> "audio/wav" "ogg" -> "audio/ogg" + "opus" -> "audio/ogg" "m4a" -> "audio/mp4" "flac" -> "audio/flac" "mp4" -> "video/mp4" @@ -214,4 +215,4 @@ class TerreProvider : DocumentsProvider() { override fun isChildDocument(parentDocumentId: String, documentId: String): Boolean { return documentId.startsWith(parentDocumentId) } -} \ No newline at end of file +} From e59a9434b30bf2f16e0f5694e7693a135112dbc5 Mon Sep 17 00:00:00 2001 From: Mahiru Date: Sat, 25 Jul 2026 16:59:23 +0800 Subject: [PATCH 04/13] =?UTF-8?q?fix:=20=E5=9B=9E=E6=94=B6=E7=AB=99?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E5=92=8C=20Mac=20=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/origine2/src/App.tsx | 2 ++ .../origine2/src/components/Assets/Assets.tsx | 9 +++++- .../src/hooks/useTrashFailedToast.tsx | 21 +++++++++++++ packages/origine2/src/locales/en.po | 6 ++++ packages/origine2/src/locales/ja.po | 6 ++++ packages/origine2/src/locales/ko.po | 6 ++++ packages/origine2/src/locales/zhCn.po | 6 ++++ .../src/pages/dashboard/GameElement.tsx | 9 +++++- .../src/pages/dashboard/TemplateElement.tsx | 9 +++++- .../src/Modules/assets/assets.controller.ts | 6 +++- .../manage-game/manage-game.controller.ts | 6 +++- .../Modules/webgal-fs/webgal-fs.service.ts | 30 +++++++++++-------- release-mac-x64.sh | 4 +++ release-mac.sh | 5 +++- 14 files changed, 106 insertions(+), 19 deletions(-) create mode 100644 packages/origine2/src/hooks/useTrashFailedToast.tsx diff --git a/packages/origine2/src/App.tsx b/packages/origine2/src/App.tsx index 23fb9f1d..8c431fa3 100644 --- a/packages/origine2/src/App.tsx +++ b/packages/origine2/src/App.tsx @@ -7,6 +7,7 @@ import useEditorStore from './store/useEditorStore'; import useLanguage from './hooks/useLanguage'; import { initMonaco } from '@/utils/initMonaco'; import { useHashRouter, RouterPage } from '@/router'; +import { TrashToaster } from '@/hooks/useTrashFailedToast'; function App() { useEffect(() => { @@ -32,6 +33,7 @@ function App() { return (
+
); } diff --git a/packages/origine2/src/components/Assets/Assets.tsx b/packages/origine2/src/components/Assets/Assets.tsx index fc9b9e59..f14b6b5d 100644 --- a/packages/origine2/src/components/Assets/Assets.tsx +++ b/packages/origine2/src/components/Assets/Assets.tsx @@ -57,6 +57,7 @@ import { t } from '@lingui/macro'; import Upload from './Upload'; import naturalCompare from 'natural-compare-lite'; import useEditorStore from '@/store/useEditorStore'; +import useTrashFailedToast from '@/hooks/useTrashFailedToast'; export interface IFile { extName: string; @@ -134,6 +135,7 @@ export default function Assets({ const sortOrder = useEditorStore.use.sortOrder(); const updateSortOrder = useEditorStore.use.updateSortOrder(); const isTrash = useEditorStore.use.isTrash(); + const toastTrashFailed = useTrashFailedToast(); const currentPath = useValue([...basePath, ...selectedFilePath.slice(0, -1)]); const currentFullPath = useMemo(() => [...rootPath, ...currentPath.value], [currentPath.value]); @@ -309,7 +311,12 @@ export default function Assets({ const handleDeleteFile = async (source: string) => { if (isTrash) { - await api.assetsControllerTrashFileOrDir({ source }); + try { + await api.assetsControllerTrashFileOrDir({ source }); + } catch { + toastTrashFailed(); + return; + } } else { await api.assetsControllerDeleteFileOrDir({ source }); } diff --git a/packages/origine2/src/hooks/useTrashFailedToast.tsx b/packages/origine2/src/hooks/useTrashFailedToast.tsx new file mode 100644 index 00000000..ab72c23f --- /dev/null +++ b/packages/origine2/src/hooks/useTrashFailedToast.tsx @@ -0,0 +1,21 @@ +import { Toast, ToastBody, Toaster, ToastTitle, useToastController } from '@fluentui/react-components'; +import { t } from '@lingui/macro'; + +const trashToasterId = 'trash-toaster'; + +/** 挂载在应用根部,供各处的删除操作复用 */ +export const TrashToaster = () => ; + +/** 移动到回收站失败时的提示,引导用户关闭回收站功能后重试 */ +export default function useTrashFailedToast() { + const { dispatchToast } = useToastController(trashToasterId); + + return () => + dispatchToast( + + {t`移动到回收站失败`} + {t`可在设置中关闭回收站功能后重试,此时删除将不可恢复`} + , + { intent: 'error' }, + ); +} diff --git a/packages/origine2/src/locales/en.po b/packages/origine2/src/locales/en.po index 5b752c26..9340e579 100644 --- a/packages/origine2/src/locales/en.po +++ b/packages/origine2/src/locales/en.po @@ -626,6 +626,9 @@ msgstr "Variable expression" msgid "只展示节点,不展示名称" msgstr "Only show nodes, not names" +msgid "可在设置中关闭回收站功能后重试,此时删除将不可恢复" +msgstr "You can disable Trash in settings and try again, but deletion will then be permanent." + msgid "可用 $0 表示当前输入" msgstr "Use $0 for the current input" @@ -1757,6 +1760,9 @@ msgstr "Disable skipping video" msgid "禁用" msgstr "Disabled" +msgid "移动到回收站失败" +msgstr "Failed to move to Trash" + msgid "立即注视" msgstr "Focus instantly" diff --git a/packages/origine2/src/locales/ja.po b/packages/origine2/src/locales/ja.po index 10f672b7..c2e0ff73 100644 --- a/packages/origine2/src/locales/ja.po +++ b/packages/origine2/src/locales/ja.po @@ -626,6 +626,9 @@ msgstr "変数式" msgid "只展示节点,不展示名称" msgstr "ノードのみ表示し、名前は表示しない" +msgid "可在设置中关闭回收站功能后重试,此时删除将不可恢复" +msgstr "設定でゴミ箱を無効にしてから再試行できます。その場合、削除したファイルは復元できません。" + msgid "可用 $0 表示当前输入" msgstr "$0 で現在の入力を表せます" @@ -1757,6 +1760,9 @@ msgstr "動画のスキップを無効にする" msgid "禁用" msgstr "無効化" +msgid "移动到回收站失败" +msgstr "ゴミ箱への移動に失敗しました" + msgid "立即注视" msgstr "即座に注視" diff --git a/packages/origine2/src/locales/ko.po b/packages/origine2/src/locales/ko.po index f15d6887..bab2f3fb 100644 --- a/packages/origine2/src/locales/ko.po +++ b/packages/origine2/src/locales/ko.po @@ -626,6 +626,9 @@ msgstr "변수 표현식" msgid "只展示节点,不展示名称" msgstr "노드만 표시하고 이름은 표시하지 않음" +msgid "可在设置中关闭回收站功能后重试,此时删除将不可恢复" +msgstr "설정에서 휴지통을 끈 뒤 다시 시도할 수 있습니다. 이 경우 삭제한 항목은 복구할 수 없습니다." + msgid "可用 $0 表示当前输入" msgstr "$0를 사용하여 현재 입력 표시 가능" @@ -1757,6 +1760,9 @@ msgstr "비디오 스킵 금지" msgid "禁用" msgstr "비활성화" +msgid "移动到回收站失败" +msgstr "휴지통으로 이동하지 못했습니다" + msgid "立即注视" msgstr "즉시 주시" diff --git a/packages/origine2/src/locales/zhCn.po b/packages/origine2/src/locales/zhCn.po index 61eef5a6..18830df8 100644 --- a/packages/origine2/src/locales/zhCn.po +++ b/packages/origine2/src/locales/zhCn.po @@ -626,6 +626,9 @@ msgstr "变量表达式" msgid "只展示节点,不展示名称" msgstr "只展示节点,不展示名称" +msgid "可在设置中关闭回收站功能后重试,此时删除将不可恢复" +msgstr "可在设置中关闭回收站功能后重试,此时删除将不可恢复" + msgid "可用 $0 表示当前输入" msgstr "可用 $0 表示当前输入" @@ -1757,6 +1760,9 @@ msgstr "禁止跳过视频" msgid "禁用" msgstr "禁用" +msgid "移动到回收站失败" +msgstr "移动到回收站失败" + msgid "立即注视" msgstr "立即注视" diff --git a/packages/origine2/src/pages/dashboard/GameElement.tsx b/packages/origine2/src/pages/dashboard/GameElement.tsx index 1c49d53e..6c66afcd 100644 --- a/packages/origine2/src/pages/dashboard/GameElement.tsx +++ b/packages/origine2/src/pages/dashboard/GameElement.tsx @@ -37,6 +37,7 @@ import { routes } from '@/router'; import { t } from '@lingui/macro'; import { GameInfoDto } from '@/api/Api'; import useEditorStore from '@/store/useEditorStore'; +import useTrashFailedToast from '@/hooks/useTrashFailedToast'; interface IGameElementProps { gameInfo: GameInfoDto; @@ -72,6 +73,7 @@ export default function GameElement(props: IGameElementProps) { const newGameName = useValue(props.gameInfo.dir); const deleteChecked = useValue(false); const isTrash = useEditorStore.use.isTrash(); + const toastTrashFailed = useTrashFailedToast(); const openInFileExplorer = () => { api.manageGameControllerOpenGameDict(props.gameInfo.dir); @@ -90,7 +92,12 @@ export default function GameElement(props: IGameElementProps) { const deleteThisGame = async () => { if (isTrash) { - await api.manageGameControllerTrash({ gameName: props.gameInfo.dir }); + try { + await api.manageGameControllerTrash({ gameName: props.gameInfo.dir }); + } catch { + toastTrashFailed(); + return; + } } else { await api.manageGameControllerDelete({ gameName: props.gameInfo.dir }); } diff --git a/packages/origine2/src/pages/dashboard/TemplateElement.tsx b/packages/origine2/src/pages/dashboard/TemplateElement.tsx index 9e45b13d..125f2ba3 100644 --- a/packages/origine2/src/pages/dashboard/TemplateElement.tsx +++ b/packages/origine2/src/pages/dashboard/TemplateElement.tsx @@ -39,6 +39,7 @@ import { t } from '@lingui/macro'; import { localStorageRename } from '@/utils/localStorageRename'; import { TemplateInfoDto } from '@/api/Api'; import useEditorStore from '@/store/useEditorStore'; +import useTrashFailedToast from '@/hooks/useTrashFailedToast'; interface ITemplateElementProps { templateInfo: TemplateInfoDto; @@ -73,6 +74,7 @@ export default function TemplateElement(props: ITemplateElementProps) { const isShowRenameDialog = useValue(false); const newTemplateName = useValue(props.templateInfo.dir); const isTrash = useEditorStore.use.isTrash(); + const toastTrashFailed = useTrashFailedToast(); const openInFileExplorer = () => { api.assetsControllerOpenDict(`templates/${props.templateInfo.dir}`); @@ -92,7 +94,12 @@ export default function TemplateElement(props: ITemplateElementProps) { const deleteThisTemplate = async (templateName: string, isTrash = false) => { if (isTrash) { - await api.manageTemplateControllerTrashTemplate(templateName); + try { + await api.manageTemplateControllerTrashTemplate(templateName); + } catch { + toastTrashFailed(); + return; + } } else { await api.manageTemplateControllerDeleteTemplate(templateName); } diff --git a/packages/terre2/src/Modules/assets/assets.controller.ts b/packages/terre2/src/Modules/assets/assets.controller.ts index b96adff4..f3b49539 100644 --- a/packages/terre2/src/Modules/assets/assets.controller.ts +++ b/packages/terre2/src/Modules/assets/assets.controller.ts @@ -164,9 +164,13 @@ export class AssetsController { description: 'Failed to trash the file or directory.', }) async trashFileOrDir(@Body() fileOperationDto: TrashFileOrDirDto) { - return this.webgalFs.trashFileOrDirectory( + const result = await this.webgalFs.trashFileOrDirectory( this.webgalFs.getPathFromRoot(`public/${fileOperationDto.source}`), ); + if (!result) { + throw new BadRequestException('Failed to trash the file or directory.'); + } + return result; } @Post('editTextFile') diff --git a/packages/terre2/src/Modules/manage-game/manage-game.controller.ts b/packages/terre2/src/Modules/manage-game/manage-game.controller.ts index 2d53497c..199ce518 100644 --- a/packages/terre2/src/Modules/manage-game/manage-game.controller.ts +++ b/packages/terre2/src/Modules/manage-game/manage-game.controller.ts @@ -396,9 +396,13 @@ export class ManageGameController { description: 'Failed to trash the file or directory.', }) async trash(@Body() trashDto: TrashDto) { - return this.webgalFs.trashFileOrDirectory( + const result = await this.webgalFs.trashFileOrDirectory( this.webgalFs.getPathFromRoot(`public/games/${trashDto.gameName}`), ); + if (!result) { + throw new BadRequestException('Failed to trash the file or directory.'); + } + return result; } @Get('getIcons/:gameDir') diff --git a/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts b/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts index 9f8de5c5..54ee7c8b 100644 --- a/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts +++ b/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts @@ -286,23 +286,27 @@ export class WebgalFsService { this.logger.log(`丢弃文件: ${path}`); } - const binaryMap = { + // 打包后 trash 库定位不到随包分发的原生二进制,改用可执行文件同级 lib 目录下的副本 + const trashBinary = { darwin: 'macos-trash', win32: 'windows-trash.exe', - }; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const isPackaged = typeof process.pkg !== 'undefined'; - if (isPackaged && Object.keys(binaryMap).includes(process.platform)) { - const trashBinaryPath = join( - dirname(process.execPath), - 'lib', - binaryMap[process.platform], - ); - await pExecFile(trashBinaryPath, [path]); - return true; + }[process.platform]; + const trashBinaryPath = + trashBinary && join(dirname(process.execPath), 'lib', trashBinary); + + if (trashBinaryPath && (await this.exists(trashBinaryPath))) { + try { + await pExecFile(trashBinaryPath, [path]); + return true; + } catch (error) { + // macOS 上二进制可能因执行位丢失或 Gatekeeper 隔离而无法运行, 回退到库实现 + this.logger.warn(`回收站二进制不可用, 回退: ${String(error)}`); + } } + await trash(path, { glob: false }); + + return true; } catch (error) { this.logger.error(`丢弃失败: ${decodeURI(_path)}, ${String(error)}`); return false; diff --git a/release-mac-x64.sh b/release-mac-x64.sh index 9dba8af8..6d0bb3c7 100644 --- a/release-mac-x64.sh +++ b/release-mac-x64.sh @@ -22,6 +22,7 @@ cd ../../ # 复制 trash 库的原生二进制 mkdir release/lib cp node_modules/trash/lib/macos-trash release/lib +chmod +x release/lib/macos-trash # 进入 Origine 目录 cd packages/origine2 @@ -82,6 +83,9 @@ cd release # 写脚本 echo 'cd "$(dirname "$0")"' >> run-webgal-on-mac.command echo 'cd WebGAL' >> run-webgal-on-mac.command +# 压缩包不保留执行权限,且下载后会被 Gatekeeper 隔离,启动时修复回收站二进制 +echo 'chmod +x lib/macos-trash 2>/dev/null' >> run-webgal-on-mac.command +echo 'xattr -c lib/macos-trash 2>/dev/null' >> run-webgal-on-mac.command echo './WebGAL_Terre' >> run-webgal-on-mac.command chmod +x run-webgal-on-mac.command chmod +x WebGAL/WebGAL_Terre diff --git a/release-mac.sh b/release-mac.sh index 56fd3a91..877be73d 100644 --- a/release-mac.sh +++ b/release-mac.sh @@ -22,7 +22,7 @@ cd ../../ # 复制 trash 库的原生二进制 mkdir release/lib cp node_modules/trash/lib/macos-trash release/lib - +chmod +x release/lib/macos-trash # 进入 Origine 目录 cd packages/origine2 @@ -83,6 +83,9 @@ cd release # 写脚本 echo 'cd "$(dirname "$0")"' >> run-webgal-on-mac.command echo 'cd WebGAL' >> run-webgal-on-mac.command +# 压缩包不保留执行权限,且下载后会被 Gatekeeper 隔离,启动时修复回收站二进制 +echo 'chmod +x lib/macos-trash 2>/dev/null' >> run-webgal-on-mac.command +echo 'xattr -c lib/macos-trash 2>/dev/null' >> run-webgal-on-mac.command echo './WebGAL_Terre' >> run-webgal-on-mac.command chmod +x run-webgal-on-mac.command chmod +x WebGAL/WebGAL_Terre From 9f6e3218c6683534d40e38ce68d7d2c1cfce2b7d Mon Sep 17 00:00:00 2001 From: Mahiru Date: Sat, 25 Jul 2026 21:58:16 +0800 Subject: [PATCH 05/13] add figure position --- .../src/hooks/usePresetTargetOptions.ts | 20 +++++++++ packages/origine2/src/locales/ko.ts | 2 +- .../SentenceEditor/BasicCommands.tsx | 15 ++----- .../SentenceEditor/ChangeFigure.tsx | 24 +++++----- .../GraphicalEditor/SentenceEditor/Say.tsx | 30 ++++++------- .../SentenceEditor/SetAnimation.tsx | 13 ++---- .../SentenceEditor/SetTempAnimation.tsx | 13 ++---- .../SentenceEditor/SetTransform.tsx | 13 ++---- .../SentenceEditor/SetTransition.tsx | 12 ++--- .../TransformableBox/referenceBoxGeometry.ts | 17 +++---- packages/origine2/src/types/stageInterface.ts | 8 +++- .../Modules/lsp/suggestionRules/getArgsKey.ts | 44 +++++++++++++++++++ .../lsp/suggestionRules/getCommands.ts | 2 +- 13 files changed, 124 insertions(+), 89 deletions(-) create mode 100644 packages/origine2/src/hooks/usePresetTargetOptions.ts diff --git a/packages/origine2/src/hooks/usePresetTargetOptions.ts b/packages/origine2/src/hooks/usePresetTargetOptions.ts new file mode 100644 index 00000000..e1c0a40e --- /dev/null +++ b/packages/origine2/src/hooks/usePresetTargetOptions.ts @@ -0,0 +1,20 @@ +import { useMemo } from "react"; +import { t } from "@lingui/macro"; + +/** + * 预设的效果目标,按立绘在舞台上从左到右的顺序排列 + * @param includeStage 是否包含舞台画面 + */ +export const usePresetTargetOptions = (includeStage = true) => { + return useMemo(() => new Map([ + [ "fig-left", t`左侧立绘` ], + [ "fig-left14", t`左侧 1/4 立绘` ], + [ "fig-left13", t`左侧 1/3 立绘` ], + [ "fig-center", t`中间立绘` ], + [ "fig-right13", t`右侧 1/3 立绘` ], + [ "fig-right14", t`右侧 1/4 立绘` ], + [ "fig-right", t`右侧立绘` ], + [ "bg-main", t`背景图片` ], + ...(includeStage ? [[ "stage-main", t`舞台画面` ] as [string, string]] : []), + ]), [includeStage]); +}; diff --git a/packages/origine2/src/locales/ko.ts b/packages/origine2/src/locales/ko.ts index 40cf3bc7..482c0a43 100644 --- a/packages/origine2/src/locales/ko.ts +++ b/packages/origine2/src/locales/ko.ts @@ -1 +1 @@ -/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"ez2arP\":\";주석\",\"ZfQJb6\":\"*스포이드를 설정한 후, 버튼을 클릭하여 변경 사항을 저장하세요\",\"9ITYjl\":\"%\",\"q0qRTC\":\"<0>새 템플릿 적용\",\"qTVI69\":[\"<0>현재 적용된 템플릿: \",[\"currentTemplateName\"],\"\"],\"+PwTIw\":\"1, 2, 3, ...\",\"eyqDdt\":\"4.6 사용자 데이터 이동\",\"YSvnx0\":\"applyStyle:TextBox_ShowName_Background->TextBox_ShowName_Background_Red;\",\"4O5JCB\":\"BGM\",\"etAybr\":\"BGM 재생 상태\",\"OtUIFf\":\"BGM 음량\",\"2YU2Kn\":\"bgm:배경음악 선택;\",\"Zjvu8X\":\"callScene:장면 파일 선택;\",\"TCV7GD\":\"callSteam: -achievementId=;\",\"FS5tux\":\"CG\",\"vESFMs\":\"changeBg:배경 이미지 선택;\",\"XfG9aQ\":\"changeFigure:스탠딩 파일 선택;\",\"jdJt3k\":\"changeScene:장면 파일 선택;\",\"+t6BAj\":\"choose:선택지:장면 파일 선택;\",\"YvwyIk\":\"CSS 속성값\",\"IiCbUY\":\"CSS 속성명:\",\"/oBAgV\":\"enable은 켜기, none 또는 비워서 끄기\",\"tBCPXB\":\"end;\",\"TJ8a+f\":\"filmMode:enable;\",\"IlFGfd\":\"font-family\",\"89SUCu\":\"getUserInput:;\",\"yZpvfb\":\"Intro 텍스트\",\"eIOW3x\":\"intro:;\",\"7DnAtq\":\"jumpLabel:label_1;\",\"aZ3bG5\":\"label:label_1;\",\"mHSbXE\":\"Live2D 모션\",\"ebsFnb\":\"Live2D 표정\",\"+Q2q6Q\":\"miniAvatar:미니 프로필 선택;\",\"wOFCxG\":\"pixiInit;\",\"VWZEcv\":\"pixiPerform:snow;\",\"Dxbz2N\":\"playEffect:효과음 파일 선택;\",\"xCHuyF\":\"playVideo:영상 파일 선택;\",\"V8p+bG\":\"Portable 모드\",\"/8PJ2M\":\"RGB 영화 필터\",\"Zrsplh\":\"setAnimation:애니메이션 파일 선택;\",\"Uxm0fk\":\"setComplexAnimation:universalSoftIn -target=fig-center;\",\"aHdzZT\":\"setTempAnimation:[{\\\"duration\\\":0},{\\\"duration\\\":500}];\",\"dfLJ5q\":\"setTextbox:hide;\",\"UYmTsP\":\"setTransform: -duration=0;\",\"RfGfVs\":\"setTransition:;\",\"74lZuk\":\"setVar:a=1;\",\"bTyzll\":\"showVars는 일반적으로 내용이 필요하지 않습니다\",\"8kbHea\":\"showVars;\",\"VV9dBJ\":\"Spine 애니메이션\",\"xOOpnF\":\"Spine 스킨\",\"RMdyHE\":\"Steam AppID\",\"LAI/Cy\":\"unlockBgm:;\",\"9afSwF\":\"unlockCg:;\",\"bIXYlr\":\"wait:1000;\",\"Z9I0PY\":\"WebGAL Standard\",\"o7ojN3\":\"WebGAL Terre 4.6 은 새로운 사용자 데이터 목록을 사용합니다. 구버전 데이터를 검사하거나 옮기는 것을 권장합니다.\",\"VAODzB\":\"X축 이동\",\"A/jVgG\":\"X축 비율\",\"UVJiw4\":\"Y축 이동\",\"0eNchO\":\"Y축 비율\",\"a6O/JX\":\"z-index\",\"TSQMsH\":\"리본 메뉴 항상 표시\",\"pu0x8w\":\"이전 단계\",\"Q/n6Ny\":\"업로드\",\"n4X166\":\"글씨체 파일 업로드\",\"eTyEPt\":\"리소스 업로드\",\"LIAXFC\":\"위로 이동\",\"4uRyNg\":\"다음 단계\",\"St/SJ5\":\"아래로 이동\",\"PP/Y5/\":\"clear 함수 사용하지 않음\",\"dOjUpV\":\"when 조건 사용하지 않음\",\"LYKall\":\"선택 불가\",\"y41keD\":\"템플릿을 적용하지 않음\",\"+H1Y3V\":\"이전 텍스트 창의 문장과 이어지지 않음\",\"7V3y7y\":\"순환 연결을 만들 수 없습니다\",\"R1CJC2\":\"설정 안 함\",\"nL7bt7\":\"동일 대상 애니메이션과 병행 실행\",\"IQKChW\":\"양끝 정렬\",\"jVh12T\":\"개의 글자\",\"mmLYLw\":\"중\",\"GTIc9/\":\"중앙\",\"hfB0wu\":\"중앙 스탠딩\",\"wz/sw7\":\"현재 스테이지에 이펙트 추가\",\"h1jPqc\":\"스탠딩 또는 배경 이미지에 단일 구간 애니메이션 이펙트 설정\",\"LpKCBj\":\"스탠딩 또는 배경 이미지에 다중 구간 애니메이션 이펙트 설정\",\"PI3ywW\":\"스탠딩 또는 배경 이미지에 애니메이션 불러오기 이펙트를 적용\",\"sWTv8x\":\"메인 플로차트는 삭제할 수 없습니다\",\"WC48YD\":\"메인 스타일\",\"qbow8i\":\"홈페이지\",\"hNFZVt\":\"테마\",\"jHJe9a\":\"밝기\",\"WmmLaN\":\"현재 장면에서 선택\",\"zA8esk\":\"코드 에디터\",\"7zsjb3\":\"다음 대상의 경로가 이미 존재하므로 덮어쓰지 않았습니다:\",\"QLZ4dX\":\"감마값\",\"Fas9Si\":\"clear 함수를 사용해 초기화\",\"uk3CXK\":\"사용 중인 템플릿:\",\"47+kxo\":\"엔진에 내장된 이펙트 사용\",\"/ySHoI\":\"본 태그 사용\",\"2cCMVu\":\"이펙트 사용\",\"f9y04E\":\"스탠딩 ID 사용\",\"GL8qtJ\":\"커스텀 특수 효과 사용\",\"IUCcDX\":\"프리셋 특수 효과 사용\",\"oWsDuj\":\"프리셋 대상을 사용하되, ID가 설정된 경우 적용되지 않습니다.\",\"NUlqj7\":\"프리셋 대상 사용\",\"jbVvhH\":\"예: -100,-100,100,100\",\"mUxmYb\":[\"예: ^.\",[\"regexRange\"],\"$\"],\"nwPnGm\":\"예: a=1 또는 name=WebGAL\",\"WCGfK0\":\"예: a>0 && flag==true\",\"IF2b07\":\"예: fig-center\",\"uyQChY\":\"예: i\",\"pxPdPB\":\"예: universalSoftIn\",\"bl9Beh\":\"사이드바\",\"rpBRee\":\"사이드바 게임 미리보기\",\"jBG25x\":\"저장\",\"aBwR6p\":\"저장 중...\",\"IbLq2X\":\"템플릿 구성 저장에 실패했습니다. 잠시후 다시 시도해 주세요\",\"h/Vi1t\":\"플로차트 저장\",\"GLd8LR\":\"눈 감기 유지(밀리초)\",\"e76V/v\":\"캐릭터 이름 유지\",\"c/sLTZ\":\"수정 시간\",\"SK6wGK\":\"게임 아이콘 수정\",\"h/QpEH\":\"색상 수정\",\"cBie7+\":\"모서리 깎기\",\"yWKT27\":\"값\",\"U7ZiGp\":\"값 또는 표현식\",\"Xr5Uul\":\"오프셋\",\"YWrmBt\":\"우측 오프셋 거리(위치 지정용)\",\"hQE+CE\":\"좌측 오프셋 거리(위치 지정용)\",\"8TBO/L\":\"하단 오프셋 거리(위치 지정용)\",\"BE5gDE\":\"상단 오프셋 거리(위치 지정용)\",\"sagY4I\":\"정지 위치\",\"GXB9zw\":\"픽셀\",\"0k7O3u\":\"스킵 허용\",\"XcY13T\":\"글로우 영화 필터\",\"gEFUMc\":\"입장 시간(단위: 밀리초)\",\"wCAGx4\":\"전역 변수\",\"T1h5rG\":\"전체 화면 텍스트\",\"ktxKKs\":\"전체 화면에 텍스트를 표시합니다. 독백이나 장면 도입부에 사용됩니다.\",\"sUqjHY\":\"모두 숨기기\",\"qFPINp\":\"모두 표시\",\"iw+qKq\":\"About\",\"/CTGfy\":\"연결된 스탠딩\",\"GS2Ofk\":\"닫기\",\"/IjfsU\":\"미니 프로필 끄기\",\"nsrfMl\":\"효과음 끄기\",\"wNuXVe\":\"배경 끄기\",\"gczUo5\":\"미리보기 창 닫기\",\"Ik81t6\":\"내용\",\"APFEOB\":\"내부 텍스트\",\"DfIXub\":\"안쪽 여백\",\"jnhO7t\":\"최대 너비\",\"NS1tk+\":\"최대 높이\",\"cnA59a\":\"최소 너비\",\"OlHHxv\":\"최소 높이\",\"txODIz\":\"최신 버전\",\"ZQEl0Y\":\"전역 변수 저장하기\",\"vEK29W\":\"변수에 저장하기\",\"coN/yH\":\"현재 세이브 변수에 저장하기\",\"71Cosn\":\"분기 선택\",\"40G9NY\":\"다른 장면 파일로 전환한 뒤, 현재 장면을 초기화\",\"qU64ut\":\"장면 전환\",\"aYsifP\":\"장면을 전환하며, 새 장면이 어버이 장면을 대체합니다\",\"7yX7g6\":\"인터페이스 언어 전환\",\"dvhHum\":\"스탠딩 전환\",\"sWeIVn\":\"배경 전환\",\"APcQQr\":\"배경 이미지 전환\",\"gMWuBH\":\"목록 확인\",\"lLPWZb\":\"생성\",\"gnSpxl\":\"사본 생성\",\"hEKmon\":\"새 템플릿 생성\",\"s2YbNh\":\"새 게임 생성\",\"tGxdbR\":\"삭제\",\"kq1kxQ\":\"속성 삭제\",\"ZlR4nz\":\"현재 문장 삭제\",\"R/0aSV\":\"템플릿 삭제\",\"kQ+WdA\":\"게임 삭제\",\"JpgJsu\":\"새로고침\",\"wRZsHb\":\"게임 새로고침\",\"MSHXWR\":\"전경\",\"zkD1td\":\"리본 메뉴 표시\",\"gNEHCz\":\"굵게\",\"BuTBJb\":\"애니메이션\",\"B1cXA5\":\"애니메이션 이름\",\"zy14kX\":\"애니메이션 프레임 제어\",\"9cawOO\":\"반쯤 벌린 입\",\"rYZPFI\":\"단위는 밀리초. 0~ 유효\",\"pM1UIg\":\"단일 애니메이션\",\"A48Tay\":\"한 줄 주석\",\"UFVfv0\":\"곧 사용자 데이터를 새로운 목록으로 이동합니다. 파일이 많을 경우 시간이 걸릴 수 있으므로, 완료될 때까지 WebGAL Terre를 닫지 마세요.\",\"T2JfAd\":\"사용자 데이터를 기본 목록으로 되돌립니다. 파일이 많을 경우 시간이 걸릴 수 있습니다.\",\"o0fQFX\":\"두께\",\"JZjf/Y\":\"기존 스타일->새 스타일, 여러 개는 쉼표로 구분\",\"4m2dYU\":\"파라미터 값\",\"KJZS3U\":\"노드 이름을 두 번 클릭하면 편집할 수 있습니다\",\"CDuvSn\":\"반사 영화 필터\",\"hHGCZw\":\"배포된 게임은 <0>https://www.openwebgal.com/games/ 에 전시됩니다. 여러분의 작품을 공유해 주세요!\",\"cYUc0S\":\"새 버전 발견\",\"jxD+iQ\":\"취소\",\"bbdi6l\":\"고정 해제\",\"OcQJ3O\":\"변형\",\"MiE0Mz\":\"변수명\",\"iZhOyz\":\"변수 표현식\",\"xg8k+a\":\"노드만 표시하고 이름은 표시하지 않음\",\"HER8aZ\":\"$0를 사용하여 현재 입력 표시 가능\",\"A27Z7l\":\"스크립트 간에 무한 루프가 있거나 대상 줄 이전에 빨리 감기해야 할 행수가 너무 많을 수 있습니다.\",\"nOgzPD\":\"선택 가능 항목\",\"oeUweH\":\"우측\",\"mL9DmJ\":\"우측 스탠딩\",\"23nU8q\":\"우측 정렬\",\"upWPde\":\"위로 추가\",\"7YMtbN\":\"배경음악 재생, 전환 또는 정지\",\"V9IP5s\":\"시작 이미지\",\"xqo5T2\":\"활성\",\"qPjXK6\":\"when 조건 활성화\",\"4l0zVt\":\"비디오 건너뛰기 활성화\",\"JZA2nJ\":\"립싱크 및 눈 깜박임\",\"MUwa78\":\"휴지통\",\"32wTLf\":\"고정 팝업\",\"uv5w6v\":\"이미지\",\"aPiTPQ\":\"그래픽 에디터\",\"t5JJx+\":\"그림\",\"ruqLSO\":\"이미지 차분\",\"sltuoL\":\"원형\",\"s6jLFn\":\"원형 이즈 인\",\"0O4ucF\":\"원형 이즈 인 아웃\",\"RDWZpZ\":\"원형 이즈 아웃\",\"TMErca\":\"둥근 모서리\",\"JzRUNZ\":\"둥근 사각형\",\"1Vx+fK\":\"대화창 좌측 하단에 미니 프로필 표시\",\"BOBeYL\":\"현재 장면 내에 이동 태그 생성\",\"lKHSrA\":\"파일 탐색기에서 열기\",\"Hia9ZF\":\"새 탭에서 미리보기\",\"n/RuiJ\":\"게임 내에서 현재 변수 표시\",\"EBxZ+B\":\"여기에 편집 위젯 배치\",\"78nH85\":\"장면\",\"Wh0Wxu\":\"장면 및 분기\",\"HFYGwW\":\"장면 파일\",\"RuqGFg\":\"파라미터 값 작성\",\"E8v2tX\":\"복잡 애니메이션\",\"l1axJE\":\"복잡 애니메이션 이름은 엔진에 내장된 애니메이션에서 가져옵니다. 프로젝트 애니메이션 파일을 사용하려면 일반적으로 '애니메이션 불러오기'을 사용해야 합니다.\",\"Tw6+I6\":\"외부 텍스트\",\"Rw2ZoI\":\"바깥 여백\",\"Rg0rou\":\"다중 구간 애니메이션\",\"Rnjd5I\":\"대\",\"DYpFps\":\"폭설\",\"dIsNg+\":\"커스텀 템플릿 사용으로 인해 미리보기에 이상이 발생할 경우, 이 옵션을 끄시길 바랍니다.\",\"5npXGU\":\"폰트\",\"IBB/4V\":\"폰트 최적화\",\"HyXaZd\":\"폰트 최적화는 게임 미리보기 창이 특정 크기 이하일때 가독성을 높이기 위해 텍스트 테두리를 비활성화합니다.\",\"nCmVP2\":\"글자 크기\",\"zAiOiy\":\"폰트 파일 URL\",\"GqjJWX\":\"폰트 파일 업로드에 실패했습니다. 다시 시도해 주세요\",\"NCX3JP\":\"폰트 유형\",\"eWhPhP\":\"폰트 구성\",\"+Nelz8\":\"글자 간격\",\"bPcq/h\":\"글꼴 두께\",\"51Vz47\":\"위치 지정 방식\",\"GWpHcc\":\"실시간 미리보기\",\"t+sLG2\":\"실시간 미리보기 꺼짐\",\"QQLO2n\":\"실시간 미리보기가 중지되었습니다\",\"306dJv\":\"실시간 미리보기 시작 시 자동으로 주입되며, 실제 게임에서는 실행되지 않습니다.\",\"kXwAov\":\"실시간 미리보기 빨리 감기가 제한 시간을 초과하여 연산을 중단했습니다.\",\"L8MJAS\":\"실시간 미리보기 켜짐\",\"EVMosY\":\"실험적 빠른 미리보기\",\"CPbMQ8\":\"너비\",\"Z8j8l6\":\"대비\",\"WuipOX\":\"대화\",\"/FOeko\":\"대화창 텍스트\",\"OLxIGA\":\"대화창 제목\",\"o2RHlj\":\"템플릿 불러오기\",\"v6FJm2\":\"내보내기\",\"RQ9guE\":\"내보내는 중...\",\"XkkDWm\":\"실행 파일로 내보내기\",\"KyQ6e8\":\"Android 프로젝트 파일로 내보내기\",\"HDLXqZ\":\"웹페이지로 내보내기\",\"2mK/uv\":\"내보내기 실패!\",\"vZo1qG\":\"내보내기 성공!\",\"Ra5S96\":\"템플릿 내보내기\",\"ztfrH+\":\"현재 템플릿을 선택한 게임에 적용\",\"3eNAFJ\":\"텍스트 창의 텍스트를 읽음으로 설정\",\"5yRaVL\":\"텍스트 창의 텍스트를 읽지 않음으로 설정\",\"Diw4et\":\"소\",\"4BzOtt\":\"미니 프로필 이미지\",\"f0BISX\":\"미니 프로필 용기\",\"1nPKgD\":\"미니 프로필 파일\",\"Bpp7ke\":\"미니 프로필 표시 상태\",\"0iXI5f\":\"아직 폰트가 추가되지 않았습니다\",\"ugHPWR\":\"아직 구성된 폰트가 없습니다\",\"6754WP\":\"계층 순서\",\"vjbngg\":\"가운데 정렬\",\"lL0plW\":\"가운데 맞춤\",\"lxKfe1\":\"미니 프로필 표시\",\"kp6HDd\":\"잠금 해제되지 않은 노드 표시\",\"KoFqVI\":\"속성\",\"KHw8dl\":\"왼쪽\",\"+b1cs9\":\"좌측 스탠딩\",\"U/hj+N\":\"좌측 정렬\",\"Tc1V/S\":\"생성됨\",\"BUYNnD\":\"활성화됨: 삭제 작업은 파일을 휴지통으로 이동합니다\",\"RO1pYQ\":[[\"0\"],\" 파일 또는 폴더가 이미 존재합니다. 다른 이름을 입력해주세요\"],\"/ctKDE\":\"빨리 감기한 줄 수\",\"W7nUsD\":\"비활성화됨: 삭제 작업은 파일을 영구 삭제합니다\",\"i5KfKE\":\"읽은 텍스트\",\"/riza6\":\"읽은 텍스트 내부\",\"KzGK3I\":\"읽은 텍스트 외부\",\"tBIZt9\":\"도움말\",\"k0vDpg\":\"도움말 문서\",\"Ysz9K8\":\"자주 쓰는 설정\",\"FfZSjF\":\"기본 연출\",\"AM6qht\":\"타일 배열\",\"84Kfga\":\"병렬 애니메이션\",\"7P0aRQ\":\"적용\",\"Hzp94p\":\"스타일 적용\",\"5IGQoe\":\"적용된 템플릿\",\"wTGMhd\":\"색상 변경 적용\",\"K8lqFB\":\"지연 시간(초)\",\"TAiYqj\":\"켜기\",\"GGYg+t\":\"시작\",\"jZlQQB\":\"이동 시작\",\"CJMpjt\":\"벌린 입\",\"sTLh1u\":\"강도\",\"bcDpsn\":\"현재 장면\",\"eWKbZk\":\"현재 장면에 선택 가능한 태그가 없습니다\",\"HxzEI9\":\"현재 portable 모드이며, 실제 데이터 목록은 다운로드 목록 내의 data 폴더에 의해 결정됩니다.\",\"dD/c/t\":\"이동 가능한 내용을 감지하지 못했지만, 새 버전은 이미 사용자 데이터 목록으로 전환되었습니다.\",\"YS+bsM\":\"현재 버전\",\"/eHs6F\":\"현재 상태\",\"sogedR\":\"현재 환경은 시스템 파일 탐색기를 지원하지 않습니다. 경로를 수동으로 입력해 주세요.\",\"/K/Jca\":\"현재 미리보기 변수\",\"BQWdVc\":\"독일어\",\"AtV2zZ\":\"빠른 미리보기 효과\",\"iXWuW8\":\"빠른 미리보기 효과 꺼짐\",\"MI1wom\":\"빠른 미리보기 효과 켜짐\",\"4fasly\":\"빠른 미리보기 문장\",\"It2H0T\":\"빠른 미리보기 문장 꺼짐\",\"SPZNPb\":\"빠른 미리보기 문장 켜짐\",\"HJOSnW\":\"무시\",\"LgENDV\":\"이 버전 무시\",\"eTTofS\":\"기본 목록으로 복원\",\"NfnpFD\":\"게임 삭제를 확정하겠습니다.\",\"Beao/k\":\"수동으로 다음 문장을 표시\",\"Rt57iW\":\"ID 수동 입력\",\"1rlyyl\":\"다운로드 목록을 열기\",\"jpPLGO\":\"전체 설정 열기\",\"aiJY5K\":\"영화 모드 켜기/끄기\",\"c+tywy\":\"이펙트 에디터 열기\",\"KFUqJk\":\"사용자 데이터 목록 열기\",\"bpjf7e\":\"설정 열기\",\"brKARj\":\"설정 디렉터리 열기\",\"A1LFzH\":\"이 문장까지 실행\",\"M/0TFg\":\"실행 후 캐릭터 이름 초기화\",\"NXEIyt\":\"확장명\",\"IVbgAn\":\"드래그하여 변형 조정(빠른 미리보기 효과를 켜는 것을 권장합니다)\",\"I2S29O\":\"이전 텍스트 창 내의 문장과 이어 붙이기\",\"4zkD4P\":\"연결 모드\",\"80W7XM\":\"지속 시간(밀리초)\",\"Yu/zJb\":\"포인터\",\"GL6/RW\":\"정렬 방식\",\"zY4737\":\"텍스트 창 표시 여부 제어\",\"qZOPAd\":\"제출\",\"z+vCyE\":\"수정 제출\",\"5tNImB\":\"힌트\",\"lSDxhM\":\"힌트:\",\"a+Vt/6\":\"힌트: 스탠딩/배경을 먼저 설정한 후 애니메이션을 적용하세요. 아니면 대상을 찾을 수 없습니다.\",\"AxfvpJ\":\"힌트: 편집을 끝낸 후 유효하지 않은 CG/BGM이 있는 경우, WebGAL 게임 화면의 옵션에서 '모든 데이터 삭제'를 선택하여 초기화하세요.\",\"jj4R18\":\"힌트: 줄바꿈은 최대 3줄까지 가능합니다\",\"0XGtc4\":\"힌트: 이펙트는 다른 스탠딩으로 전환하거나 이전 스탠딩을 끄고 다시 추가할 때만 적용됩니다. 기존 스탠딩에 이펙트를 설정하려면 개별 이펙트 설정 명령을 사용하세요.\",\"U09jTA\":\"알림: 이펙트는 다른 배경으로 전환하거나 이전 배경을 끄고 다시 추가할 때만 적용됩니다. 기존 배경에 이펙트를 설정하려면 개별 이펙트 설정 명령을 사용하세요.\",\"edt8dJ\":\"알림: 스타일에 사용되는 리소스는 assets 목록 안에 배치하세요\",\"XtMbyn\":\"힌트 버튼 텍스트\",\"Gk7oil\":\"옵션 검색\",\"R/Ak5y\":\"비디오 클립 재생\",\"r5d14I\":\"효과음 클립 재생\",\"OM/lPn\":\"효과음 재생\",\"45SohF\":\"비디오 재생\",\"5oBbwZ\":\"조작\",\"LE7+P/\":\"조작 실패\",\"6H0Rzq\":\"글리치 영화 필터\",\"/07ndC\":\"이펙트\",\"3Dp9hq\":\"효과음\",\"2QqRJk\":\"이펙트 편집\",\"Itta+U\":\"이펙트 에디터\",\"9wRgLM\":\"이펙트 옵션\",\"N51b5K\":\"효과음 ID\",\"y21/fE\":\"효과음 ID (입력 시 효과음이 루프되며, 나중에 이 ID를 사용하여 끌 수 있습니다)\",\"LgZspE\":\"효과음 음량\",\"2KoYhh\":\"효과음 재생 상태\",\"PZAGAX\":\"효과음 파일\",\"hQpdKw\":\"파일 이름\",\"4vzm41\":\"파일 이름에 특수 기호를 포함할 수 없습니다\",\"exMMNn\":\"파일 크기\",\"UfcQun\":\"파일 경로에 잘못된 기호가 포함되어 있습니다\",\"ZqMdtM\":\"글자 크기\",\"25djyb\":\"텍스트가 모두 표시된 후 다음 문장을 실행\",\"Xe6iTK\":\"텍스트가 모두 표시된 후 대기\",\"AbDeuF\":\"글자 색상\",\"8WMg1Y\":\"텍스트 정렬\",\"xj/ZNd\":\"텍스트 표시 후 연속 실행\",\"nOB9MD\":\"텍스트 표시\",\"SfnBqH\":\"대화창\",\"eE+Ord\":\"대화창(미니 프로필 꺼짐)\",\"pnzpdc\":\"대화창 텍스트\",\"fdGpE4\":\"대화창 표시 상태\",\"nGBSca\":\"대화창 배경\",\"JMaNpp\":\"텍스트 길이(em)\",\"x9T6kF\":\"텍스트 그림자\",\"xbs2D9\":\"문서\",\"V2DE3t\":\"변수 추가\",\"+5qs/w\":\"새 폰트 추가\",\"LUK5zU\":\"새 파일\",\"126tQc\":\"새 폴더\",\"G7saQ/\":\"새 템플릿\",\"21csR9\":\"새 플로차트\",\"rXcvwy\":\"새 게임\",\"1pF7Zd\":\"새 파일 이름\",\"9z9vJ8\":\"새 폴더 이름\",\"/RuL7b\":\"새 탭 미리보기\",\"kg3joS\":\"새로운 템플릿\",\"IvrO4n\":\"새로운 게임\",\"v0fvoZ\":\"사각형\",\"sWI3tG\":\"내레이션 모드, 캐릭터 이름 없음\",\"ik1gpm\":\"회전 (호도)\",\"Kr6U3I\":\"회전(각도)\",\"XvTqzn\":\"없음\",\"OfjOK8\":\"일치하는 옵션 없음\",\"dVaVRr\":\"일본어\",\"SvylRi\":\"구버전 커스텀 엔진\",\"dgVFlc\":\"구버전 게임 목록\",\"jh/dy4\":\"구버전 커스텀 템플릿\",\"0n5tzF\":\"팝업 고정 여부\",\"DXvwOs\":\"커스텀 구분 기호 사용 여부\",\"6N+UWw\":[\"\\\"\",[\"0\"],\"\\\"을(를) 삭제하시겠습니까?\"],\"p4ahuY\":[\"\\\"\",[\"gameName\"],\"\\\"을(를) 삭제하시겠습니까?\"],\"VHPKr9\":\"사이드바 표시\",\"dz9ELq\":\"변수 표시\",\"dSlg2+\":\"이펙트 표시\",\"ZTwiNO\":\"대화창 표시\",\"3eT2Lc\":\"스탠딩 표시\",\"XKcypk\":\"배경 표시\",\"c6zSKZ\":\"캐릭터 이름 표시\",\"7oSWX7\":\"미리보기 창 표시\",\"N0xob0\":\"일반 대화\",\"EqrmU6\":\"변수 스냅샷 없음\",\"sK6GZl\":\"디버그 변수가 없습니다. “변수 추가”를 클릭하여 생성하세요.\",\"NFJ1K/\":\"추가 설정\",\"Bzk8JL\":\"더 많은 옵션\",\"HI9ajp\":\"추가 구성\",\"ZpRWXT\":\"UI 스타일 태그 교체\",\"XwMGie\":\"동일 대상 애니메이션 교체\",\"ZlhOAY\":\"지정되지 않음\",\"uGVPQd\":\"인식되지 않음\",\"PM5Ds/\":\"인식되지 않은 명령어\",\"OT2noP\":\"이동 대상이 선택되지 않았습니다\",\"HCmZmd\":\"이 문장 앞에 문장 삽입\",\"4jptfY\":\"이 문장 실행 후 다음 문장 실행\",\"1Qxsnf\":\"이 문장 실행 후 대기\",\"rSc/B7\":\"이 장면 내의 유일한 label 이름\",\"n5ywqz\":\"조건 실행\",\"cD7Nwy\":\"실시간 미리보기 스냅샷에서 가져옴\",\"QcDp1B\":\"WebGAL 홈페이지 가이드 보기\",\"4/5pw9\":\"이동된 문서 보기\",\"9qqIHt\":\"태그\",\"OcMqmU\":\"태그명\",\"bXluqY\":\"타이틀\",\"IBpbBo\":\"타이틀 예비 배경\",\"sm61Fq\":\"타이틀 버튼\",\"cCaoCQ\":\"타이틀 버튼 목록\",\"z6W0W9\":\"타이틀 버튼 텍스트\",\"efdGnC\":\"타이틀 버튼 텍스트 내부\",\"adOfww\":\"타이틀 버튼 텍스트 외부\",\"mHVFZb\":\"타이틀 버튼 비활성화\",\"0qw1qw\":\"타이틀 배경 이미지\",\"dF9IWl\":\"타이틀 배경음악\",\"h7RStd\":\"검증 실패 알림\",\"WGLiaE\":\"스타일 교체\",\"jz/awg\":\"루트 텍스트 길이 (rem)\",\"1+2L9V\":\"루트 노드\",\"K1AQKm\":[\"이동이 필요한 내용 감지됨: \",[\"0\"],\".\"],\"pVlW0S\":\"모드\",\"Z+KZ+3\":\"템플릿\",\"fcWwlS\":\"템플릿 목록\",\"LFlBFj\":\"템플릿 이름\",\"rox69+\":\"템플릿 이름은 비워둘 수 없습니다\",\"qplWc4\":\"템플릿 불러오기 실패. 이름이 중복되거나 템플릿이 완전한지 확인하세요\",\"To9mh/\":\"템플릿 불러오기 성공\",\"OC4/zY\":\"템플릿 목록\",\"mRirE0\":\"흐림\",\"AFjvIu\":\"벚꽃\",\"mOnIih\":\"정규식 태그\",\"xmuRsu\":\"업로드 중\",\"2cT+rp\":\"템플릿 구성을 불러오는 중\",\"kj9Nk9\":\"사용자 데이터 설정을 읽는 중\",\"IzVr3S\":\"정상\",\"ur/Lu4\":\"정상적으로 BGM 재생\",\"kSc8RE\":\"곱하기\",\"mVdAcK\":\"이 명령어는 게임을 종료시킵니다.\",\"lrj0ka\":\"각 플로차트에는 루트 노드를 하나만 둘 수 있습니다\",\"GS2FI3\":\"줄바꿈 안 함\",\"8hF8WJ\":\"프랑스어\",\"wmjh1W\":\"빛 번짐\",\"k3QEyx\":\"시선\",\"qN4lqc\":\"주시점 X(-1~1)\",\"jDe3iF\":\"주시점 Y(-1~1)\",\"cWSO8i\":\"주석\",\"lTDJms\":\"주석은 편집창에만 보이며, 게임 화면에 실행되지 않습니다\",\"HsqwrS\":\"플로차트 목록\",\"GUjz+8\":\"플로차트 기능(실험적)\",\"Rq1KKu\":\"플로차트 편집기\",\"zlIdoj\":\"밝은 색\",\"421vib\":\"브라우저가 목록를 선택했지만, 안전으로 인해 로컬 경로가 노출되지 않아 이동에 직접 사용할 수 없습니다. 경로를 수동으로 입력해 주세요.\",\"MzKPVG\":\"페이드 인/아웃\",\"zvy4g4\":\"어두운 색\",\"l3sUO5\":\"혼합 모드\",\"Btmkds\":\"추가\",\"e4Fwgn\":\"대화를 한 줄 추가하며 음성을 첨부할 수 있습니다\",\"HNs965\":\"CG 감상 인터페이스에 이미지 한 장 추가\",\"jqcHiz\":\"주석 한 줄 추가\",\"3b5CZ7\":\"음악 감상 인터페이스에 음악 한 곡 추가\",\"vlP6yy\":\"애니메이션 프레임 추가\",\"3TW1Bl\":\"속성 추가\",\"WWXT9D\":\"지정된 위치의 스탠딩을 추가하거나 전환합니다\",\"9tBJog\":\"새 줄 추가\",\"dem41Q\":\"루트 노드 추가\",\"ziI5AO\":\"커스텀 속성 추가\",\"1GdTCH\":\"노드 추가\",\"BmJh7o\":\"문장 추가\",\"ZhE+iM\":\"현재 스테이지의 이펙트 초기화\",\"zhmz6S\":\"알림 초기화\",\"Qhrqw8\":\"특수 효과 초기화\",\"vbktCH\":\"스탠딩 초기화\",\"cKm/RQ\":\"게임\",\"+yl443\":\"게임 목록\",\"MX3KTN\":\"게임 패키지 이름\",\"v1nnHf\":\"게임 이름\",\"/IxVmK\":\"게임 아이콘\",\"mG4Gl3\":\"게임 제어\",\"BN1ggT\":\"게임 목록\",\"PoJmXT\":\"게임 소개\",\"zw1gej\":\"게임 식별 코드\",\"nF64Xp\":\"게임 구성\",\"DFrvDB\":\"소스 코드\",\"sYmCuk\":\"스크린\",\"PWpJXC\":\"필터\",\"oTVGa5\":\"파일을 클릭하거나 여기로 드래그하여 업로드\",\"sXWAgH\":\"노드를 클릭하여 장면 파일 선택\",\"BxKZLo\":\"점박이 영화 필터\",\"Xj8vOB\":\"특수 효과 출처\",\"aQtyiH\":\"특수 효과 상태\",\"hD7QyJ\":\"특수 이펙트\",\"X1oR2i\":\"사용자 데이터\",\"DmxF7+\":\"사용자 데이터 위치\",\"EekhEj\":\"사용자 데이터 디렉터리\",\"OCkJC9\":\"사용자 데이터 마이그레이션\",\"KfhNrP\":\"사용자 디렉터리 모드\",\"Ypvisl\":\"이전 문장의 next에 의해 연속 실행\",\"EVx+TX\":\"시네마틱 모드\",\"ktyz3u\":\"시네마틱 모드\",\"I87OCc\":\"비워두면 기본값 사용\",\"c+IVlb\":\"백분율. 0-100 유효\",\"gi2fy8\":\"현재 열려 있는 파일이 없습니다\",\"7IcZx8\":\"대상\",\"AQ43u3\":\"대상 ID\",\"M+xcl2\":\"대상 줄\",\"gcRMK0\":\"상대적\",\"3NBJK7\":\"눈 깜박임\",\"8sFmsw\":\"눈 깜박임 간격 (밀리초)\",\"uROCuu\":\"눈 깜박임 간격 무작위 변화 (밀리초)\",\"yNBfEJ\":\"눈 뜨기\",\"ckvA2A\":\"눈 뜬 시간 (밀리초)\",\"6Ql6i3\":\"확인\",\"ErIaG5\":[\"\\\"\",[\"templateName\"],\"\\\" 템플릿을 삭제하시겠습니까?\"],\"snCgV5\":\"확인 버튼 텍스트\",\"ncHulm\":\"보기\",\"zKI8OE\":\"비주얼 노벨 편집, 그리고 진화\",\"l3Q20d\":\"비디오\",\"VbUGdf\":\"비디오 파일\",\"6QlMn6\":\"비디오 스킵\",\"6nUdFs\":\"스킵 금지\",\"+683uI\":\"비디오 스킵 금지\",\"23W6Wl\":\"비활성화\",\"h9pF1u\":\"즉시 주시\",\"ihAyIf\":\"스탠딩\",\"FcjaTr\":\"스탠딩 ID\",\"EYmzxD\":\"스탠딩 ID(선택 사항)\",\"1OhtrG\":\"스탠딩 위치\",\"K8GECw\":\"스탠딩 일러스트 ID\",\"RZ8SvI\":\"스탠딩 파일\",\"gMyE6M\":\"스탠딩 초기화 매개변수\",\"w6EWzr\":\"대기\",\"n1wZjH\":\"일정 시간 대기\",\"2qySjq\":\"대기 시간(단위: 밀리초)\",\"5/yex+\":\"중국어 간체\",\"/xmozg\":\"빨강 채널\",\"QFGCRR\":\"긴급 회피\",\"31sI1g\":\"텍스처\",\"4Ryv+h\":\"선형\",\"I5B8IR\":\"선형 닷지\",\"mIujq6\":\"선형 그라디언트\",\"BVUmrx\":\"종점 반동\",\"zfY9Wq\":\"종점 튕김\",\"7ccx+d\":\"종료 후 유지\",\"TL09Qo\":\"현재 BGM 재생 종료\",\"b6/zT3\":\"현재 게임을 종료하고 타이틀 화면으로 돌아갑니다\",\"nsMb6+\":\"게임 종료\",\"q1eJm6\":\"절대적\",\"PmBfdg\":\"기존 이펙트 유지\",\"ZuBRxU\":\"기본 이펙트 유지\",\"iLuKDS\":\"이어하기 버튼\",\"rVJOKr\":\"초록 채널\",\"EovAFg\":\"이즈 인\",\"IlRCMw\":\"이즈 인 아웃\",\"qJJ0ZL\":\"이즈 아웃\",\"TV73Uu\":\"이징 유형\",\"FIVBEw\":\"아이콘 편집\",\"ujrc0w\":\"더 많은 옵션 편집\",\"PIpq47\":\"템플릿 편집\",\"BUgUdY\":\"플로차트 편집\",\"f2Hsh4\":\"게임 편집\",\"AJlxRN\":\"편집 위젯\",\"2+9HNW\":\"중국어 번체\",\"9nARxd\":\"축소/확대\",\"ULLh8/\":\"그리드 보기\",\"ijy6uH\":\"옛날 영화 필터\",\"8tazQc\":\"소요 시간\",\"ync79W\":\"배경\",\"HtIv8c\":\"배경 이미지 위치\",\"EHsdeU\":\"배경 이미지 크기\",\"cZadEj\":\"배경 이미지\",\"BXVjkF\":\"배경 파일\",\"wtJEvW\":\"배경 표시 상태\",\"q8Wpux\":\"배경음악\",\"RxIR6z\":\"배경음악 파일\",\"pEsD4f\":\"배경 색상\",\"DjbmVo\":\"스크립트 에디터\",\"KI2Pfg\":\"자동\",\"tYkTRz\":\"플로차트 자동 저장\",\"zF11u5\":\"자동 줄바꿈\",\"mDoVSI\":\"리본 메뉴 자동 숨기기\",\"y1IFGJ\":\"커스텀\",\"7CJiec\":\"커스텀 Live2D 렌더링 범위\",\"uyE7Hp\":\"커스텀 값\",\"/inxt8\":\"커스텀 구분 기호\",\"Y9wTkP\":\"커스텀 특수 효과 이름\",\"aN6yDE\":\"커스텀 캐스케이드 선택기 구분 기호\",\"3xyq1o\":\"스테이지 객체 제어\",\"NBEJ5q\":\"스테이지 화면\",\"4Bo++T\":\"노드 작업\",\"1zf5DR\":\"영어\",\"oNrgZM\":\"최신 버전 가져오기\",\"8k5YaI\":\"사용자로부터 문자부호 입력을 획득\",\"BLtegj\":\"입력 가져오기\",\"9VjNqd\":\"파랑 채널\",\"4kyBmj\":\"줄 스크립트\",\"24OFWJ\":\"보완\",\"vVFcCc\":\"기본값 보충\",\"Vj0QFo\":\"표현식\",\"gP2Dzw\":\"자르기 모양\",\"GQZtyL\":[[\"0\"],\" 속성을 삭제하시겠습니까?\"],\"W05f7Q\":\"덮어쓰기\",\"w1aScd\":\"캐릭터 이름 (빈칸으로 두면 이전 문장에서 이어짐)\",\"6L6cDi\":\"캐릭터 이름, 이전 문장과 이으려면 빈칸:대화;\",\"Sad8Ov\":\"캐릭터 이름 내부 텍스트\",\"s3YC4c\":\"캐릭터 이름 처리\",\"01rrCD\":\"캐릭터 이름 외부 텍스트\",\"5CqTX3\":\"캐릭터 이름 용기\",\"TQWMFh\":\"캐릭터 이름 텍스트 창\",\"SaZFuD\":\"캐릭터 이름 텍스트 창 배경\",\"aSifLw\":\"캐릭터 이름 표시\",\"PcGPnf\":\"구석 미니 프로필\",\"PbRRLM\":\"명칭 해금\",\"L0LlwN\":\"업적 해금\",\"nm64JJ\":\"해금된 BGM 이름\",\"TEwxlP\":\"해금된 CG 이름\",\"rimhjz\":\"해금된 CG 또는 BGM 이름\",\"Co/7Bs\":\"해금 갤러리 유형\",\"flJD55\":\"설정\",\"epZj1P\":\"변수 설정\",\"XU5bCO\":\"일반 변수 또는 전체 변수 설정\",\"4Ikuoy\":\"스탠딩 또는 배경의 입/퇴장 애니메이션 설정\",\"ON+a2j\":\"커스텀 캐스케이드 선택기 구분 기호 설정 (입력 후 Enter 키를 눌러 확인)\",\"ihmbRH\":\"이 명령어는 텍스트 창에 현재 변수를 표시합니다.\",\"WufxGR\":\"이 명령어를 인식할 수 없습니다. 스크립트 편집 모드를 열어 수동으로 편집하세요.\",\"ZK+M59\":\"이 파일 유형은 미리보기를 지원하지 않습니다\",\"bJIN8W\":\"언어\",\"fVumBf\":\"음성\",\"X/tvHa\":\"음성 볼륨\",\"EG4Gpn\":\"각 폰트의 정보를 빠짐없이 입력해 주세요\",\"Zxehh6\":\"폰트 이름을 입력하거나 선택하세요\",\"0yFPGs\":\"검색어를 입력하세요...\",\"qw/kKq\":\"전경 이미지 조정\",\"7tVEwG\":\"배경 이미지 조정\",\"0eeLln\":\"Steam 호출\",\"908fto\":\"Steam API 호출, 다중 변수 지원\",\"AscWws\":\"장면 호출/전환\",\"A+Kvx5\":\"장면 파일을 호출하고, 종료 후 어버이 장면으로 돌아갑니다\",\"NM2PYS\":\"애니메이션 호출\",\"SFhdxy\":\"장면 호출\",\"sIqZen\":\"장면을 호출하며, 새 장면 종료 후 어버이 장면으로 돌아갑니다\",\"q2MDQ4\":\"엔진 내장 복잡 애니메이션 호출\",\"a9l6Ko\":\"디버그 변수\",\"rA5YEf\":\"리소스\",\"dIjPmP\":\"시점 종점 반동\",\"wYBRIj\":\"시점 종점 튕김\",\"Nw2SVE\":\"시점 반동\",\"FGpuUP\":\"시점 튕김\",\"rgKNR6\":\"문장 간 애니메이션\",\"r2iiTZ\":\"태그 이동\",\"gS/kSz\":\"장면으로 이동\",\"LcuzKM\":\"현재 장면 내의 태그으로 이동\",\"LQKR1b\":\"현재 장면 태그로 이동\",\"cjm0bm\":\"이동 태그\",\"zzTLpV\":\"부드럽게(0-1)\",\"CZvKht\":\"label 이름 입력\",\"6GZPH3\":\"임의의 태그 입력\",\"I4ELPC\":\"명령어 내용 입력\",\"/JO1a6\":\"현재 장면 내의 label 이름 입력\",\"T1KwXF\":\"검증 정규식 입력\",\"o6udFd\":\"대상 ID 입력\",\"zTyfb+\":\"이 목록으로 이동\",\"kRJCg/\":\"전환 시간(단위: 밀리초)\",\"wFPRfi\":\"파일 필터링\",\"cwfmFH\":\"실행 플랫폼\",\"SmBcVU\":\"뒤로\",\"1TOWH9\":\"입/퇴장 애니메이션\",\"tpiLRd\":\"연속 실행\",\"2wAUT7\":\"퇴장 시간(단위: 밀리초)\",\"dmA2LU\":\"노드나 연결선을 선택한 뒤 Backspace 또는 Delete 키를 누르면 삭제할 수 있습니다\",\"4IIuG6\":\"선택\",\"iAuIBv\":\"애니메이션 선택\",\"FkMdaZ\":\"애니메이션 파일 선택\",\"NbEGUP\":\"이미지 선택\",\"jNHP9q\":\"이미지 파일 선택\",\"OhgvlB\":\"장면 파일 선택\",\"zIJ3Yi\":\"미니 프로필 선택\",\"69M7Zv\":\"미니 프로필 파일 선택\",\"xHolc8\":\"적용할 템플릿 선택\",\"dmGYap\":\"효과음 파일 선택\",\"EPdKlK\":\"파일 선택\",\"38LIEV\":\"게임 엔진 버전 선택\",\"zlTAav\":\"디렉터리 선택\",\"Z8Z53Q\":\"비디오 파일 선택\",\"2I2ENa\":\"스탠딩 파일 선택\",\"72OHF4\":\"배경 이미지 선택\",\"b+7XSx\":\"배경 파일 선택\",\"AffInu\":\"배경음악 선택\",\"x2onXX\":\"음성 파일 선택\",\"ukXlZn\":\"호출 매개변수 선택\",\"q+LyW7\":\"점프 대상 선택\",\"VOdP8N\":\"입장 애니메이션 선택\",\"1j2242\":\"입장 애니메이션 파일 선택\",\"Ux++lj\":\"퇴장 애니메이션 선택\",\"SL5vhv\":\"퇴장 애니메이션 파일 선택\",\"OjVuTD\":\"갤러리 리소스 파일 선택\",\"bDEDWB\":\"사전 설정 대상 선택\",\"96JQhm\":\"색상 선택\",\"5uBl9m\":\"옵션\",\"pIlyJr\":\"옵션 목록\",\"3UJ7SP\":\"옵션 이름\",\"ionQHN\":\"선택지 버튼\",\"p4SMUC\":\"선택지 버튼(비활성화)\",\"hjiIez\":\"선택지 버튼 외층\",\"sM2BIi\":\"투명도(0-1)\",\"oNWnYZ\":\"일반\",\"o+zYhS\":\"선택지를 통해 다른 장면으로 진입합니다\",\"8uQYfH\":\"구성\",\"4GHpC0\":\"구성은 항상 통합된 .webgal_terre 디렉터리에 저장됩니다. 게임, 커스텀 템플릿, 커스텀 엔진은 다른 디스크로 이동 할 수 있습니다.\",\"9/7aYu\":\"템플릿 구성\",\"DwDd05\":\"설정 목록\",\"nlSg/s\":\"이름 바꾸기\",\"utQhKE\":\"템플릿 목록의 이름 바꾸기\",\"oLjZxR\":\"게임 목록의 이름 바꾸기\",\"NUIley\":\"감상(갤러리)\",\"YjDE9d\":\"갤러리\",\"VL0Lml\":\"이미지 감상\",\"tQwgmO\":\"감상 정렬\",\"N5WNMM\":\"시리즈 감상\",\"6jwl0g\":\"감상 리소스 파일\",\"IxlRXl\":\"음악 감상\",\"gvQ8W0\":\"장기\",\"Nfy7bQ\":\"장기 변수\",\"PZzhTE\":\"장기 변수 용도\",\"zqVxgb\":\"다문 입\",\"mlaDy3\":\"감은 눈\",\"YiB7xn\":\"눈 감은 시간(밀리초)\",\"DmRnQt\":\"임계값(Threshold)\",\"aP3Sr2\":\"사이드바 숨기기\",\"Xrn1+k\":\"대화창 숨기기\",\"Y6FOz1\":\"캐릭터 이름 숨기기\",\"Ero88N\":\"비\",\"u/Wc2v\":\"눈\",\"RTl+vt\":\"정적\",\"ioXuTK\":\"위쪽 정렬\",\"45SR3o\":\"아래쪽 정렬\",\"WK1sjs\":\"오른쪽 정렬\",\"tLjs3i\":\"왼쪽 정렬\",\"VpqMXM\":\"음악\",\"Sojbxt\":\"프로젝트 홈페이지\",\"/nWgLT\":\"사전 반전\",\"4P9ckh\":\"사전 설정 템플릿\",\"BGUvk3\":\"사전 설정 템플릿은 설치 디렉터리에 저장됩니다. 편집하려면 먼저 커스텀 템플릿으로 복사하세요.\",\"g4jjs+\":\"미리보기\",\"sOx1hS\":\"미리보기 아이콘\",\"sXJqG4\":\"미리보기 창\",\"L3omXQ\":\"색상\",\"2Jfux7\":\"색상 조정\",\"yjBCOs\":\"채도\",\"1I7Hpb\":\"높이\",\"kduvmj\":\"가우시안 흐림\",\"ozxBe2\":\"기본값\",\"5oYHM9\":\"기본값 default\",\"tsdOxl\":\"기본값\",\"LeACf8\":\"기본값 0\",\"fzevFI\":\"기본값 1\",\"o3LaEy\":\"기본값 100\",\"g8d5Vh\":\"기본값 1000\",\"O/vKJn\":\"기본값 150\",\"FOOWl6\":\"기본값 24시간\",\"Wdn4MI\":\"기본값 50\",\"wbrcoe\":\"기본 변환 및 효과\",\"BYO6xr\":\"기본 언어\",\"Nr4gXI\":\"기본 옵션\",\"4sxeqQ\":\"마우스 호버 스타일\",\"6Q3X1x\":\"마우스 포인터\",\"Zqpl0E\":\"마우스 클릭 스타일\"}")as Messages; \ No newline at end of file +/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"ez2arP\":\";주석\",\"ZfQJb6\":\"*스포이드를 설정한 후, 버튼을 클릭하여 변경 사항을 저장하세요\",\"9ITYjl\":\"%\",\"q0qRTC\":\"<0>새 템플릿 적용\",\"qTVI69\":[\"<0>현재 적용된 템플릿: \",[\"currentTemplateName\"],\"\"],\"+PwTIw\":\"1, 2, 3, ...\",\"eyqDdt\":\"4.6 사용자 데이터 이동\",\"YSvnx0\":\"applyStyle:TextBox_ShowName_Background->TextBox_ShowName_Background_Red;\",\"4O5JCB\":\"BGM\",\"etAybr\":\"BGM 재생 상태\",\"OtUIFf\":\"BGM 음량\",\"2YU2Kn\":\"bgm:배경음악 선택;\",\"Zjvu8X\":\"callScene:장면 파일 선택;\",\"TCV7GD\":\"callSteam: -achievementId=;\",\"FS5tux\":\"CG\",\"vESFMs\":\"changeBg:배경 이미지 선택;\",\"XfG9aQ\":\"changeFigure:스탠딩 파일 선택;\",\"jdJt3k\":\"changeScene:장면 파일 선택;\",\"+t6BAj\":\"choose:선택지:장면 파일 선택;\",\"YvwyIk\":\"CSS 속성값\",\"IiCbUY\":\"CSS 속성명:\",\"/oBAgV\":\"enable은 켜기, none 또는 비워서 끄기\",\"tBCPXB\":\"end;\",\"TJ8a+f\":\"filmMode:enable;\",\"IlFGfd\":\"font-family\",\"89SUCu\":\"getUserInput:;\",\"yZpvfb\":\"Intro 텍스트\",\"eIOW3x\":\"intro:;\",\"7DnAtq\":\"jumpLabel:label_1;\",\"aZ3bG5\":\"label:label_1;\",\"mHSbXE\":\"Live2D 모션\",\"ebsFnb\":\"Live2D 표정\",\"+Q2q6Q\":\"miniAvatar:미니 프로필 선택;\",\"wOFCxG\":\"pixiInit;\",\"VWZEcv\":\"pixiPerform:snow;\",\"Dxbz2N\":\"playEffect:효과음 파일 선택;\",\"xCHuyF\":\"playVideo:영상 파일 선택;\",\"V8p+bG\":\"Portable 모드\",\"/8PJ2M\":\"RGB 영화 필터\",\"Zrsplh\":\"setAnimation:애니메이션 파일 선택;\",\"Uxm0fk\":\"setComplexAnimation:universalSoftIn -target=fig-center;\",\"aHdzZT\":\"setTempAnimation:[{\\\"duration\\\":0},{\\\"duration\\\":500}];\",\"dfLJ5q\":\"setTextbox:hide;\",\"UYmTsP\":\"setTransform: -duration=0;\",\"RfGfVs\":\"setTransition:;\",\"74lZuk\":\"setVar:a=1;\",\"bTyzll\":\"showVars는 일반적으로 내용이 필요하지 않습니다\",\"8kbHea\":\"showVars;\",\"VV9dBJ\":\"Spine 애니메이션\",\"xOOpnF\":\"Spine 스킨\",\"RMdyHE\":\"Steam AppID\",\"LAI/Cy\":\"unlockBgm:;\",\"9afSwF\":\"unlockCg:;\",\"bIXYlr\":\"wait:1000;\",\"Z9I0PY\":\"WebGAL Standard\",\"o7ojN3\":\"WebGAL Terre 4.6 은 새로운 사용자 데이터 목록을 사용합니다. 구버전 데이터를 검사하거나 옮기는 것을 권장합니다.\",\"VAODzB\":\"X축 이동\",\"A/jVgG\":\"X축 비율\",\"UVJiw4\":\"Y축 이동\",\"0eNchO\":\"Y축 비율\",\"a6O/JX\":\"z-index\",\"TSQMsH\":\"리본 메뉴 항상 표시\",\"pu0x8w\":\"이전 단계\",\"Q/n6Ny\":\"업로드\",\"n4X166\":\"글씨체 파일 업로드\",\"eTyEPt\":\"리소스 업로드\",\"LIAXFC\":\"위로 이동\",\"4uRyNg\":\"다음 단계\",\"St/SJ5\":\"아래로 이동\",\"PP/Y5/\":\"clear 함수 사용하지 않음\",\"dOjUpV\":\"when 조건 사용하지 않음\",\"LYKall\":\"선택 불가\",\"y41keD\":\"템플릿을 적용하지 않음\",\"+H1Y3V\":\"이전 텍스트 창의 문장과 이어지지 않음\",\"7V3y7y\":\"순환 연결을 만들 수 없습니다\",\"R1CJC2\":\"설정 안 함\",\"nL7bt7\":\"동일 대상 애니메이션과 병행 실행\",\"IQKChW\":\"양끝 정렬\",\"jVh12T\":\"개의 글자\",\"mmLYLw\":\"중\",\"GTIc9/\":\"중앙\",\"hfB0wu\":\"중앙 스탠딩\",\"wz/sw7\":\"현재 스테이지에 이펙트 추가\",\"h1jPqc\":\"스탠딩 또는 배경 이미지에 단일 구간 애니메이션 이펙트 설정\",\"LpKCBj\":\"스탠딩 또는 배경 이미지에 다중 구간 애니메이션 이펙트 설정\",\"PI3ywW\":\"스탠딩 또는 배경 이미지에 애니메이션 불러오기 이펙트를 적용\",\"sWTv8x\":\"메인 플로차트는 삭제할 수 없습니다\",\"WC48YD\":\"메인 스타일\",\"qbow8i\":\"홈페이지\",\"hNFZVt\":\"테마\",\"jHJe9a\":\"밝기\",\"WmmLaN\":\"현재 장면에서 선택\",\"zA8esk\":\"코드 에디터\",\"7zsjb3\":\"다음 대상의 경로가 이미 존재하므로 덮어쓰지 않았습니다:\",\"QLZ4dX\":\"감마값\",\"Fas9Si\":\"clear 함수를 사용해 초기화\",\"uk3CXK\":\"사용 중인 템플릿:\",\"47+kxo\":\"엔진에 내장된 이펙트 사용\",\"/ySHoI\":\"본 태그 사용\",\"2cCMVu\":\"이펙트 사용\",\"f9y04E\":\"스탠딩 ID 사용\",\"GL8qtJ\":\"커스텀 특수 효과 사용\",\"IUCcDX\":\"프리셋 특수 효과 사용\",\"oWsDuj\":\"프리셋 대상을 사용하되, ID가 설정된 경우 적용되지 않습니다.\",\"NUlqj7\":\"프리셋 대상 사용\",\"jbVvhH\":\"예: -100,-100,100,100\",\"mUxmYb\":[\"예: ^.\",[\"regexRange\"],\"$\"],\"nwPnGm\":\"예: a=1 또는 name=WebGAL\",\"WCGfK0\":\"예: a>0 && flag==true\",\"IF2b07\":\"예: fig-center\",\"uyQChY\":\"예: i\",\"pxPdPB\":\"예: universalSoftIn\",\"bl9Beh\":\"사이드바\",\"rpBRee\":\"사이드바 게임 미리보기\",\"jBG25x\":\"저장\",\"aBwR6p\":\"저장 중...\",\"IbLq2X\":\"템플릿 구성 저장에 실패했습니다. 잠시후 다시 시도해 주세요\",\"h/Vi1t\":\"플로차트 저장\",\"GLd8LR\":\"눈 감기 유지(밀리초)\",\"e76V/v\":\"캐릭터 이름 유지\",\"c/sLTZ\":\"수정 시간\",\"SK6wGK\":\"게임 아이콘 수정\",\"h/QpEH\":\"색상 수정\",\"cBie7+\":\"모서리 깎기\",\"yWKT27\":\"값\",\"U7ZiGp\":\"값 또는 표현식\",\"Xr5Uul\":\"오프셋\",\"YWrmBt\":\"우측 오프셋 거리(위치 지정용)\",\"hQE+CE\":\"좌측 오프셋 거리(위치 지정용)\",\"8TBO/L\":\"하단 오프셋 거리(위치 지정용)\",\"BE5gDE\":\"상단 오프셋 거리(위치 지정용)\",\"sagY4I\":\"정지 위치\",\"GXB9zw\":\"픽셀\",\"0k7O3u\":\"스킵 허용\",\"XcY13T\":\"글로우 영화 필터\",\"gEFUMc\":\"입장 시간(단위: 밀리초)\",\"wCAGx4\":\"전역 변수\",\"T1h5rG\":\"전체 화면 텍스트\",\"ktxKKs\":\"전체 화면에 텍스트를 표시합니다. 독백이나 장면 도입부에 사용됩니다.\",\"sUqjHY\":\"모두 숨기기\",\"qFPINp\":\"모두 표시\",\"iw+qKq\":\"About\",\"/CTGfy\":\"연결된 스탠딩\",\"GS2Ofk\":\"닫기\",\"/IjfsU\":\"미니 프로필 끄기\",\"nsrfMl\":\"효과음 끄기\",\"wNuXVe\":\"배경 끄기\",\"gczUo5\":\"미리보기 창 닫기\",\"Ik81t6\":\"내용\",\"APFEOB\":\"내부 텍스트\",\"DfIXub\":\"안쪽 여백\",\"jnhO7t\":\"최대 너비\",\"NS1tk+\":\"최대 높이\",\"cnA59a\":\"최소 너비\",\"OlHHxv\":\"최소 높이\",\"txODIz\":\"최신 버전\",\"ZQEl0Y\":\"전역 변수 저장하기\",\"vEK29W\":\"변수에 저장하기\",\"coN/yH\":\"현재 세이브 변수에 저장하기\",\"71Cosn\":\"분기 선택\",\"40G9NY\":\"다른 장면 파일로 전환한 뒤, 현재 장면을 초기화\",\"qU64ut\":\"장면 전환\",\"aYsifP\":\"장면을 전환하며, 새 장면이 어버이 장면을 대체합니다\",\"7yX7g6\":\"인터페이스 언어 전환\",\"dvhHum\":\"스탠딩 전환\",\"sWeIVn\":\"배경 전환\",\"APcQQr\":\"배경 이미지 전환\",\"gMWuBH\":\"목록 확인\",\"lLPWZb\":\"생성\",\"gnSpxl\":\"사본 생성\",\"hEKmon\":\"새 템플릿 생성\",\"s2YbNh\":\"새 게임 생성\",\"tGxdbR\":\"삭제\",\"kq1kxQ\":\"속성 삭제\",\"ZlR4nz\":\"현재 문장 삭제\",\"R/0aSV\":\"템플릿 삭제\",\"kQ+WdA\":\"게임 삭제\",\"JpgJsu\":\"새로고침\",\"wRZsHb\":\"게임 새로고침\",\"MSHXWR\":\"전경\",\"zkD1td\":\"리본 메뉴 표시\",\"gNEHCz\":\"굵게\",\"BuTBJb\":\"애니메이션\",\"B1cXA5\":\"애니메이션 이름\",\"zy14kX\":\"애니메이션 프레임 제어\",\"9cawOO\":\"반쯤 벌린 입\",\"rYZPFI\":\"단위는 밀리초. 0~ 유효\",\"pM1UIg\":\"단일 애니메이션\",\"A48Tay\":\"한 줄 주석\",\"UFVfv0\":\"곧 사용자 데이터를 새로운 목록으로 이동합니다. 파일이 많을 경우 시간이 걸릴 수 있으므로, 완료될 때까지 WebGAL Terre를 닫지 마세요.\",\"T2JfAd\":\"사용자 데이터를 기본 목록으로 되돌립니다. 파일이 많을 경우 시간이 걸릴 수 있습니다.\",\"o0fQFX\":\"두께\",\"JZjf/Y\":\"기존 스타일->새 스타일, 여러 개는 쉼표로 구분\",\"4m2dYU\":\"파라미터 값\",\"KJZS3U\":\"노드 이름을 두 번 클릭하면 편집할 수 있습니다\",\"CDuvSn\":\"반사 영화 필터\",\"hHGCZw\":\"배포된 게임은 <0>https://www.openwebgal.com/games/ 에 전시됩니다. 여러분의 작품을 공유해 주세요!\",\"cYUc0S\":\"새 버전 발견\",\"jxD+iQ\":\"취소\",\"bbdi6l\":\"고정 해제\",\"OcQJ3O\":\"변형\",\"MiE0Mz\":\"변수명\",\"iZhOyz\":\"변수 표현식\",\"xg8k+a\":\"노드만 표시하고 이름은 표시하지 않음\",\"WJg1Su\":\"설정에서 휴지통을 끈 뒤 다시 시도할 수 있습니다. 이 경우 삭제한 항목은 복구할 수 없습니다.\",\"HER8aZ\":\"$0를 사용하여 현재 입력 표시 가능\",\"A27Z7l\":\"스크립트 간에 무한 루프가 있거나 대상 줄 이전에 빨리 감기해야 할 행수가 너무 많을 수 있습니다.\",\"nOgzPD\":\"선택 가능 항목\",\"oeUweH\":\"우측\",\"mL9DmJ\":\"우측 스탠딩\",\"23nU8q\":\"우측 정렬\",\"upWPde\":\"위로 추가\",\"7YMtbN\":\"배경음악 재생, 전환 또는 정지\",\"V9IP5s\":\"시작 이미지\",\"xqo5T2\":\"활성\",\"qPjXK6\":\"when 조건 활성화\",\"4l0zVt\":\"비디오 건너뛰기 활성화\",\"JZA2nJ\":\"립싱크 및 눈 깜박임\",\"MUwa78\":\"휴지통\",\"32wTLf\":\"고정 팝업\",\"uv5w6v\":\"이미지\",\"aPiTPQ\":\"그래픽 에디터\",\"t5JJx+\":\"그림\",\"ruqLSO\":\"이미지 차분\",\"sltuoL\":\"원형\",\"s6jLFn\":\"원형 이즈 인\",\"0O4ucF\":\"원형 이즈 인 아웃\",\"RDWZpZ\":\"원형 이즈 아웃\",\"TMErca\":\"둥근 모서리\",\"JzRUNZ\":\"둥근 사각형\",\"1Vx+fK\":\"대화창 좌측 하단에 미니 프로필 표시\",\"BOBeYL\":\"현재 장면 내에 이동 태그 생성\",\"lKHSrA\":\"파일 탐색기에서 열기\",\"Hia9ZF\":\"새 탭에서 미리보기\",\"n/RuiJ\":\"게임 내에서 현재 변수 표시\",\"EBxZ+B\":\"여기에 편집 위젯 배치\",\"78nH85\":\"장면\",\"Wh0Wxu\":\"장면 및 분기\",\"HFYGwW\":\"장면 파일\",\"RuqGFg\":\"파라미터 값 작성\",\"E8v2tX\":\"복잡 애니메이션\",\"l1axJE\":\"복잡 애니메이션 이름은 엔진에 내장된 애니메이션에서 가져옵니다. 프로젝트 애니메이션 파일을 사용하려면 일반적으로 '애니메이션 불러오기'을 사용해야 합니다.\",\"Tw6+I6\":\"외부 텍스트\",\"Rw2ZoI\":\"바깥 여백\",\"Rg0rou\":\"다중 구간 애니메이션\",\"Rnjd5I\":\"대\",\"DYpFps\":\"폭설\",\"dIsNg+\":\"커스텀 템플릿 사용으로 인해 미리보기에 이상이 발생할 경우, 이 옵션을 끄시길 바랍니다.\",\"5npXGU\":\"폰트\",\"IBB/4V\":\"폰트 최적화\",\"HyXaZd\":\"폰트 최적화는 게임 미리보기 창이 특정 크기 이하일때 가독성을 높이기 위해 텍스트 테두리를 비활성화합니다.\",\"nCmVP2\":\"글자 크기\",\"zAiOiy\":\"폰트 파일 URL\",\"GqjJWX\":\"폰트 파일 업로드에 실패했습니다. 다시 시도해 주세요\",\"NCX3JP\":\"폰트 유형\",\"eWhPhP\":\"폰트 구성\",\"+Nelz8\":\"글자 간격\",\"bPcq/h\":\"글꼴 두께\",\"51Vz47\":\"위치 지정 방식\",\"GWpHcc\":\"실시간 미리보기\",\"t+sLG2\":\"실시간 미리보기 꺼짐\",\"QQLO2n\":\"실시간 미리보기가 중지되었습니다\",\"306dJv\":\"실시간 미리보기 시작 시 자동으로 주입되며, 실제 게임에서는 실행되지 않습니다.\",\"kXwAov\":\"실시간 미리보기 빨리 감기가 제한 시간을 초과하여 연산을 중단했습니다.\",\"L8MJAS\":\"실시간 미리보기 켜짐\",\"EVMosY\":\"실험적 빠른 미리보기\",\"CPbMQ8\":\"너비\",\"Z8j8l6\":\"대비\",\"WuipOX\":\"대화\",\"/FOeko\":\"대화창 텍스트\",\"OLxIGA\":\"대화창 제목\",\"o2RHlj\":\"템플릿 불러오기\",\"v6FJm2\":\"내보내기\",\"RQ9guE\":\"내보내는 중...\",\"XkkDWm\":\"실행 파일로 내보내기\",\"KyQ6e8\":\"Android 프로젝트 파일로 내보내기\",\"HDLXqZ\":\"웹페이지로 내보내기\",\"2mK/uv\":\"내보내기 실패!\",\"vZo1qG\":\"내보내기 성공!\",\"Ra5S96\":\"템플릿 내보내기\",\"ztfrH+\":\"현재 템플릿을 선택한 게임에 적용\",\"3eNAFJ\":\"텍스트 창의 텍스트를 읽음으로 설정\",\"5yRaVL\":\"텍스트 창의 텍스트를 읽지 않음으로 설정\",\"Diw4et\":\"소\",\"4BzOtt\":\"미니 프로필 이미지\",\"f0BISX\":\"미니 프로필 용기\",\"1nPKgD\":\"미니 프로필 파일\",\"Bpp7ke\":\"미니 프로필 표시 상태\",\"0iXI5f\":\"아직 폰트가 추가되지 않았습니다\",\"ugHPWR\":\"아직 구성된 폰트가 없습니다\",\"6754WP\":\"계층 순서\",\"vjbngg\":\"가운데 정렬\",\"lL0plW\":\"가운데 맞춤\",\"lxKfe1\":\"미니 프로필 표시\",\"kp6HDd\":\"잠금 해제되지 않은 노드 표시\",\"KoFqVI\":\"속성\",\"KHw8dl\":\"왼쪽\",\"+b1cs9\":\"좌측 스탠딩\",\"U/hj+N\":\"좌측 정렬\",\"Tc1V/S\":\"생성됨\",\"BUYNnD\":\"활성화됨: 삭제 작업은 파일을 휴지통으로 이동합니다\",\"RO1pYQ\":[[\"0\"],\" 파일 또는 폴더가 이미 존재합니다. 다른 이름을 입력해주세요\"],\"/ctKDE\":\"빨리 감기한 줄 수\",\"W7nUsD\":\"비활성화됨: 삭제 작업은 파일을 영구 삭제합니다\",\"i5KfKE\":\"읽은 텍스트\",\"/riza6\":\"읽은 텍스트 내부\",\"KzGK3I\":\"읽은 텍스트 외부\",\"tBIZt9\":\"도움말\",\"k0vDpg\":\"도움말 문서\",\"Ysz9K8\":\"자주 쓰는 설정\",\"FfZSjF\":\"기본 연출\",\"AM6qht\":\"타일 배열\",\"84Kfga\":\"병렬 애니메이션\",\"7P0aRQ\":\"적용\",\"Hzp94p\":\"스타일 적용\",\"5IGQoe\":\"적용된 템플릿\",\"wTGMhd\":\"색상 변경 적용\",\"K8lqFB\":\"지연 시간(초)\",\"TAiYqj\":\"켜기\",\"GGYg+t\":\"시작\",\"jZlQQB\":\"이동 시작\",\"CJMpjt\":\"벌린 입\",\"sTLh1u\":\"강도\",\"bcDpsn\":\"현재 장면\",\"eWKbZk\":\"현재 장면에 선택 가능한 태그가 없습니다\",\"HxzEI9\":\"현재 portable 모드이며, 실제 데이터 목록은 다운로드 목록 내의 data 폴더에 의해 결정됩니다.\",\"dD/c/t\":\"이동 가능한 내용을 감지하지 못했지만, 새 버전은 이미 사용자 데이터 목록으로 전환되었습니다.\",\"YS+bsM\":\"현재 버전\",\"/eHs6F\":\"현재 상태\",\"sogedR\":\"현재 환경은 시스템 파일 탐색기를 지원하지 않습니다. 경로를 수동으로 입력해 주세요.\",\"/K/Jca\":\"현재 미리보기 변수\",\"BQWdVc\":\"독일어\",\"AtV2zZ\":\"빠른 미리보기 효과\",\"iXWuW8\":\"빠른 미리보기 효과 꺼짐\",\"MI1wom\":\"빠른 미리보기 효과 켜짐\",\"4fasly\":\"빠른 미리보기 문장\",\"It2H0T\":\"빠른 미리보기 문장 꺼짐\",\"SPZNPb\":\"빠른 미리보기 문장 켜짐\",\"HJOSnW\":\"무시\",\"LgENDV\":\"이 버전 무시\",\"eTTofS\":\"기본 목록으로 복원\",\"NfnpFD\":\"게임 삭제를 확정하겠습니다.\",\"Beao/k\":\"수동으로 다음 문장을 표시\",\"Rt57iW\":\"ID 수동 입력\",\"1rlyyl\":\"다운로드 목록을 열기\",\"jpPLGO\":\"전체 설정 열기\",\"aiJY5K\":\"영화 모드 켜기/끄기\",\"c+tywy\":\"이펙트 에디터 열기\",\"KFUqJk\":\"사용자 데이터 목록 열기\",\"bpjf7e\":\"설정 열기\",\"brKARj\":\"설정 디렉터리 열기\",\"A1LFzH\":\"이 문장까지 실행\",\"M/0TFg\":\"실행 후 캐릭터 이름 초기화\",\"NXEIyt\":\"확장명\",\"IVbgAn\":\"드래그하여 변형 조정(빠른 미리보기 효과를 켜는 것을 권장합니다)\",\"I2S29O\":\"이전 텍스트 창 내의 문장과 이어 붙이기\",\"4zkD4P\":\"연결 모드\",\"80W7XM\":\"지속 시간(밀리초)\",\"Yu/zJb\":\"포인터\",\"GL6/RW\":\"정렬 방식\",\"zY4737\":\"텍스트 창 표시 여부 제어\",\"qZOPAd\":\"제출\",\"z+vCyE\":\"수정 제출\",\"5tNImB\":\"힌트\",\"lSDxhM\":\"힌트:\",\"a+Vt/6\":\"힌트: 스탠딩/배경을 먼저 설정한 후 애니메이션을 적용하세요. 아니면 대상을 찾을 수 없습니다.\",\"AxfvpJ\":\"힌트: 편집을 끝낸 후 유효하지 않은 CG/BGM이 있는 경우, WebGAL 게임 화면의 옵션에서 '모든 데이터 삭제'를 선택하여 초기화하세요.\",\"jj4R18\":\"힌트: 줄바꿈은 최대 3줄까지 가능합니다\",\"0XGtc4\":\"힌트: 이펙트는 다른 스탠딩으로 전환하거나 이전 스탠딩을 끄고 다시 추가할 때만 적용됩니다. 기존 스탠딩에 이펙트를 설정하려면 개별 이펙트 설정 명령을 사용하세요.\",\"U09jTA\":\"알림: 이펙트는 다른 배경으로 전환하거나 이전 배경을 끄고 다시 추가할 때만 적용됩니다. 기존 배경에 이펙트를 설정하려면 개별 이펙트 설정 명령을 사용하세요.\",\"edt8dJ\":\"알림: 스타일에 사용되는 리소스는 assets 목록 안에 배치하세요\",\"XtMbyn\":\"힌트 버튼 텍스트\",\"Gk7oil\":\"옵션 검색\",\"R/Ak5y\":\"비디오 클립 재생\",\"r5d14I\":\"효과음 클립 재생\",\"OM/lPn\":\"효과음 재생\",\"45SohF\":\"비디오 재생\",\"5oBbwZ\":\"조작\",\"LE7+P/\":\"조작 실패\",\"6H0Rzq\":\"글리치 영화 필터\",\"/07ndC\":\"이펙트\",\"3Dp9hq\":\"효과음\",\"2QqRJk\":\"이펙트 편집\",\"Itta+U\":\"이펙트 에디터\",\"9wRgLM\":\"이펙트 옵션\",\"N51b5K\":\"효과음 ID\",\"y21/fE\":\"효과음 ID (입력 시 효과음이 루프되며, 나중에 이 ID를 사용하여 끌 수 있습니다)\",\"LgZspE\":\"효과음 음량\",\"2KoYhh\":\"효과음 재생 상태\",\"PZAGAX\":\"효과음 파일\",\"hQpdKw\":\"파일 이름\",\"4vzm41\":\"파일 이름에 특수 기호를 포함할 수 없습니다\",\"exMMNn\":\"파일 크기\",\"UfcQun\":\"파일 경로에 잘못된 기호가 포함되어 있습니다\",\"ZqMdtM\":\"글자 크기\",\"25djyb\":\"텍스트가 모두 표시된 후 다음 문장을 실행\",\"Xe6iTK\":\"텍스트가 모두 표시된 후 대기\",\"AbDeuF\":\"글자 색상\",\"8WMg1Y\":\"텍스트 정렬\",\"xj/ZNd\":\"텍스트 표시 후 연속 실행\",\"nOB9MD\":\"텍스트 표시\",\"SfnBqH\":\"대화창\",\"eE+Ord\":\"대화창(미니 프로필 꺼짐)\",\"pnzpdc\":\"대화창 텍스트\",\"fdGpE4\":\"대화창 표시 상태\",\"nGBSca\":\"대화창 배경\",\"JMaNpp\":\"텍스트 길이(em)\",\"x9T6kF\":\"텍스트 그림자\",\"xbs2D9\":\"문서\",\"V2DE3t\":\"변수 추가\",\"+5qs/w\":\"새 폰트 추가\",\"LUK5zU\":\"새 파일\",\"126tQc\":\"새 폴더\",\"G7saQ/\":\"새 템플릿\",\"21csR9\":\"새 플로차트\",\"rXcvwy\":\"새 게임\",\"1pF7Zd\":\"새 파일 이름\",\"9z9vJ8\":\"새 폴더 이름\",\"/RuL7b\":\"새 탭 미리보기\",\"kg3joS\":\"새로운 템플릿\",\"IvrO4n\":\"새로운 게임\",\"v0fvoZ\":\"사각형\",\"sWI3tG\":\"내레이션 모드, 캐릭터 이름 없음\",\"ik1gpm\":\"회전 (호도)\",\"Kr6U3I\":\"회전(각도)\",\"XvTqzn\":\"없음\",\"OfjOK8\":\"일치하는 옵션 없음\",\"dVaVRr\":\"일본어\",\"SvylRi\":\"구버전 커스텀 엔진\",\"dgVFlc\":\"구버전 게임 목록\",\"jh/dy4\":\"구버전 커스텀 템플릿\",\"0n5tzF\":\"팝업 고정 여부\",\"DXvwOs\":\"커스텀 구분 기호 사용 여부\",\"6N+UWw\":[\"\\\"\",[\"0\"],\"\\\"을(를) 삭제하시겠습니까?\"],\"p4ahuY\":[\"\\\"\",[\"gameName\"],\"\\\"을(를) 삭제하시겠습니까?\"],\"VHPKr9\":\"사이드바 표시\",\"dz9ELq\":\"변수 표시\",\"dSlg2+\":\"이펙트 표시\",\"ZTwiNO\":\"대화창 표시\",\"3eT2Lc\":\"스탠딩 표시\",\"XKcypk\":\"배경 표시\",\"c6zSKZ\":\"캐릭터 이름 표시\",\"7oSWX7\":\"미리보기 창 표시\",\"N0xob0\":\"일반 대화\",\"EqrmU6\":\"변수 스냅샷 없음\",\"sK6GZl\":\"디버그 변수가 없습니다. “변수 추가”를 클릭하여 생성하세요.\",\"NFJ1K/\":\"추가 설정\",\"Bzk8JL\":\"더 많은 옵션\",\"HI9ajp\":\"추가 구성\",\"ZpRWXT\":\"UI 스타일 태그 교체\",\"XwMGie\":\"동일 대상 애니메이션 교체\",\"ZlhOAY\":\"지정되지 않음\",\"uGVPQd\":\"인식되지 않음\",\"PM5Ds/\":\"인식되지 않은 명령어\",\"OT2noP\":\"이동 대상이 선택되지 않았습니다\",\"HCmZmd\":\"이 문장 앞에 문장 삽입\",\"4jptfY\":\"이 문장 실행 후 다음 문장 실행\",\"1Qxsnf\":\"이 문장 실행 후 대기\",\"rSc/B7\":\"이 장면 내의 유일한 label 이름\",\"n5ywqz\":\"조건 실행\",\"cD7Nwy\":\"실시간 미리보기 스냅샷에서 가져옴\",\"QcDp1B\":\"WebGAL 홈페이지 가이드 보기\",\"4/5pw9\":\"이동된 문서 보기\",\"9qqIHt\":\"태그\",\"OcMqmU\":\"태그명\",\"bXluqY\":\"타이틀\",\"IBpbBo\":\"타이틀 예비 배경\",\"sm61Fq\":\"타이틀 버튼\",\"cCaoCQ\":\"타이틀 버튼 목록\",\"z6W0W9\":\"타이틀 버튼 텍스트\",\"efdGnC\":\"타이틀 버튼 텍스트 내부\",\"adOfww\":\"타이틀 버튼 텍스트 외부\",\"mHVFZb\":\"타이틀 버튼 비활성화\",\"0qw1qw\":\"타이틀 배경 이미지\",\"dF9IWl\":\"타이틀 배경음악\",\"h7RStd\":\"검증 실패 알림\",\"WGLiaE\":\"스타일 교체\",\"jz/awg\":\"루트 텍스트 길이 (rem)\",\"1+2L9V\":\"루트 노드\",\"K1AQKm\":[\"이동이 필요한 내용 감지됨: \",[\"0\"],\".\"],\"pVlW0S\":\"모드\",\"Z+KZ+3\":\"템플릿\",\"fcWwlS\":\"템플릿 목록\",\"LFlBFj\":\"템플릿 이름\",\"rox69+\":\"템플릿 이름은 비워둘 수 없습니다\",\"qplWc4\":\"템플릿 불러오기 실패. 이름이 중복되거나 템플릿이 완전한지 확인하세요\",\"To9mh/\":\"템플릿 불러오기 성공\",\"OC4/zY\":\"템플릿 목록\",\"mRirE0\":\"흐림\",\"AFjvIu\":\"벚꽃\",\"mOnIih\":\"정규식 태그\",\"xmuRsu\":\"업로드 중\",\"2cT+rp\":\"템플릿 구성을 불러오는 중\",\"kj9Nk9\":\"사용자 데이터 설정을 읽는 중\",\"IzVr3S\":\"정상\",\"ur/Lu4\":\"정상적으로 BGM 재생\",\"kSc8RE\":\"곱하기\",\"mVdAcK\":\"이 명령어는 게임을 종료시킵니다.\",\"lrj0ka\":\"각 플로차트에는 루트 노드를 하나만 둘 수 있습니다\",\"GS2FI3\":\"줄바꿈 안 함\",\"8hF8WJ\":\"프랑스어\",\"wmjh1W\":\"빛 번짐\",\"k3QEyx\":\"시선\",\"qN4lqc\":\"주시점 X(-1~1)\",\"jDe3iF\":\"주시점 Y(-1~1)\",\"cWSO8i\":\"주석\",\"lTDJms\":\"주석은 편집창에만 보이며, 게임 화면에 실행되지 않습니다\",\"HsqwrS\":\"플로차트 목록\",\"GUjz+8\":\"플로차트 기능(실험적)\",\"Rq1KKu\":\"플로차트 편집기\",\"zlIdoj\":\"밝은 색\",\"421vib\":\"브라우저가 목록를 선택했지만, 안전으로 인해 로컬 경로가 노출되지 않아 이동에 직접 사용할 수 없습니다. 경로를 수동으로 입력해 주세요.\",\"MzKPVG\":\"페이드 인/아웃\",\"zvy4g4\":\"어두운 색\",\"l3sUO5\":\"혼합 모드\",\"Btmkds\":\"추가\",\"e4Fwgn\":\"대화를 한 줄 추가하며 음성을 첨부할 수 있습니다\",\"HNs965\":\"CG 감상 인터페이스에 이미지 한 장 추가\",\"jqcHiz\":\"주석 한 줄 추가\",\"3b5CZ7\":\"음악 감상 인터페이스에 음악 한 곡 추가\",\"vlP6yy\":\"애니메이션 프레임 추가\",\"3TW1Bl\":\"속성 추가\",\"WWXT9D\":\"지정된 위치의 스탠딩을 추가하거나 전환합니다\",\"9tBJog\":\"새 줄 추가\",\"dem41Q\":\"루트 노드 추가\",\"ziI5AO\":\"커스텀 속성 추가\",\"1GdTCH\":\"노드 추가\",\"BmJh7o\":\"문장 추가\",\"ZhE+iM\":\"현재 스테이지의 이펙트 초기화\",\"zhmz6S\":\"알림 초기화\",\"Qhrqw8\":\"특수 효과 초기화\",\"vbktCH\":\"스탠딩 초기화\",\"cKm/RQ\":\"게임\",\"+yl443\":\"게임 목록\",\"MX3KTN\":\"게임 패키지 이름\",\"v1nnHf\":\"게임 이름\",\"/IxVmK\":\"게임 아이콘\",\"mG4Gl3\":\"게임 제어\",\"BN1ggT\":\"게임 목록\",\"PoJmXT\":\"게임 소개\",\"zw1gej\":\"게임 식별 코드\",\"nF64Xp\":\"게임 구성\",\"DFrvDB\":\"소스 코드\",\"sYmCuk\":\"스크린\",\"PWpJXC\":\"필터\",\"oTVGa5\":\"파일을 클릭하거나 여기로 드래그하여 업로드\",\"sXWAgH\":\"노드를 클릭하여 장면 파일 선택\",\"BxKZLo\":\"점박이 영화 필터\",\"Xj8vOB\":\"특수 효과 출처\",\"aQtyiH\":\"특수 효과 상태\",\"hD7QyJ\":\"특수 이펙트\",\"X1oR2i\":\"사용자 데이터\",\"DmxF7+\":\"사용자 데이터 위치\",\"EekhEj\":\"사용자 데이터 디렉터리\",\"OCkJC9\":\"사용자 데이터 마이그레이션\",\"KfhNrP\":\"사용자 디렉터리 모드\",\"Ypvisl\":\"이전 문장의 next에 의해 연속 실행\",\"EVx+TX\":\"시네마틱 모드\",\"ktyz3u\":\"시네마틱 모드\",\"I87OCc\":\"비워두면 기본값 사용\",\"c+IVlb\":\"백분율. 0-100 유효\",\"gi2fy8\":\"현재 열려 있는 파일이 없습니다\",\"7IcZx8\":\"대상\",\"AQ43u3\":\"대상 ID\",\"M+xcl2\":\"대상 줄\",\"gcRMK0\":\"상대적\",\"3NBJK7\":\"눈 깜박임\",\"8sFmsw\":\"눈 깜박임 간격 (밀리초)\",\"uROCuu\":\"눈 깜박임 간격 무작위 변화 (밀리초)\",\"yNBfEJ\":\"눈 뜨기\",\"ckvA2A\":\"눈 뜬 시간 (밀리초)\",\"6Ql6i3\":\"확인\",\"ErIaG5\":[\"\\\"\",[\"templateName\"],\"\\\" 템플릿을 삭제하시겠습니까?\"],\"snCgV5\":\"확인 버튼 텍스트\",\"ncHulm\":\"보기\",\"zKI8OE\":\"비주얼 노벨 편집, 그리고 진화\",\"l3Q20d\":\"비디오\",\"VbUGdf\":\"비디오 파일\",\"6QlMn6\":\"비디오 스킵\",\"6nUdFs\":\"스킵 금지\",\"+683uI\":\"비디오 스킵 금지\",\"23W6Wl\":\"비활성화\",\"+xsk+g\":\"휴지통으로 이동하지 못했습니다\",\"h9pF1u\":\"즉시 주시\",\"ihAyIf\":\"스탠딩\",\"FcjaTr\":\"스탠딩 ID\",\"EYmzxD\":\"스탠딩 ID(선택 사항)\",\"1OhtrG\":\"스탠딩 위치\",\"K8GECw\":\"스탠딩 일러스트 ID\",\"RZ8SvI\":\"스탠딩 파일\",\"gMyE6M\":\"스탠딩 초기화 매개변수\",\"w6EWzr\":\"대기\",\"n1wZjH\":\"일정 시간 대기\",\"2qySjq\":\"대기 시간(단위: 밀리초)\",\"5/yex+\":\"중국어 간체\",\"/xmozg\":\"빨강 채널\",\"QFGCRR\":\"긴급 회피\",\"31sI1g\":\"텍스처\",\"4Ryv+h\":\"선형\",\"I5B8IR\":\"선형 닷지\",\"mIujq6\":\"선형 그라디언트\",\"BVUmrx\":\"종점 반동\",\"zfY9Wq\":\"종점 튕김\",\"7ccx+d\":\"종료 후 유지\",\"TL09Qo\":\"현재 BGM 재생 종료\",\"b6/zT3\":\"현재 게임을 종료하고 타이틀 화면으로 돌아갑니다\",\"nsMb6+\":\"게임 종료\",\"q1eJm6\":\"절대적\",\"PmBfdg\":\"기존 이펙트 유지\",\"ZuBRxU\":\"기본 이펙트 유지\",\"iLuKDS\":\"이어하기 버튼\",\"rVJOKr\":\"초록 채널\",\"EovAFg\":\"이즈 인\",\"IlRCMw\":\"이즈 인 아웃\",\"qJJ0ZL\":\"이즈 아웃\",\"TV73Uu\":\"이징 유형\",\"FIVBEw\":\"아이콘 편집\",\"ujrc0w\":\"더 많은 옵션 편집\",\"PIpq47\":\"템플릿 편집\",\"BUgUdY\":\"플로차트 편집\",\"f2Hsh4\":\"게임 편집\",\"AJlxRN\":\"편집 위젯\",\"2+9HNW\":\"중국어 번체\",\"9nARxd\":\"축소/확대\",\"ULLh8/\":\"그리드 보기\",\"ijy6uH\":\"옛날 영화 필터\",\"8tazQc\":\"소요 시간\",\"ync79W\":\"배경\",\"HtIv8c\":\"배경 이미지 위치\",\"EHsdeU\":\"배경 이미지 크기\",\"cZadEj\":\"배경 이미지\",\"BXVjkF\":\"배경 파일\",\"wtJEvW\":\"배경 표시 상태\",\"q8Wpux\":\"배경음악\",\"RxIR6z\":\"배경음악 파일\",\"pEsD4f\":\"배경 색상\",\"DjbmVo\":\"스크립트 에디터\",\"KI2Pfg\":\"자동\",\"tYkTRz\":\"플로차트 자동 저장\",\"zF11u5\":\"자동 줄바꿈\",\"mDoVSI\":\"리본 메뉴 자동 숨기기\",\"y1IFGJ\":\"커스텀\",\"7CJiec\":\"커스텀 Live2D 렌더링 범위\",\"uyE7Hp\":\"커스텀 값\",\"/inxt8\":\"커스텀 구분 기호\",\"Y9wTkP\":\"커스텀 특수 효과 이름\",\"aN6yDE\":\"커스텀 캐스케이드 선택기 구분 기호\",\"3xyq1o\":\"스테이지 객체 제어\",\"NBEJ5q\":\"스테이지 화면\",\"4Bo++T\":\"노드 작업\",\"1zf5DR\":\"영어\",\"oNrgZM\":\"최신 버전 가져오기\",\"8k5YaI\":\"사용자로부터 문자부호 입력을 획득\",\"BLtegj\":\"입력 가져오기\",\"9VjNqd\":\"파랑 채널\",\"4kyBmj\":\"줄 스크립트\",\"24OFWJ\":\"보완\",\"vVFcCc\":\"기본값 보충\",\"Vj0QFo\":\"표현식\",\"gP2Dzw\":\"자르기 모양\",\"GQZtyL\":[[\"0\"],\" 속성을 삭제하시겠습니까?\"],\"W05f7Q\":\"덮어쓰기\",\"w1aScd\":\"캐릭터 이름 (빈칸으로 두면 이전 문장에서 이어짐)\",\"6L6cDi\":\"캐릭터 이름, 이전 문장과 이으려면 빈칸:대화;\",\"Sad8Ov\":\"캐릭터 이름 내부 텍스트\",\"s3YC4c\":\"캐릭터 이름 처리\",\"01rrCD\":\"캐릭터 이름 외부 텍스트\",\"5CqTX3\":\"캐릭터 이름 용기\",\"TQWMFh\":\"캐릭터 이름 텍스트 창\",\"SaZFuD\":\"캐릭터 이름 텍스트 창 배경\",\"aSifLw\":\"캐릭터 이름 표시\",\"PcGPnf\":\"구석 미니 프로필\",\"PbRRLM\":\"명칭 해금\",\"L0LlwN\":\"업적 해금\",\"nm64JJ\":\"해금된 BGM 이름\",\"TEwxlP\":\"해금된 CG 이름\",\"rimhjz\":\"해금된 CG 또는 BGM 이름\",\"Co/7Bs\":\"해금 갤러리 유형\",\"flJD55\":\"설정\",\"epZj1P\":\"변수 설정\",\"XU5bCO\":\"일반 변수 또는 전체 변수 설정\",\"4Ikuoy\":\"스탠딩 또는 배경의 입/퇴장 애니메이션 설정\",\"ON+a2j\":\"커스텀 캐스케이드 선택기 구분 기호 설정 (입력 후 Enter 키를 눌러 확인)\",\"ihmbRH\":\"이 명령어는 텍스트 창에 현재 변수를 표시합니다.\",\"WufxGR\":\"이 명령어를 인식할 수 없습니다. 스크립트 편집 모드를 열어 수동으로 편집하세요.\",\"ZK+M59\":\"이 파일 유형은 미리보기를 지원하지 않습니다\",\"bJIN8W\":\"언어\",\"fVumBf\":\"음성\",\"X/tvHa\":\"음성 볼륨\",\"EG4Gpn\":\"각 폰트의 정보를 빠짐없이 입력해 주세요\",\"Zxehh6\":\"폰트 이름을 입력하거나 선택하세요\",\"0yFPGs\":\"검색어를 입력하세요...\",\"qw/kKq\":\"전경 이미지 조정\",\"7tVEwG\":\"배경 이미지 조정\",\"0eeLln\":\"Steam 호출\",\"908fto\":\"Steam API 호출, 다중 변수 지원\",\"AscWws\":\"장면 호출/전환\",\"A+Kvx5\":\"장면 파일을 호출하고, 종료 후 어버이 장면으로 돌아갑니다\",\"NM2PYS\":\"애니메이션 호출\",\"SFhdxy\":\"장면 호출\",\"sIqZen\":\"장면을 호출하며, 새 장면 종료 후 어버이 장면으로 돌아갑니다\",\"q2MDQ4\":\"엔진 내장 복잡 애니메이션 호출\",\"a9l6Ko\":\"디버그 변수\",\"rA5YEf\":\"리소스\",\"dIjPmP\":\"시점 종점 반동\",\"wYBRIj\":\"시점 종점 튕김\",\"Nw2SVE\":\"시점 반동\",\"FGpuUP\":\"시점 튕김\",\"rgKNR6\":\"문장 간 애니메이션\",\"r2iiTZ\":\"태그 이동\",\"gS/kSz\":\"장면으로 이동\",\"LcuzKM\":\"현재 장면 내의 태그으로 이동\",\"LQKR1b\":\"현재 장면 태그로 이동\",\"cjm0bm\":\"이동 태그\",\"zzTLpV\":\"부드럽게(0-1)\",\"CZvKht\":\"label 이름 입력\",\"6GZPH3\":\"임의의 태그 입력\",\"I4ELPC\":\"명령어 내용 입력\",\"/JO1a6\":\"현재 장면 내의 label 이름 입력\",\"T1KwXF\":\"검증 정규식 입력\",\"o6udFd\":\"대상 ID 입력\",\"zTyfb+\":\"이 목록으로 이동\",\"kRJCg/\":\"전환 시간(단위: 밀리초)\",\"wFPRfi\":\"파일 필터링\",\"cwfmFH\":\"실행 플랫폼\",\"SmBcVU\":\"뒤로\",\"1TOWH9\":\"입/퇴장 애니메이션\",\"tpiLRd\":\"연속 실행\",\"2wAUT7\":\"퇴장 시간(단위: 밀리초)\",\"dmA2LU\":\"노드나 연결선을 선택한 뒤 Backspace 또는 Delete 키를 누르면 삭제할 수 있습니다\",\"4IIuG6\":\"선택\",\"iAuIBv\":\"애니메이션 선택\",\"FkMdaZ\":\"애니메이션 파일 선택\",\"NbEGUP\":\"이미지 선택\",\"jNHP9q\":\"이미지 파일 선택\",\"OhgvlB\":\"장면 파일 선택\",\"zIJ3Yi\":\"미니 프로필 선택\",\"69M7Zv\":\"미니 프로필 파일 선택\",\"xHolc8\":\"적용할 템플릿 선택\",\"dmGYap\":\"효과음 파일 선택\",\"EPdKlK\":\"파일 선택\",\"38LIEV\":\"게임 엔진 버전 선택\",\"zlTAav\":\"디렉터리 선택\",\"Z8Z53Q\":\"비디오 파일 선택\",\"2I2ENa\":\"스탠딩 파일 선택\",\"72OHF4\":\"배경 이미지 선택\",\"b+7XSx\":\"배경 파일 선택\",\"AffInu\":\"배경음악 선택\",\"x2onXX\":\"음성 파일 선택\",\"ukXlZn\":\"호출 매개변수 선택\",\"q+LyW7\":\"점프 대상 선택\",\"VOdP8N\":\"입장 애니메이션 선택\",\"1j2242\":\"입장 애니메이션 파일 선택\",\"Ux++lj\":\"퇴장 애니메이션 선택\",\"SL5vhv\":\"퇴장 애니메이션 파일 선택\",\"OjVuTD\":\"갤러리 리소스 파일 선택\",\"bDEDWB\":\"사전 설정 대상 선택\",\"96JQhm\":\"색상 선택\",\"5uBl9m\":\"옵션\",\"pIlyJr\":\"옵션 목록\",\"3UJ7SP\":\"옵션 이름\",\"ionQHN\":\"선택지 버튼\",\"p4SMUC\":\"선택지 버튼(비활성화)\",\"hjiIez\":\"선택지 버튼 외층\",\"sM2BIi\":\"투명도(0-1)\",\"oNWnYZ\":\"일반\",\"o+zYhS\":\"선택지를 통해 다른 장면으로 진입합니다\",\"8uQYfH\":\"구성\",\"4GHpC0\":\"구성은 항상 통합된 .webgal_terre 디렉터리에 저장됩니다. 게임, 커스텀 템플릿, 커스텀 엔진은 다른 디스크로 이동 할 수 있습니다.\",\"9/7aYu\":\"템플릿 구성\",\"DwDd05\":\"설정 목록\",\"nlSg/s\":\"이름 바꾸기\",\"utQhKE\":\"템플릿 목록의 이름 바꾸기\",\"oLjZxR\":\"게임 목록의 이름 바꾸기\",\"NUIley\":\"감상(갤러리)\",\"YjDE9d\":\"갤러리\",\"VL0Lml\":\"이미지 감상\",\"tQwgmO\":\"감상 정렬\",\"N5WNMM\":\"시리즈 감상\",\"6jwl0g\":\"감상 리소스 파일\",\"IxlRXl\":\"음악 감상\",\"gvQ8W0\":\"장기\",\"Nfy7bQ\":\"장기 변수\",\"PZzhTE\":\"장기 변수 용도\",\"zqVxgb\":\"다문 입\",\"mlaDy3\":\"감은 눈\",\"YiB7xn\":\"눈 감은 시간(밀리초)\",\"DmRnQt\":\"임계값(Threshold)\",\"aP3Sr2\":\"사이드바 숨기기\",\"Xrn1+k\":\"대화창 숨기기\",\"Y6FOz1\":\"캐릭터 이름 숨기기\",\"Ero88N\":\"비\",\"u/Wc2v\":\"눈\",\"RTl+vt\":\"정적\",\"ioXuTK\":\"위쪽 정렬\",\"45SR3o\":\"아래쪽 정렬\",\"WK1sjs\":\"오른쪽 정렬\",\"tLjs3i\":\"왼쪽 정렬\",\"VpqMXM\":\"음악\",\"Sojbxt\":\"프로젝트 홈페이지\",\"/nWgLT\":\"사전 반전\",\"4P9ckh\":\"사전 설정 템플릿\",\"BGUvk3\":\"사전 설정 템플릿은 설치 디렉터리에 저장됩니다. 편집하려면 먼저 커스텀 템플릿으로 복사하세요.\",\"g4jjs+\":\"미리보기\",\"sOx1hS\":\"미리보기 아이콘\",\"sXJqG4\":\"미리보기 창\",\"L3omXQ\":\"색상\",\"2Jfux7\":\"색상 조정\",\"yjBCOs\":\"채도\",\"1I7Hpb\":\"높이\",\"kduvmj\":\"가우시안 흐림\",\"ozxBe2\":\"기본값\",\"5oYHM9\":\"기본값 default\",\"tsdOxl\":\"기본값\",\"LeACf8\":\"기본값 0\",\"fzevFI\":\"기본값 1\",\"o3LaEy\":\"기본값 100\",\"g8d5Vh\":\"기본값 1000\",\"O/vKJn\":\"기본값 150\",\"FOOWl6\":\"기본값 24시간\",\"Wdn4MI\":\"기본값 50\",\"wbrcoe\":\"기본 변환 및 효과\",\"BYO6xr\":\"기본 언어\",\"Nr4gXI\":\"기본 옵션\",\"4sxeqQ\":\"마우스 호버 스타일\",\"6Q3X1x\":\"마우스 포인터\",\"Zqpl0E\":\"마우스 클릭 스타일\"}")as Messages; \ No newline at end of file diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/BasicCommands.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/BasicCommands.tsx index e0d7d9dd..a42fc072 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/BasicCommands.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/BasicCommands.tsx @@ -9,16 +9,7 @@ import { useValue } from "@/hooks/useValue"; import { getArgByKey } from "../utils/getArgByKey"; import { combineSubmitString } from "@/utils/combineSubmitString"; import { t } from "@lingui/macro"; - -type PresetTarget = "fig-left" | "fig-center" | "fig-right" | "bg-main" | "stage-main"; - -const presetTargets = () => new Map([ - ["fig-left", t`左侧立绘`], - ["fig-center", t`中间立绘`], - ["fig-right", t`右侧立绘`], - ["bg-main", t`背景图片`], - ["stage-main", t`舞台画面`], -]); +import { usePresetTargetOptions } from "@/hooks/usePresetTargetOptions"; function TextInput(props: { title: string; @@ -151,8 +142,8 @@ function SetComplexAnimationCommand(props: ISentenceEditorProps) { const target = useValue(getArgByKey(props.sentence, "target")?.toString() ?? ""); const duration = useValue(getArgByKey(props.sentence, "duration")?.toString() ?? ""); const isGoNext = useValue(getArgByKey(props.sentence, "next") === true); - const targets = presetTargets(); - const isUsePreset = useValue(Array.from(targets.keys()).includes(target.value as PresetTarget)); + const targets = usePresetTargetOptions(); + const isUsePreset = useValue(Array.from(targets.keys()).includes(target.value)); const submit = () => { props.onSubmit(combineSubmitString( diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeFigure.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeFigure.tsx index 5b6d7e19..d78140eb 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeFigure.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeFigure.tsx @@ -22,7 +22,7 @@ import { AssetPreview } from "../components/AssetPreview"; import { useGlobalEffectEditor } from "@/hooks/useGlobalEffectEditor"; import { IgnoreDefaultOption } from "../components/IgnoreDefaultOption"; -type FigurePosition = "" | "left" | "right"; +type FigurePosition = "" | "left" | "left14" | "left13" | "right13" | "right14" | "right"; type AnimationFlag = "" | "on"; type PanelType = "effect" | "moreOptions"; @@ -69,8 +69,12 @@ export default function ChangeFigure(props: ISentenceEditorProps) { const currentSkin = useValue(getArgByKey(props.sentence, "skin").toString() ?? ""); const figurePositions = new Map([ - ["", t`中间`], ["left", t`左侧`], + ["left14", t`左侧 1/4`], + ["left13", t`左侧 1/3`], + ["", t`中间`], + ["right13", t`右侧 1/3`], + ["right14", t`右侧 1/4`], ["right", t`右侧`] ]); @@ -230,12 +234,11 @@ export default function ChangeFigure(props: ISentenceEditorProps) { /** * 初始化立绘位置 */ - if (getArgByKey(props.sentence, "left")) { - figurePosition.set("left"); - } - if (getArgByKey(props.sentence, "right")) { - figurePosition.set("right"); - } + figurePositions.forEach((_, position) => { + if (position !== "" && getArgByKey(props.sentence, position)) { + figurePosition.set(position); + } + }); }, []); useEffect(() => { if (animationFlag.value === "on") { @@ -253,8 +256,9 @@ export default function ChangeFigure(props: ISentenceEditorProps) { contentWithType, props.sentence.args, [ - {key: "left", value: figurePosition.value === "left"}, - {key: "right", value: figurePosition.value === "right"}, + ...Array.from(figurePositions.keys()) + .filter((position) => position !== "") + .map((position) => ({key: position, value: figurePosition.value === position})), {key: "clear", value: clear.value}, {key: "id", value: id.value}, {key: "transform", value: json.value}, diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Say.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Say.tsx index c5aca3bc..920c5cf0 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Say.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Say.tsx @@ -14,7 +14,7 @@ import WheelDropdown from "@/pages/editor/GraphicalEditor/components/WheelDropdo import { combineSubmitString } from "@/utils/combineSubmitString"; import { extNameMap } from "../../ChooseFile/chooseFileConfig"; -type FigurePosition = "" | "left" | "right" | "center" | "id"; +type FigurePosition = "" | "left" | "left14" | "left13" | "center" | "right13" | "right14" | "right" | "id"; type FontSize = "default" | "small" | "medium" | "large"; export default function Say(props: ISentenceEditorProps) { @@ -29,7 +29,11 @@ export default function Say(props: ISentenceEditorProps) { const figurePositions = new Map([ [ "", t`未指定` ], [ "left", t`左侧立绘` ], + [ "left14", t`左侧 1/4 立绘` ], + [ "left13", t`左侧 1/3 立绘` ], [ "center", t`中间立绘` ], + [ "right13", t`右侧 1/3 立绘` ], + [ "right14", t`右侧 1/4 立绘` ], [ "right", t`右侧立绘` ], [ "id", t`使用立绘ID` ], ]); @@ -41,18 +45,11 @@ export default function Say(props: ISentenceEditorProps) { /** * 初始化立绘位置 */ - if (getArgByKey(props.sentence, "left")) { - figurePosition.set("left"); - } - if (getArgByKey(props.sentence, "right")) { - figurePosition.set("right"); - } - if (getArgByKey(props.sentence, "center")) { - figurePosition.set("center"); - } - if (getArgByKey(props.sentence, "id")) { - figurePosition.set("id"); - } + figurePositions.forEach((_, position) => { + if (position !== "" && getArgByKey(props.sentence, position)) { + figurePosition.set(position); + } + }); }, []); const fontSizes = new Map([ @@ -100,10 +97,9 @@ export default function Say(props: ISentenceEditorProps) { {key: "notend", value: isNotend.value}, {key: "clear", value: isClearSpeaker.value}, {key: "fontSize", value: (fontSize.value !== "default" ? fontSize.value : "")}, - {key: "left", value: figurePosition.value === "left"}, - {key: "right", value: figurePosition.value === "right"}, - {key: "center", value: figurePosition.value === "center"}, - {key: "id", value: figurePosition.value === "id"}, + ...Array.from(figurePositions.keys()) + .filter((position) => position !== "") + .map((position) => ({key: position, value: figurePosition.value === position})), {key: "figureId", value: (figurePosition.value === "id" ? figureId.value : "")}, ], props.sentence.inlineComment, diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetAnimation.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetAnimation.tsx index 7ff46597..91e5530d 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetAnimation.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetAnimation.tsx @@ -11,20 +11,13 @@ import WheelDropdown from "@/pages/editor/GraphicalEditor/components/WheelDropdo import { combineSubmitString } from "@/utils/combineSubmitString"; import { extNameMap } from "../../ChooseFile/chooseFileConfig"; import { IgnoreDefaultOption } from "../components/IgnoreDefaultOption"; - -type PresetTarget = "fig-left" | "fig-center" | "fig-right" | "bg-main" | "stage-main"; +import { usePresetTargetOptions } from "@/hooks/usePresetTargetOptions"; export default function SetAnimation(props: ISentenceEditorProps) { const fileName = useValue(props.sentence.content); const target = useValue(getArgByKey(props.sentence, "target")?.toString() ?? ""); - const presetTargets = new Map([ - [ "fig-left", t`左侧立绘` ], - [ "fig-center", t`中间立绘` ], - [ "fig-right", t`右侧立绘` ], - [ "bg-main", t`背景图片` ], - [ "stage-main", t`舞台画面` ], - ]); - const isPresetTarget = Array.from(presetTargets.keys()).includes(target.value as PresetTarget); + const presetTargets = usePresetTargetOptions(); + const isPresetTarget = Array.from(presetTargets.keys()).includes(target.value); const isUsePreset = useValue(isPresetTarget); const isGoNext = useValue(!!getArgByKey(props.sentence, "next")); const writeDefault = useValue(getArgByKey(props.sentence, 'writeDefault') === true); diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTempAnimation.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTempAnimation.tsx index 2b8e996f..ac9cd07e 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTempAnimation.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTempAnimation.tsx @@ -14,8 +14,7 @@ import { CloseSmall, Down, More, Plus, Up } from "@icon-park/react"; import { useGlobalEffectEditor } from "@/hooks/useGlobalEffectEditor"; import { useRef } from "react"; import { IgnoreDefaultOption } from "../components/IgnoreDefaultOption"; - -type PresetTarget = "fig-left" | "fig-center" | "fig-right" | "bg-main" | "stage-main"; +import { usePresetTargetOptions } from "@/hooks/usePresetTargetOptions"; interface IAnimationFrame { transform: string; @@ -27,14 +26,8 @@ export default function SetTempAnimation(props: ISentenceEditorProps) { const content = useValue(props.sentence.content); const animationFrameArray = useValue(initTransformArray(content.value)); const target = useValue(getArgByKey(props.sentence, "target")?.toString() ?? ""); - const presetTargets = new Map([ - [ "fig-left", t`左侧立绘` ], - [ "fig-center", t`中间立绘` ], - [ "fig-right", t`右侧立绘` ], - [ "bg-main", t`背景图片` ], - [ "stage-main", t`舞台画面` ], - ]); - const isPresetTarget = Array.from(presetTargets.keys()).includes(target.value as PresetTarget); + const presetTargets = usePresetTargetOptions(); + const isPresetTarget = Array.from(presetTargets.keys()).includes(target.value); const isUsePreset = useValue(isPresetTarget); const isGoNext = useValue(!!getArgByKey(props.sentence, "next")); const writeDefault = useValue(getArgByKey(props.sentence, 'writeDefault') === true); diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransform.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransform.tsx index b355a504..ab58ccb6 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransform.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransform.tsx @@ -9,12 +9,11 @@ import { Button } from "@fluentui/react-components"; import { t } from "@lingui/macro"; import { combineSubmitString } from "@/utils/combineSubmitString"; import { useEaseTypeOptions } from "@/hooks/useEaseTypeOptions"; +import { usePresetTargetOptions } from "@/hooks/usePresetTargetOptions"; import { EditorPreviewClient } from "@/utils/editorPreviewClient"; import { useGlobalEffectEditor } from "@/hooks/useGlobalEffectEditor"; import { IgnoreDefaultOption } from "../components/IgnoreDefaultOption"; -type PresetTarget = "fig-left" | "fig-center" | "fig-right" | "bg-main" | "stage-main"; - export default function SetTransform(props: ISentenceEditorProps) { // const t = useTrans('editor.graphical.components.template.'); const { sentence } = props; @@ -24,14 +23,8 @@ export default function SetTransform(props: ISentenceEditorProps) { const duration = useValue((durationFromArgs ?? 0) as number); const isGoNext = useValue(!!getArgByKey(props.sentence, "next")); const target = useValue(getArgByKey(props.sentence, "target")?.toString() ?? ""); - const presetTargets = new Map([ - [ "fig-left", t`左侧立绘` ], - [ "fig-center", t`中间立绘` ], - [ "fig-right", t`右侧立绘` ], - [ "bg-main", t`背景图片` ], - [ "stage-main", t`舞台画面` ], - ]); - const isPresetTarget = Array.from(presetTargets.keys()).includes(target.value as PresetTarget); + const presetTargets = usePresetTargetOptions(); + const isPresetTarget = Array.from(presetTargets.keys()).includes(target.value); const isUsePreset = useValue(isPresetTarget); const ease = useValue(getArgByKey(props.sentence, 'ease').toString() ?? ''); const easeTypeOptions = useEaseTypeOptions(); diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransition.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransition.tsx index 45101497..caa87c45 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransition.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransition.tsx @@ -11,21 +11,15 @@ import WheelDropdown from "@/pages/editor/GraphicalEditor/components/WheelDropdo import { combineSubmitString } from "@/utils/combineSubmitString"; import { extNameMap } from "../../ChooseFile/chooseFileConfig"; import { IgnoreDefaultOption } from "../components/IgnoreDefaultOption"; - -type PresetTarget = "fig-left" | "fig-center" | "fig-right" | "bg-main"; +import { usePresetTargetOptions } from "@/hooks/usePresetTargetOptions"; export default function SetTransition(props: ISentenceEditorProps) { const enterFileName = useValue(getArgByKey(props.sentence, 'enter')); const exitFileName = useValue(getArgByKey(props.sentence, 'exit')); const target = useValue(getArgByKey(props.sentence, "target")?.toString() ?? ""); const ignoreDefault = useValue(getArgByKey(props.sentence, "ignoreDefault") === true); - const presetTargets = new Map([ - [ "fig-left", t`左侧立绘` ], - [ "fig-center", t`中间立绘` ], - [ "fig-right", t`右侧立绘` ], - [ "bg-main", t`背景图片` ], - ]); - const isPresetTarget = Array.from(presetTargets.keys()).includes(target.value as PresetTarget); + const presetTargets = usePresetTargetOptions(false); + const isPresetTarget = Array.from(presetTargets.keys()).includes(target.value); const isUsePreset = useValue(isPresetTarget); const submit = () => { const submitString = combineSubmitString( diff --git a/packages/origine2/src/pages/editor/TransformableBox/referenceBoxGeometry.ts b/packages/origine2/src/pages/editor/TransformableBox/referenceBoxGeometry.ts index 5144c9b8..7b751179 100644 --- a/packages/origine2/src/pages/editor/TransformableBox/referenceBoxGeometry.ts +++ b/packages/origine2/src/pages/editor/TransformableBox/referenceBoxGeometry.ts @@ -31,14 +31,15 @@ function getArgValue( return lineSentence?.args?.find((arg) => arg.key === key)?.value; } -function readDirection(input: ResolveTargetInput): 'left' | 'center' | 'right' { - if (getArgValue(input.lineSentence, 'left') === true || /(?:^|\s)-left(?:\s|;|$)/.test(input.lineContent)) { - return 'left'; - } - if (getArgValue(input.lineSentence, 'right') === true || /(?:^|\s)-right(?:\s|;|$)/.test(input.lineContent)) { - return 'right'; - } - return 'center'; +const FIGURE_DIRECTIONS = ['left', 'right', 'left13', 'right13', 'left14', 'right14'] as const; + +function readDirection(input: ResolveTargetInput): string { + const direction = FIGURE_DIRECTIONS.find( + (item) => + getArgValue(input.lineSentence, item) === true || + new RegExp(`(?:^|\\s)-${item}(?:\\s|;|$)`).test(input.lineContent), + ); + return direction ?? 'center'; } export function resolveTransformTarget(input: ResolveTargetInput): string | undefined { diff --git a/packages/origine2/src/types/stageInterface.ts b/packages/origine2/src/types/stageInterface.ts index 1e3fb643..2e84d3a4 100644 --- a/packages/origine2/src/types/stageInterface.ts +++ b/packages/origine2/src/types/stageInterface.ts @@ -70,8 +70,10 @@ export const baseTransform: ITransform = { blur: 0, }; +export type IFigurePosition = 'center' | 'left' | 'right' | 'left13' | 'right13' | 'left14' | 'right14'; + export interface IFreeFigure { - basePosition: 'left' | 'center' | 'right'; + basePosition: IFigurePosition; name: string; key: string; } @@ -113,6 +115,10 @@ export interface IStageState { figName: string; // 立绘_中 文件地址(相对或绝对) figNameLeft: string; // 立绘_左 文件地址(相对或绝对) figNameRight: string; // 立绘_右 文件地址(相对或绝对) + figNameLeft13: string; // 立绘_左 1/3 文件地址(相对或绝对) + figNameRight13: string; // 立绘_右 1/3 文件地址(相对或绝对) + figNameLeft14: string; // 立绘_左 1/4 文件地址(相对或绝对) + figNameRight14: string; // 立绘_右 1/4 文件地址(相对或绝对) // 自由立绘 freeFigure: Array; figureAssociatedAnimation: Array; diff --git a/packages/terre2/src/Modules/lsp/suggestionRules/getArgsKey.ts b/packages/terre2/src/Modules/lsp/suggestionRules/getArgsKey.ts index f2bc654f..76376dfa 100644 --- a/packages/terre2/src/Modules/lsp/suggestionRules/getArgsKey.ts +++ b/packages/terre2/src/Modules/lsp/suggestionRules/getArgsKey.ts @@ -21,6 +21,7 @@ export function getArgsKey( leftSayKey, rightSayKey, centerSayKey, + ...centeredPositionSayKeys, ]; } case commandType.changeBg: { @@ -51,6 +52,7 @@ export function getArgsKey( idFigureKey, leftKey, rightKey, + ...centeredPositionKeys, transformKey, zIndexKey, motionKey, @@ -405,6 +407,48 @@ changeFigure:k2.png -next; `), }; +/** + * 除左右靠边定位以外的立绘位置,均以立绘中心为基准定位 + */ +const centeredFigurePositions: Array<[string, string]> = [ + ['left13', '左侧 1/3'], + ['right13', '右侧 1/3'], + ['left14', '左侧 1/4'], + ['right14', '右侧 1/4'], +]; + +const centeredPositionKeys: CompletionItem[] = centeredFigurePositions.map( + ([position, name]) => ({ + kind: CompletionItemKind.Constant, + label: position, + insertText: position, + detail: `将立绘置于${name}处`, + documentation: markdown(` +将立绘放置在舞台${name}处,以立绘的中心为基准定位 + +\`\`\` +changeFigure:testFigure03.png -${position}; +\`\`\` + `), + }), +); + +const centeredPositionSayKeys: CompletionItem[] = centeredFigurePositions.map( + ([position, name]) => ({ + kind: CompletionItemKind.Constant, + label: position, + insertText: position, + detail: `对话属于${name}处的立绘`, + documentation: markdown(` +指定该对话所属的立绘为${name}处的立绘 + +\`\`\` +WebGAL:这是${name}处立绘的对话 -${position}; +\`\`\` + `), + }), +); + const leftSayKey: CompletionItem = { kind: CompletionItemKind.Constant, label: 'left', diff --git a/packages/terre2/src/Modules/lsp/suggestionRules/getCommands.ts b/packages/terre2/src/Modules/lsp/suggestionRules/getCommands.ts index ed612962..1bf41ba0 100644 --- a/packages/terre2/src/Modules/lsp/suggestionRules/getCommands.ts +++ b/packages/terre2/src/Modules/lsp/suggestionRules/getCommands.ts @@ -45,7 +45,7 @@ changeBg: [-next] [-enter=animationName] [-exit=animationName]; `\`\`\` changeFigure:testFigure03.png -left -next; changeFigure:testFigure03.png -left -enter=fadeIn -exit=fadeOut; -changeFigure: [-left] [-right] [id=figureId] [-next] [-enter=animationName] [-exit=animationName]; +changeFigure: [-left] [-right] [-left13] [-right13] [-left14] [-right14] [id=figureId] [-next] [-enter=animationName] [-exit=animationName]; \`\`\``, ), }, From 136afa6eb8642849dbae73a4774ba4eec9b0a605 Mon Sep 17 00:00:00 2001 From: Mahiru Date: Sat, 25 Jul 2026 22:15:08 +0800 Subject: [PATCH 06/13] i18n --- .gitignore | 8 ++------ packages/origine2/src/locales/en.po | 24 ++++++++++++++++++++++++ packages/origine2/src/locales/ja.po | 24 ++++++++++++++++++++++++ packages/origine2/src/locales/ko.po | 24 ++++++++++++++++++++++++ packages/origine2/src/locales/ko.ts | 1 - packages/origine2/src/locales/zhCn.po | 24 ++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 7 deletions(-) delete mode 100644 packages/origine2/src/locales/ko.ts diff --git a/.gitignore b/.gitignore index 9af49f9e..ccbfa0a7 100644 --- a/.gitignore +++ b/.gitignore @@ -12,10 +12,6 @@ package-lock.json /steam/depot_build_*.vdf !/steam/app_build_TEMPLATE.vdf !/steam/depot_build_TEMPLATE.vdf -packages/origine2/src/locales/en.js -packages/origine2/src/locales/ja.js -packages/origine2/src/locales/zhCn.js -packages/origine2/src/locales/en.ts -packages/origine2/src/locales/ja.ts -packages/origine2/src/locales/zhCn.ts +packages/origine2/src/locales/*.js +packages/origine2/src/locales/*.ts .claude diff --git a/packages/origine2/src/locales/en.po b/packages/origine2/src/locales/en.po index 9340e579..6fa3daee 100644 --- a/packages/origine2/src/locales/en.po +++ b/packages/origine2/src/locales/en.po @@ -641,6 +641,18 @@ msgstr "Selectable options" msgid "右侧" msgstr "Right" +msgid "右侧 1/3" +msgstr "Right 1/3" + +msgid "右侧 1/3 立绘" +msgstr "Right 1/3 figure" + +msgid "右侧 1/4" +msgstr "Right 1/4" + +msgid "右侧 1/4 立绘" +msgstr "Right 1/4 figure" + msgid "右侧立绘" msgstr "Right side figure" @@ -905,6 +917,18 @@ msgstr "Property" msgid "左侧" msgstr "Left" +msgid "左侧 1/3" +msgstr "Left 1/3" + +msgid "左侧 1/3 立绘" +msgstr "Left 1/3 figure" + +msgid "左侧 1/4" +msgstr "Left 1/4" + +msgid "左侧 1/4 立绘" +msgstr "Left 1/4 figure" + msgid "左侧立绘" msgstr "Left side figure" diff --git a/packages/origine2/src/locales/ja.po b/packages/origine2/src/locales/ja.po index c2e0ff73..03cf04bc 100644 --- a/packages/origine2/src/locales/ja.po +++ b/packages/origine2/src/locales/ja.po @@ -641,6 +641,18 @@ msgstr "選択可能な選択肢" msgid "右侧" msgstr "右" +msgid "右侧 1/3" +msgstr "右 1/3" + +msgid "右侧 1/3 立绘" +msgstr "右 1/3 の立ち絵" + +msgid "右侧 1/4" +msgstr "右 1/4" + +msgid "右侧 1/4 立绘" +msgstr "右 1/4 の立ち絵" + msgid "右侧立绘" msgstr "右の立ち絵" @@ -905,6 +917,18 @@ msgstr "プロパティ" msgid "左侧" msgstr "左" +msgid "左侧 1/3" +msgstr "左 1/3" + +msgid "左侧 1/3 立绘" +msgstr "左 1/3 の立ち絵" + +msgid "左侧 1/4" +msgstr "左 1/4" + +msgid "左侧 1/4 立绘" +msgstr "左 1/4 の立ち絵" + msgid "左侧立绘" msgstr "左の立ち絵" diff --git a/packages/origine2/src/locales/ko.po b/packages/origine2/src/locales/ko.po index bab2f3fb..cd627505 100644 --- a/packages/origine2/src/locales/ko.po +++ b/packages/origine2/src/locales/ko.po @@ -641,6 +641,18 @@ msgstr "선택 가능 항목" msgid "右侧" msgstr "우측" +msgid "右侧 1/3" +msgstr "우측 1/3" + +msgid "右侧 1/3 立绘" +msgstr "우측 1/3 스탠딩" + +msgid "右侧 1/4" +msgstr "우측 1/4" + +msgid "右侧 1/4 立绘" +msgstr "우측 1/4 스탠딩" + msgid "右侧立绘" msgstr "우측 스탠딩" @@ -905,6 +917,18 @@ msgstr "속성" msgid "左侧" msgstr "왼쪽" +msgid "左侧 1/3" +msgstr "좌측 1/3" + +msgid "左侧 1/3 立绘" +msgstr "좌측 1/3 스탠딩" + +msgid "左侧 1/4" +msgstr "좌측 1/4" + +msgid "左侧 1/4 立绘" +msgstr "좌측 1/4 스탠딩" + msgid "左侧立绘" msgstr "좌측 스탠딩" diff --git a/packages/origine2/src/locales/ko.ts b/packages/origine2/src/locales/ko.ts deleted file mode 100644 index 482c0a43..00000000 --- a/packages/origine2/src/locales/ko.ts +++ /dev/null @@ -1 +0,0 @@ -/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"ez2arP\":\";주석\",\"ZfQJb6\":\"*스포이드를 설정한 후, 버튼을 클릭하여 변경 사항을 저장하세요\",\"9ITYjl\":\"%\",\"q0qRTC\":\"<0>새 템플릿 적용\",\"qTVI69\":[\"<0>현재 적용된 템플릿: \",[\"currentTemplateName\"],\"\"],\"+PwTIw\":\"1, 2, 3, ...\",\"eyqDdt\":\"4.6 사용자 데이터 이동\",\"YSvnx0\":\"applyStyle:TextBox_ShowName_Background->TextBox_ShowName_Background_Red;\",\"4O5JCB\":\"BGM\",\"etAybr\":\"BGM 재생 상태\",\"OtUIFf\":\"BGM 음량\",\"2YU2Kn\":\"bgm:배경음악 선택;\",\"Zjvu8X\":\"callScene:장면 파일 선택;\",\"TCV7GD\":\"callSteam: -achievementId=;\",\"FS5tux\":\"CG\",\"vESFMs\":\"changeBg:배경 이미지 선택;\",\"XfG9aQ\":\"changeFigure:스탠딩 파일 선택;\",\"jdJt3k\":\"changeScene:장면 파일 선택;\",\"+t6BAj\":\"choose:선택지:장면 파일 선택;\",\"YvwyIk\":\"CSS 속성값\",\"IiCbUY\":\"CSS 속성명:\",\"/oBAgV\":\"enable은 켜기, none 또는 비워서 끄기\",\"tBCPXB\":\"end;\",\"TJ8a+f\":\"filmMode:enable;\",\"IlFGfd\":\"font-family\",\"89SUCu\":\"getUserInput:;\",\"yZpvfb\":\"Intro 텍스트\",\"eIOW3x\":\"intro:;\",\"7DnAtq\":\"jumpLabel:label_1;\",\"aZ3bG5\":\"label:label_1;\",\"mHSbXE\":\"Live2D 모션\",\"ebsFnb\":\"Live2D 표정\",\"+Q2q6Q\":\"miniAvatar:미니 프로필 선택;\",\"wOFCxG\":\"pixiInit;\",\"VWZEcv\":\"pixiPerform:snow;\",\"Dxbz2N\":\"playEffect:효과음 파일 선택;\",\"xCHuyF\":\"playVideo:영상 파일 선택;\",\"V8p+bG\":\"Portable 모드\",\"/8PJ2M\":\"RGB 영화 필터\",\"Zrsplh\":\"setAnimation:애니메이션 파일 선택;\",\"Uxm0fk\":\"setComplexAnimation:universalSoftIn -target=fig-center;\",\"aHdzZT\":\"setTempAnimation:[{\\\"duration\\\":0},{\\\"duration\\\":500}];\",\"dfLJ5q\":\"setTextbox:hide;\",\"UYmTsP\":\"setTransform: -duration=0;\",\"RfGfVs\":\"setTransition:;\",\"74lZuk\":\"setVar:a=1;\",\"bTyzll\":\"showVars는 일반적으로 내용이 필요하지 않습니다\",\"8kbHea\":\"showVars;\",\"VV9dBJ\":\"Spine 애니메이션\",\"xOOpnF\":\"Spine 스킨\",\"RMdyHE\":\"Steam AppID\",\"LAI/Cy\":\"unlockBgm:;\",\"9afSwF\":\"unlockCg:;\",\"bIXYlr\":\"wait:1000;\",\"Z9I0PY\":\"WebGAL Standard\",\"o7ojN3\":\"WebGAL Terre 4.6 은 새로운 사용자 데이터 목록을 사용합니다. 구버전 데이터를 검사하거나 옮기는 것을 권장합니다.\",\"VAODzB\":\"X축 이동\",\"A/jVgG\":\"X축 비율\",\"UVJiw4\":\"Y축 이동\",\"0eNchO\":\"Y축 비율\",\"a6O/JX\":\"z-index\",\"TSQMsH\":\"리본 메뉴 항상 표시\",\"pu0x8w\":\"이전 단계\",\"Q/n6Ny\":\"업로드\",\"n4X166\":\"글씨체 파일 업로드\",\"eTyEPt\":\"리소스 업로드\",\"LIAXFC\":\"위로 이동\",\"4uRyNg\":\"다음 단계\",\"St/SJ5\":\"아래로 이동\",\"PP/Y5/\":\"clear 함수 사용하지 않음\",\"dOjUpV\":\"when 조건 사용하지 않음\",\"LYKall\":\"선택 불가\",\"y41keD\":\"템플릿을 적용하지 않음\",\"+H1Y3V\":\"이전 텍스트 창의 문장과 이어지지 않음\",\"7V3y7y\":\"순환 연결을 만들 수 없습니다\",\"R1CJC2\":\"설정 안 함\",\"nL7bt7\":\"동일 대상 애니메이션과 병행 실행\",\"IQKChW\":\"양끝 정렬\",\"jVh12T\":\"개의 글자\",\"mmLYLw\":\"중\",\"GTIc9/\":\"중앙\",\"hfB0wu\":\"중앙 스탠딩\",\"wz/sw7\":\"현재 스테이지에 이펙트 추가\",\"h1jPqc\":\"스탠딩 또는 배경 이미지에 단일 구간 애니메이션 이펙트 설정\",\"LpKCBj\":\"스탠딩 또는 배경 이미지에 다중 구간 애니메이션 이펙트 설정\",\"PI3ywW\":\"스탠딩 또는 배경 이미지에 애니메이션 불러오기 이펙트를 적용\",\"sWTv8x\":\"메인 플로차트는 삭제할 수 없습니다\",\"WC48YD\":\"메인 스타일\",\"qbow8i\":\"홈페이지\",\"hNFZVt\":\"테마\",\"jHJe9a\":\"밝기\",\"WmmLaN\":\"현재 장면에서 선택\",\"zA8esk\":\"코드 에디터\",\"7zsjb3\":\"다음 대상의 경로가 이미 존재하므로 덮어쓰지 않았습니다:\",\"QLZ4dX\":\"감마값\",\"Fas9Si\":\"clear 함수를 사용해 초기화\",\"uk3CXK\":\"사용 중인 템플릿:\",\"47+kxo\":\"엔진에 내장된 이펙트 사용\",\"/ySHoI\":\"본 태그 사용\",\"2cCMVu\":\"이펙트 사용\",\"f9y04E\":\"스탠딩 ID 사용\",\"GL8qtJ\":\"커스텀 특수 효과 사용\",\"IUCcDX\":\"프리셋 특수 효과 사용\",\"oWsDuj\":\"프리셋 대상을 사용하되, ID가 설정된 경우 적용되지 않습니다.\",\"NUlqj7\":\"프리셋 대상 사용\",\"jbVvhH\":\"예: -100,-100,100,100\",\"mUxmYb\":[\"예: ^.\",[\"regexRange\"],\"$\"],\"nwPnGm\":\"예: a=1 또는 name=WebGAL\",\"WCGfK0\":\"예: a>0 && flag==true\",\"IF2b07\":\"예: fig-center\",\"uyQChY\":\"예: i\",\"pxPdPB\":\"예: universalSoftIn\",\"bl9Beh\":\"사이드바\",\"rpBRee\":\"사이드바 게임 미리보기\",\"jBG25x\":\"저장\",\"aBwR6p\":\"저장 중...\",\"IbLq2X\":\"템플릿 구성 저장에 실패했습니다. 잠시후 다시 시도해 주세요\",\"h/Vi1t\":\"플로차트 저장\",\"GLd8LR\":\"눈 감기 유지(밀리초)\",\"e76V/v\":\"캐릭터 이름 유지\",\"c/sLTZ\":\"수정 시간\",\"SK6wGK\":\"게임 아이콘 수정\",\"h/QpEH\":\"색상 수정\",\"cBie7+\":\"모서리 깎기\",\"yWKT27\":\"값\",\"U7ZiGp\":\"값 또는 표현식\",\"Xr5Uul\":\"오프셋\",\"YWrmBt\":\"우측 오프셋 거리(위치 지정용)\",\"hQE+CE\":\"좌측 오프셋 거리(위치 지정용)\",\"8TBO/L\":\"하단 오프셋 거리(위치 지정용)\",\"BE5gDE\":\"상단 오프셋 거리(위치 지정용)\",\"sagY4I\":\"정지 위치\",\"GXB9zw\":\"픽셀\",\"0k7O3u\":\"스킵 허용\",\"XcY13T\":\"글로우 영화 필터\",\"gEFUMc\":\"입장 시간(단위: 밀리초)\",\"wCAGx4\":\"전역 변수\",\"T1h5rG\":\"전체 화면 텍스트\",\"ktxKKs\":\"전체 화면에 텍스트를 표시합니다. 독백이나 장면 도입부에 사용됩니다.\",\"sUqjHY\":\"모두 숨기기\",\"qFPINp\":\"모두 표시\",\"iw+qKq\":\"About\",\"/CTGfy\":\"연결된 스탠딩\",\"GS2Ofk\":\"닫기\",\"/IjfsU\":\"미니 프로필 끄기\",\"nsrfMl\":\"효과음 끄기\",\"wNuXVe\":\"배경 끄기\",\"gczUo5\":\"미리보기 창 닫기\",\"Ik81t6\":\"내용\",\"APFEOB\":\"내부 텍스트\",\"DfIXub\":\"안쪽 여백\",\"jnhO7t\":\"최대 너비\",\"NS1tk+\":\"최대 높이\",\"cnA59a\":\"최소 너비\",\"OlHHxv\":\"최소 높이\",\"txODIz\":\"최신 버전\",\"ZQEl0Y\":\"전역 변수 저장하기\",\"vEK29W\":\"변수에 저장하기\",\"coN/yH\":\"현재 세이브 변수에 저장하기\",\"71Cosn\":\"분기 선택\",\"40G9NY\":\"다른 장면 파일로 전환한 뒤, 현재 장면을 초기화\",\"qU64ut\":\"장면 전환\",\"aYsifP\":\"장면을 전환하며, 새 장면이 어버이 장면을 대체합니다\",\"7yX7g6\":\"인터페이스 언어 전환\",\"dvhHum\":\"스탠딩 전환\",\"sWeIVn\":\"배경 전환\",\"APcQQr\":\"배경 이미지 전환\",\"gMWuBH\":\"목록 확인\",\"lLPWZb\":\"생성\",\"gnSpxl\":\"사본 생성\",\"hEKmon\":\"새 템플릿 생성\",\"s2YbNh\":\"새 게임 생성\",\"tGxdbR\":\"삭제\",\"kq1kxQ\":\"속성 삭제\",\"ZlR4nz\":\"현재 문장 삭제\",\"R/0aSV\":\"템플릿 삭제\",\"kQ+WdA\":\"게임 삭제\",\"JpgJsu\":\"새로고침\",\"wRZsHb\":\"게임 새로고침\",\"MSHXWR\":\"전경\",\"zkD1td\":\"리본 메뉴 표시\",\"gNEHCz\":\"굵게\",\"BuTBJb\":\"애니메이션\",\"B1cXA5\":\"애니메이션 이름\",\"zy14kX\":\"애니메이션 프레임 제어\",\"9cawOO\":\"반쯤 벌린 입\",\"rYZPFI\":\"단위는 밀리초. 0~ 유효\",\"pM1UIg\":\"단일 애니메이션\",\"A48Tay\":\"한 줄 주석\",\"UFVfv0\":\"곧 사용자 데이터를 새로운 목록으로 이동합니다. 파일이 많을 경우 시간이 걸릴 수 있으므로, 완료될 때까지 WebGAL Terre를 닫지 마세요.\",\"T2JfAd\":\"사용자 데이터를 기본 목록으로 되돌립니다. 파일이 많을 경우 시간이 걸릴 수 있습니다.\",\"o0fQFX\":\"두께\",\"JZjf/Y\":\"기존 스타일->새 스타일, 여러 개는 쉼표로 구분\",\"4m2dYU\":\"파라미터 값\",\"KJZS3U\":\"노드 이름을 두 번 클릭하면 편집할 수 있습니다\",\"CDuvSn\":\"반사 영화 필터\",\"hHGCZw\":\"배포된 게임은 <0>https://www.openwebgal.com/games/ 에 전시됩니다. 여러분의 작품을 공유해 주세요!\",\"cYUc0S\":\"새 버전 발견\",\"jxD+iQ\":\"취소\",\"bbdi6l\":\"고정 해제\",\"OcQJ3O\":\"변형\",\"MiE0Mz\":\"변수명\",\"iZhOyz\":\"변수 표현식\",\"xg8k+a\":\"노드만 표시하고 이름은 표시하지 않음\",\"WJg1Su\":\"설정에서 휴지통을 끈 뒤 다시 시도할 수 있습니다. 이 경우 삭제한 항목은 복구할 수 없습니다.\",\"HER8aZ\":\"$0를 사용하여 현재 입력 표시 가능\",\"A27Z7l\":\"스크립트 간에 무한 루프가 있거나 대상 줄 이전에 빨리 감기해야 할 행수가 너무 많을 수 있습니다.\",\"nOgzPD\":\"선택 가능 항목\",\"oeUweH\":\"우측\",\"mL9DmJ\":\"우측 스탠딩\",\"23nU8q\":\"우측 정렬\",\"upWPde\":\"위로 추가\",\"7YMtbN\":\"배경음악 재생, 전환 또는 정지\",\"V9IP5s\":\"시작 이미지\",\"xqo5T2\":\"활성\",\"qPjXK6\":\"when 조건 활성화\",\"4l0zVt\":\"비디오 건너뛰기 활성화\",\"JZA2nJ\":\"립싱크 및 눈 깜박임\",\"MUwa78\":\"휴지통\",\"32wTLf\":\"고정 팝업\",\"uv5w6v\":\"이미지\",\"aPiTPQ\":\"그래픽 에디터\",\"t5JJx+\":\"그림\",\"ruqLSO\":\"이미지 차분\",\"sltuoL\":\"원형\",\"s6jLFn\":\"원형 이즈 인\",\"0O4ucF\":\"원형 이즈 인 아웃\",\"RDWZpZ\":\"원형 이즈 아웃\",\"TMErca\":\"둥근 모서리\",\"JzRUNZ\":\"둥근 사각형\",\"1Vx+fK\":\"대화창 좌측 하단에 미니 프로필 표시\",\"BOBeYL\":\"현재 장면 내에 이동 태그 생성\",\"lKHSrA\":\"파일 탐색기에서 열기\",\"Hia9ZF\":\"새 탭에서 미리보기\",\"n/RuiJ\":\"게임 내에서 현재 변수 표시\",\"EBxZ+B\":\"여기에 편집 위젯 배치\",\"78nH85\":\"장면\",\"Wh0Wxu\":\"장면 및 분기\",\"HFYGwW\":\"장면 파일\",\"RuqGFg\":\"파라미터 값 작성\",\"E8v2tX\":\"복잡 애니메이션\",\"l1axJE\":\"복잡 애니메이션 이름은 엔진에 내장된 애니메이션에서 가져옵니다. 프로젝트 애니메이션 파일을 사용하려면 일반적으로 '애니메이션 불러오기'을 사용해야 합니다.\",\"Tw6+I6\":\"외부 텍스트\",\"Rw2ZoI\":\"바깥 여백\",\"Rg0rou\":\"다중 구간 애니메이션\",\"Rnjd5I\":\"대\",\"DYpFps\":\"폭설\",\"dIsNg+\":\"커스텀 템플릿 사용으로 인해 미리보기에 이상이 발생할 경우, 이 옵션을 끄시길 바랍니다.\",\"5npXGU\":\"폰트\",\"IBB/4V\":\"폰트 최적화\",\"HyXaZd\":\"폰트 최적화는 게임 미리보기 창이 특정 크기 이하일때 가독성을 높이기 위해 텍스트 테두리를 비활성화합니다.\",\"nCmVP2\":\"글자 크기\",\"zAiOiy\":\"폰트 파일 URL\",\"GqjJWX\":\"폰트 파일 업로드에 실패했습니다. 다시 시도해 주세요\",\"NCX3JP\":\"폰트 유형\",\"eWhPhP\":\"폰트 구성\",\"+Nelz8\":\"글자 간격\",\"bPcq/h\":\"글꼴 두께\",\"51Vz47\":\"위치 지정 방식\",\"GWpHcc\":\"실시간 미리보기\",\"t+sLG2\":\"실시간 미리보기 꺼짐\",\"QQLO2n\":\"실시간 미리보기가 중지되었습니다\",\"306dJv\":\"실시간 미리보기 시작 시 자동으로 주입되며, 실제 게임에서는 실행되지 않습니다.\",\"kXwAov\":\"실시간 미리보기 빨리 감기가 제한 시간을 초과하여 연산을 중단했습니다.\",\"L8MJAS\":\"실시간 미리보기 켜짐\",\"EVMosY\":\"실험적 빠른 미리보기\",\"CPbMQ8\":\"너비\",\"Z8j8l6\":\"대비\",\"WuipOX\":\"대화\",\"/FOeko\":\"대화창 텍스트\",\"OLxIGA\":\"대화창 제목\",\"o2RHlj\":\"템플릿 불러오기\",\"v6FJm2\":\"내보내기\",\"RQ9guE\":\"내보내는 중...\",\"XkkDWm\":\"실행 파일로 내보내기\",\"KyQ6e8\":\"Android 프로젝트 파일로 내보내기\",\"HDLXqZ\":\"웹페이지로 내보내기\",\"2mK/uv\":\"내보내기 실패!\",\"vZo1qG\":\"내보내기 성공!\",\"Ra5S96\":\"템플릿 내보내기\",\"ztfrH+\":\"현재 템플릿을 선택한 게임에 적용\",\"3eNAFJ\":\"텍스트 창의 텍스트를 읽음으로 설정\",\"5yRaVL\":\"텍스트 창의 텍스트를 읽지 않음으로 설정\",\"Diw4et\":\"소\",\"4BzOtt\":\"미니 프로필 이미지\",\"f0BISX\":\"미니 프로필 용기\",\"1nPKgD\":\"미니 프로필 파일\",\"Bpp7ke\":\"미니 프로필 표시 상태\",\"0iXI5f\":\"아직 폰트가 추가되지 않았습니다\",\"ugHPWR\":\"아직 구성된 폰트가 없습니다\",\"6754WP\":\"계층 순서\",\"vjbngg\":\"가운데 정렬\",\"lL0plW\":\"가운데 맞춤\",\"lxKfe1\":\"미니 프로필 표시\",\"kp6HDd\":\"잠금 해제되지 않은 노드 표시\",\"KoFqVI\":\"속성\",\"KHw8dl\":\"왼쪽\",\"+b1cs9\":\"좌측 스탠딩\",\"U/hj+N\":\"좌측 정렬\",\"Tc1V/S\":\"생성됨\",\"BUYNnD\":\"활성화됨: 삭제 작업은 파일을 휴지통으로 이동합니다\",\"RO1pYQ\":[[\"0\"],\" 파일 또는 폴더가 이미 존재합니다. 다른 이름을 입력해주세요\"],\"/ctKDE\":\"빨리 감기한 줄 수\",\"W7nUsD\":\"비활성화됨: 삭제 작업은 파일을 영구 삭제합니다\",\"i5KfKE\":\"읽은 텍스트\",\"/riza6\":\"읽은 텍스트 내부\",\"KzGK3I\":\"읽은 텍스트 외부\",\"tBIZt9\":\"도움말\",\"k0vDpg\":\"도움말 문서\",\"Ysz9K8\":\"자주 쓰는 설정\",\"FfZSjF\":\"기본 연출\",\"AM6qht\":\"타일 배열\",\"84Kfga\":\"병렬 애니메이션\",\"7P0aRQ\":\"적용\",\"Hzp94p\":\"스타일 적용\",\"5IGQoe\":\"적용된 템플릿\",\"wTGMhd\":\"색상 변경 적용\",\"K8lqFB\":\"지연 시간(초)\",\"TAiYqj\":\"켜기\",\"GGYg+t\":\"시작\",\"jZlQQB\":\"이동 시작\",\"CJMpjt\":\"벌린 입\",\"sTLh1u\":\"강도\",\"bcDpsn\":\"현재 장면\",\"eWKbZk\":\"현재 장면에 선택 가능한 태그가 없습니다\",\"HxzEI9\":\"현재 portable 모드이며, 실제 데이터 목록은 다운로드 목록 내의 data 폴더에 의해 결정됩니다.\",\"dD/c/t\":\"이동 가능한 내용을 감지하지 못했지만, 새 버전은 이미 사용자 데이터 목록으로 전환되었습니다.\",\"YS+bsM\":\"현재 버전\",\"/eHs6F\":\"현재 상태\",\"sogedR\":\"현재 환경은 시스템 파일 탐색기를 지원하지 않습니다. 경로를 수동으로 입력해 주세요.\",\"/K/Jca\":\"현재 미리보기 변수\",\"BQWdVc\":\"독일어\",\"AtV2zZ\":\"빠른 미리보기 효과\",\"iXWuW8\":\"빠른 미리보기 효과 꺼짐\",\"MI1wom\":\"빠른 미리보기 효과 켜짐\",\"4fasly\":\"빠른 미리보기 문장\",\"It2H0T\":\"빠른 미리보기 문장 꺼짐\",\"SPZNPb\":\"빠른 미리보기 문장 켜짐\",\"HJOSnW\":\"무시\",\"LgENDV\":\"이 버전 무시\",\"eTTofS\":\"기본 목록으로 복원\",\"NfnpFD\":\"게임 삭제를 확정하겠습니다.\",\"Beao/k\":\"수동으로 다음 문장을 표시\",\"Rt57iW\":\"ID 수동 입력\",\"1rlyyl\":\"다운로드 목록을 열기\",\"jpPLGO\":\"전체 설정 열기\",\"aiJY5K\":\"영화 모드 켜기/끄기\",\"c+tywy\":\"이펙트 에디터 열기\",\"KFUqJk\":\"사용자 데이터 목록 열기\",\"bpjf7e\":\"설정 열기\",\"brKARj\":\"설정 디렉터리 열기\",\"A1LFzH\":\"이 문장까지 실행\",\"M/0TFg\":\"실행 후 캐릭터 이름 초기화\",\"NXEIyt\":\"확장명\",\"IVbgAn\":\"드래그하여 변형 조정(빠른 미리보기 효과를 켜는 것을 권장합니다)\",\"I2S29O\":\"이전 텍스트 창 내의 문장과 이어 붙이기\",\"4zkD4P\":\"연결 모드\",\"80W7XM\":\"지속 시간(밀리초)\",\"Yu/zJb\":\"포인터\",\"GL6/RW\":\"정렬 방식\",\"zY4737\":\"텍스트 창 표시 여부 제어\",\"qZOPAd\":\"제출\",\"z+vCyE\":\"수정 제출\",\"5tNImB\":\"힌트\",\"lSDxhM\":\"힌트:\",\"a+Vt/6\":\"힌트: 스탠딩/배경을 먼저 설정한 후 애니메이션을 적용하세요. 아니면 대상을 찾을 수 없습니다.\",\"AxfvpJ\":\"힌트: 편집을 끝낸 후 유효하지 않은 CG/BGM이 있는 경우, WebGAL 게임 화면의 옵션에서 '모든 데이터 삭제'를 선택하여 초기화하세요.\",\"jj4R18\":\"힌트: 줄바꿈은 최대 3줄까지 가능합니다\",\"0XGtc4\":\"힌트: 이펙트는 다른 스탠딩으로 전환하거나 이전 스탠딩을 끄고 다시 추가할 때만 적용됩니다. 기존 스탠딩에 이펙트를 설정하려면 개별 이펙트 설정 명령을 사용하세요.\",\"U09jTA\":\"알림: 이펙트는 다른 배경으로 전환하거나 이전 배경을 끄고 다시 추가할 때만 적용됩니다. 기존 배경에 이펙트를 설정하려면 개별 이펙트 설정 명령을 사용하세요.\",\"edt8dJ\":\"알림: 스타일에 사용되는 리소스는 assets 목록 안에 배치하세요\",\"XtMbyn\":\"힌트 버튼 텍스트\",\"Gk7oil\":\"옵션 검색\",\"R/Ak5y\":\"비디오 클립 재생\",\"r5d14I\":\"효과음 클립 재생\",\"OM/lPn\":\"효과음 재생\",\"45SohF\":\"비디오 재생\",\"5oBbwZ\":\"조작\",\"LE7+P/\":\"조작 실패\",\"6H0Rzq\":\"글리치 영화 필터\",\"/07ndC\":\"이펙트\",\"3Dp9hq\":\"효과음\",\"2QqRJk\":\"이펙트 편집\",\"Itta+U\":\"이펙트 에디터\",\"9wRgLM\":\"이펙트 옵션\",\"N51b5K\":\"효과음 ID\",\"y21/fE\":\"효과음 ID (입력 시 효과음이 루프되며, 나중에 이 ID를 사용하여 끌 수 있습니다)\",\"LgZspE\":\"효과음 음량\",\"2KoYhh\":\"효과음 재생 상태\",\"PZAGAX\":\"효과음 파일\",\"hQpdKw\":\"파일 이름\",\"4vzm41\":\"파일 이름에 특수 기호를 포함할 수 없습니다\",\"exMMNn\":\"파일 크기\",\"UfcQun\":\"파일 경로에 잘못된 기호가 포함되어 있습니다\",\"ZqMdtM\":\"글자 크기\",\"25djyb\":\"텍스트가 모두 표시된 후 다음 문장을 실행\",\"Xe6iTK\":\"텍스트가 모두 표시된 후 대기\",\"AbDeuF\":\"글자 색상\",\"8WMg1Y\":\"텍스트 정렬\",\"xj/ZNd\":\"텍스트 표시 후 연속 실행\",\"nOB9MD\":\"텍스트 표시\",\"SfnBqH\":\"대화창\",\"eE+Ord\":\"대화창(미니 프로필 꺼짐)\",\"pnzpdc\":\"대화창 텍스트\",\"fdGpE4\":\"대화창 표시 상태\",\"nGBSca\":\"대화창 배경\",\"JMaNpp\":\"텍스트 길이(em)\",\"x9T6kF\":\"텍스트 그림자\",\"xbs2D9\":\"문서\",\"V2DE3t\":\"변수 추가\",\"+5qs/w\":\"새 폰트 추가\",\"LUK5zU\":\"새 파일\",\"126tQc\":\"새 폴더\",\"G7saQ/\":\"새 템플릿\",\"21csR9\":\"새 플로차트\",\"rXcvwy\":\"새 게임\",\"1pF7Zd\":\"새 파일 이름\",\"9z9vJ8\":\"새 폴더 이름\",\"/RuL7b\":\"새 탭 미리보기\",\"kg3joS\":\"새로운 템플릿\",\"IvrO4n\":\"새로운 게임\",\"v0fvoZ\":\"사각형\",\"sWI3tG\":\"내레이션 모드, 캐릭터 이름 없음\",\"ik1gpm\":\"회전 (호도)\",\"Kr6U3I\":\"회전(각도)\",\"XvTqzn\":\"없음\",\"OfjOK8\":\"일치하는 옵션 없음\",\"dVaVRr\":\"일본어\",\"SvylRi\":\"구버전 커스텀 엔진\",\"dgVFlc\":\"구버전 게임 목록\",\"jh/dy4\":\"구버전 커스텀 템플릿\",\"0n5tzF\":\"팝업 고정 여부\",\"DXvwOs\":\"커스텀 구분 기호 사용 여부\",\"6N+UWw\":[\"\\\"\",[\"0\"],\"\\\"을(를) 삭제하시겠습니까?\"],\"p4ahuY\":[\"\\\"\",[\"gameName\"],\"\\\"을(를) 삭제하시겠습니까?\"],\"VHPKr9\":\"사이드바 표시\",\"dz9ELq\":\"변수 표시\",\"dSlg2+\":\"이펙트 표시\",\"ZTwiNO\":\"대화창 표시\",\"3eT2Lc\":\"스탠딩 표시\",\"XKcypk\":\"배경 표시\",\"c6zSKZ\":\"캐릭터 이름 표시\",\"7oSWX7\":\"미리보기 창 표시\",\"N0xob0\":\"일반 대화\",\"EqrmU6\":\"변수 스냅샷 없음\",\"sK6GZl\":\"디버그 변수가 없습니다. “변수 추가”를 클릭하여 생성하세요.\",\"NFJ1K/\":\"추가 설정\",\"Bzk8JL\":\"더 많은 옵션\",\"HI9ajp\":\"추가 구성\",\"ZpRWXT\":\"UI 스타일 태그 교체\",\"XwMGie\":\"동일 대상 애니메이션 교체\",\"ZlhOAY\":\"지정되지 않음\",\"uGVPQd\":\"인식되지 않음\",\"PM5Ds/\":\"인식되지 않은 명령어\",\"OT2noP\":\"이동 대상이 선택되지 않았습니다\",\"HCmZmd\":\"이 문장 앞에 문장 삽입\",\"4jptfY\":\"이 문장 실행 후 다음 문장 실행\",\"1Qxsnf\":\"이 문장 실행 후 대기\",\"rSc/B7\":\"이 장면 내의 유일한 label 이름\",\"n5ywqz\":\"조건 실행\",\"cD7Nwy\":\"실시간 미리보기 스냅샷에서 가져옴\",\"QcDp1B\":\"WebGAL 홈페이지 가이드 보기\",\"4/5pw9\":\"이동된 문서 보기\",\"9qqIHt\":\"태그\",\"OcMqmU\":\"태그명\",\"bXluqY\":\"타이틀\",\"IBpbBo\":\"타이틀 예비 배경\",\"sm61Fq\":\"타이틀 버튼\",\"cCaoCQ\":\"타이틀 버튼 목록\",\"z6W0W9\":\"타이틀 버튼 텍스트\",\"efdGnC\":\"타이틀 버튼 텍스트 내부\",\"adOfww\":\"타이틀 버튼 텍스트 외부\",\"mHVFZb\":\"타이틀 버튼 비활성화\",\"0qw1qw\":\"타이틀 배경 이미지\",\"dF9IWl\":\"타이틀 배경음악\",\"h7RStd\":\"검증 실패 알림\",\"WGLiaE\":\"스타일 교체\",\"jz/awg\":\"루트 텍스트 길이 (rem)\",\"1+2L9V\":\"루트 노드\",\"K1AQKm\":[\"이동이 필요한 내용 감지됨: \",[\"0\"],\".\"],\"pVlW0S\":\"모드\",\"Z+KZ+3\":\"템플릿\",\"fcWwlS\":\"템플릿 목록\",\"LFlBFj\":\"템플릿 이름\",\"rox69+\":\"템플릿 이름은 비워둘 수 없습니다\",\"qplWc4\":\"템플릿 불러오기 실패. 이름이 중복되거나 템플릿이 완전한지 확인하세요\",\"To9mh/\":\"템플릿 불러오기 성공\",\"OC4/zY\":\"템플릿 목록\",\"mRirE0\":\"흐림\",\"AFjvIu\":\"벚꽃\",\"mOnIih\":\"정규식 태그\",\"xmuRsu\":\"업로드 중\",\"2cT+rp\":\"템플릿 구성을 불러오는 중\",\"kj9Nk9\":\"사용자 데이터 설정을 읽는 중\",\"IzVr3S\":\"정상\",\"ur/Lu4\":\"정상적으로 BGM 재생\",\"kSc8RE\":\"곱하기\",\"mVdAcK\":\"이 명령어는 게임을 종료시킵니다.\",\"lrj0ka\":\"각 플로차트에는 루트 노드를 하나만 둘 수 있습니다\",\"GS2FI3\":\"줄바꿈 안 함\",\"8hF8WJ\":\"프랑스어\",\"wmjh1W\":\"빛 번짐\",\"k3QEyx\":\"시선\",\"qN4lqc\":\"주시점 X(-1~1)\",\"jDe3iF\":\"주시점 Y(-1~1)\",\"cWSO8i\":\"주석\",\"lTDJms\":\"주석은 편집창에만 보이며, 게임 화면에 실행되지 않습니다\",\"HsqwrS\":\"플로차트 목록\",\"GUjz+8\":\"플로차트 기능(실험적)\",\"Rq1KKu\":\"플로차트 편집기\",\"zlIdoj\":\"밝은 색\",\"421vib\":\"브라우저가 목록를 선택했지만, 안전으로 인해 로컬 경로가 노출되지 않아 이동에 직접 사용할 수 없습니다. 경로를 수동으로 입력해 주세요.\",\"MzKPVG\":\"페이드 인/아웃\",\"zvy4g4\":\"어두운 색\",\"l3sUO5\":\"혼합 모드\",\"Btmkds\":\"추가\",\"e4Fwgn\":\"대화를 한 줄 추가하며 음성을 첨부할 수 있습니다\",\"HNs965\":\"CG 감상 인터페이스에 이미지 한 장 추가\",\"jqcHiz\":\"주석 한 줄 추가\",\"3b5CZ7\":\"음악 감상 인터페이스에 음악 한 곡 추가\",\"vlP6yy\":\"애니메이션 프레임 추가\",\"3TW1Bl\":\"속성 추가\",\"WWXT9D\":\"지정된 위치의 스탠딩을 추가하거나 전환합니다\",\"9tBJog\":\"새 줄 추가\",\"dem41Q\":\"루트 노드 추가\",\"ziI5AO\":\"커스텀 속성 추가\",\"1GdTCH\":\"노드 추가\",\"BmJh7o\":\"문장 추가\",\"ZhE+iM\":\"현재 스테이지의 이펙트 초기화\",\"zhmz6S\":\"알림 초기화\",\"Qhrqw8\":\"특수 효과 초기화\",\"vbktCH\":\"스탠딩 초기화\",\"cKm/RQ\":\"게임\",\"+yl443\":\"게임 목록\",\"MX3KTN\":\"게임 패키지 이름\",\"v1nnHf\":\"게임 이름\",\"/IxVmK\":\"게임 아이콘\",\"mG4Gl3\":\"게임 제어\",\"BN1ggT\":\"게임 목록\",\"PoJmXT\":\"게임 소개\",\"zw1gej\":\"게임 식별 코드\",\"nF64Xp\":\"게임 구성\",\"DFrvDB\":\"소스 코드\",\"sYmCuk\":\"스크린\",\"PWpJXC\":\"필터\",\"oTVGa5\":\"파일을 클릭하거나 여기로 드래그하여 업로드\",\"sXWAgH\":\"노드를 클릭하여 장면 파일 선택\",\"BxKZLo\":\"점박이 영화 필터\",\"Xj8vOB\":\"특수 효과 출처\",\"aQtyiH\":\"특수 효과 상태\",\"hD7QyJ\":\"특수 이펙트\",\"X1oR2i\":\"사용자 데이터\",\"DmxF7+\":\"사용자 데이터 위치\",\"EekhEj\":\"사용자 데이터 디렉터리\",\"OCkJC9\":\"사용자 데이터 마이그레이션\",\"KfhNrP\":\"사용자 디렉터리 모드\",\"Ypvisl\":\"이전 문장의 next에 의해 연속 실행\",\"EVx+TX\":\"시네마틱 모드\",\"ktyz3u\":\"시네마틱 모드\",\"I87OCc\":\"비워두면 기본값 사용\",\"c+IVlb\":\"백분율. 0-100 유효\",\"gi2fy8\":\"현재 열려 있는 파일이 없습니다\",\"7IcZx8\":\"대상\",\"AQ43u3\":\"대상 ID\",\"M+xcl2\":\"대상 줄\",\"gcRMK0\":\"상대적\",\"3NBJK7\":\"눈 깜박임\",\"8sFmsw\":\"눈 깜박임 간격 (밀리초)\",\"uROCuu\":\"눈 깜박임 간격 무작위 변화 (밀리초)\",\"yNBfEJ\":\"눈 뜨기\",\"ckvA2A\":\"눈 뜬 시간 (밀리초)\",\"6Ql6i3\":\"확인\",\"ErIaG5\":[\"\\\"\",[\"templateName\"],\"\\\" 템플릿을 삭제하시겠습니까?\"],\"snCgV5\":\"확인 버튼 텍스트\",\"ncHulm\":\"보기\",\"zKI8OE\":\"비주얼 노벨 편집, 그리고 진화\",\"l3Q20d\":\"비디오\",\"VbUGdf\":\"비디오 파일\",\"6QlMn6\":\"비디오 스킵\",\"6nUdFs\":\"스킵 금지\",\"+683uI\":\"비디오 스킵 금지\",\"23W6Wl\":\"비활성화\",\"+xsk+g\":\"휴지통으로 이동하지 못했습니다\",\"h9pF1u\":\"즉시 주시\",\"ihAyIf\":\"스탠딩\",\"FcjaTr\":\"스탠딩 ID\",\"EYmzxD\":\"스탠딩 ID(선택 사항)\",\"1OhtrG\":\"스탠딩 위치\",\"K8GECw\":\"스탠딩 일러스트 ID\",\"RZ8SvI\":\"스탠딩 파일\",\"gMyE6M\":\"스탠딩 초기화 매개변수\",\"w6EWzr\":\"대기\",\"n1wZjH\":\"일정 시간 대기\",\"2qySjq\":\"대기 시간(단위: 밀리초)\",\"5/yex+\":\"중국어 간체\",\"/xmozg\":\"빨강 채널\",\"QFGCRR\":\"긴급 회피\",\"31sI1g\":\"텍스처\",\"4Ryv+h\":\"선형\",\"I5B8IR\":\"선형 닷지\",\"mIujq6\":\"선형 그라디언트\",\"BVUmrx\":\"종점 반동\",\"zfY9Wq\":\"종점 튕김\",\"7ccx+d\":\"종료 후 유지\",\"TL09Qo\":\"현재 BGM 재생 종료\",\"b6/zT3\":\"현재 게임을 종료하고 타이틀 화면으로 돌아갑니다\",\"nsMb6+\":\"게임 종료\",\"q1eJm6\":\"절대적\",\"PmBfdg\":\"기존 이펙트 유지\",\"ZuBRxU\":\"기본 이펙트 유지\",\"iLuKDS\":\"이어하기 버튼\",\"rVJOKr\":\"초록 채널\",\"EovAFg\":\"이즈 인\",\"IlRCMw\":\"이즈 인 아웃\",\"qJJ0ZL\":\"이즈 아웃\",\"TV73Uu\":\"이징 유형\",\"FIVBEw\":\"아이콘 편집\",\"ujrc0w\":\"더 많은 옵션 편집\",\"PIpq47\":\"템플릿 편집\",\"BUgUdY\":\"플로차트 편집\",\"f2Hsh4\":\"게임 편집\",\"AJlxRN\":\"편집 위젯\",\"2+9HNW\":\"중국어 번체\",\"9nARxd\":\"축소/확대\",\"ULLh8/\":\"그리드 보기\",\"ijy6uH\":\"옛날 영화 필터\",\"8tazQc\":\"소요 시간\",\"ync79W\":\"배경\",\"HtIv8c\":\"배경 이미지 위치\",\"EHsdeU\":\"배경 이미지 크기\",\"cZadEj\":\"배경 이미지\",\"BXVjkF\":\"배경 파일\",\"wtJEvW\":\"배경 표시 상태\",\"q8Wpux\":\"배경음악\",\"RxIR6z\":\"배경음악 파일\",\"pEsD4f\":\"배경 색상\",\"DjbmVo\":\"스크립트 에디터\",\"KI2Pfg\":\"자동\",\"tYkTRz\":\"플로차트 자동 저장\",\"zF11u5\":\"자동 줄바꿈\",\"mDoVSI\":\"리본 메뉴 자동 숨기기\",\"y1IFGJ\":\"커스텀\",\"7CJiec\":\"커스텀 Live2D 렌더링 범위\",\"uyE7Hp\":\"커스텀 값\",\"/inxt8\":\"커스텀 구분 기호\",\"Y9wTkP\":\"커스텀 특수 효과 이름\",\"aN6yDE\":\"커스텀 캐스케이드 선택기 구분 기호\",\"3xyq1o\":\"스테이지 객체 제어\",\"NBEJ5q\":\"스테이지 화면\",\"4Bo++T\":\"노드 작업\",\"1zf5DR\":\"영어\",\"oNrgZM\":\"최신 버전 가져오기\",\"8k5YaI\":\"사용자로부터 문자부호 입력을 획득\",\"BLtegj\":\"입력 가져오기\",\"9VjNqd\":\"파랑 채널\",\"4kyBmj\":\"줄 스크립트\",\"24OFWJ\":\"보완\",\"vVFcCc\":\"기본값 보충\",\"Vj0QFo\":\"표현식\",\"gP2Dzw\":\"자르기 모양\",\"GQZtyL\":[[\"0\"],\" 속성을 삭제하시겠습니까?\"],\"W05f7Q\":\"덮어쓰기\",\"w1aScd\":\"캐릭터 이름 (빈칸으로 두면 이전 문장에서 이어짐)\",\"6L6cDi\":\"캐릭터 이름, 이전 문장과 이으려면 빈칸:대화;\",\"Sad8Ov\":\"캐릭터 이름 내부 텍스트\",\"s3YC4c\":\"캐릭터 이름 처리\",\"01rrCD\":\"캐릭터 이름 외부 텍스트\",\"5CqTX3\":\"캐릭터 이름 용기\",\"TQWMFh\":\"캐릭터 이름 텍스트 창\",\"SaZFuD\":\"캐릭터 이름 텍스트 창 배경\",\"aSifLw\":\"캐릭터 이름 표시\",\"PcGPnf\":\"구석 미니 프로필\",\"PbRRLM\":\"명칭 해금\",\"L0LlwN\":\"업적 해금\",\"nm64JJ\":\"해금된 BGM 이름\",\"TEwxlP\":\"해금된 CG 이름\",\"rimhjz\":\"해금된 CG 또는 BGM 이름\",\"Co/7Bs\":\"해금 갤러리 유형\",\"flJD55\":\"설정\",\"epZj1P\":\"변수 설정\",\"XU5bCO\":\"일반 변수 또는 전체 변수 설정\",\"4Ikuoy\":\"스탠딩 또는 배경의 입/퇴장 애니메이션 설정\",\"ON+a2j\":\"커스텀 캐스케이드 선택기 구분 기호 설정 (입력 후 Enter 키를 눌러 확인)\",\"ihmbRH\":\"이 명령어는 텍스트 창에 현재 변수를 표시합니다.\",\"WufxGR\":\"이 명령어를 인식할 수 없습니다. 스크립트 편집 모드를 열어 수동으로 편집하세요.\",\"ZK+M59\":\"이 파일 유형은 미리보기를 지원하지 않습니다\",\"bJIN8W\":\"언어\",\"fVumBf\":\"음성\",\"X/tvHa\":\"음성 볼륨\",\"EG4Gpn\":\"각 폰트의 정보를 빠짐없이 입력해 주세요\",\"Zxehh6\":\"폰트 이름을 입력하거나 선택하세요\",\"0yFPGs\":\"검색어를 입력하세요...\",\"qw/kKq\":\"전경 이미지 조정\",\"7tVEwG\":\"배경 이미지 조정\",\"0eeLln\":\"Steam 호출\",\"908fto\":\"Steam API 호출, 다중 변수 지원\",\"AscWws\":\"장면 호출/전환\",\"A+Kvx5\":\"장면 파일을 호출하고, 종료 후 어버이 장면으로 돌아갑니다\",\"NM2PYS\":\"애니메이션 호출\",\"SFhdxy\":\"장면 호출\",\"sIqZen\":\"장면을 호출하며, 새 장면 종료 후 어버이 장면으로 돌아갑니다\",\"q2MDQ4\":\"엔진 내장 복잡 애니메이션 호출\",\"a9l6Ko\":\"디버그 변수\",\"rA5YEf\":\"리소스\",\"dIjPmP\":\"시점 종점 반동\",\"wYBRIj\":\"시점 종점 튕김\",\"Nw2SVE\":\"시점 반동\",\"FGpuUP\":\"시점 튕김\",\"rgKNR6\":\"문장 간 애니메이션\",\"r2iiTZ\":\"태그 이동\",\"gS/kSz\":\"장면으로 이동\",\"LcuzKM\":\"현재 장면 내의 태그으로 이동\",\"LQKR1b\":\"현재 장면 태그로 이동\",\"cjm0bm\":\"이동 태그\",\"zzTLpV\":\"부드럽게(0-1)\",\"CZvKht\":\"label 이름 입력\",\"6GZPH3\":\"임의의 태그 입력\",\"I4ELPC\":\"명령어 내용 입력\",\"/JO1a6\":\"현재 장면 내의 label 이름 입력\",\"T1KwXF\":\"검증 정규식 입력\",\"o6udFd\":\"대상 ID 입력\",\"zTyfb+\":\"이 목록으로 이동\",\"kRJCg/\":\"전환 시간(단위: 밀리초)\",\"wFPRfi\":\"파일 필터링\",\"cwfmFH\":\"실행 플랫폼\",\"SmBcVU\":\"뒤로\",\"1TOWH9\":\"입/퇴장 애니메이션\",\"tpiLRd\":\"연속 실행\",\"2wAUT7\":\"퇴장 시간(단위: 밀리초)\",\"dmA2LU\":\"노드나 연결선을 선택한 뒤 Backspace 또는 Delete 키를 누르면 삭제할 수 있습니다\",\"4IIuG6\":\"선택\",\"iAuIBv\":\"애니메이션 선택\",\"FkMdaZ\":\"애니메이션 파일 선택\",\"NbEGUP\":\"이미지 선택\",\"jNHP9q\":\"이미지 파일 선택\",\"OhgvlB\":\"장면 파일 선택\",\"zIJ3Yi\":\"미니 프로필 선택\",\"69M7Zv\":\"미니 프로필 파일 선택\",\"xHolc8\":\"적용할 템플릿 선택\",\"dmGYap\":\"효과음 파일 선택\",\"EPdKlK\":\"파일 선택\",\"38LIEV\":\"게임 엔진 버전 선택\",\"zlTAav\":\"디렉터리 선택\",\"Z8Z53Q\":\"비디오 파일 선택\",\"2I2ENa\":\"스탠딩 파일 선택\",\"72OHF4\":\"배경 이미지 선택\",\"b+7XSx\":\"배경 파일 선택\",\"AffInu\":\"배경음악 선택\",\"x2onXX\":\"음성 파일 선택\",\"ukXlZn\":\"호출 매개변수 선택\",\"q+LyW7\":\"점프 대상 선택\",\"VOdP8N\":\"입장 애니메이션 선택\",\"1j2242\":\"입장 애니메이션 파일 선택\",\"Ux++lj\":\"퇴장 애니메이션 선택\",\"SL5vhv\":\"퇴장 애니메이션 파일 선택\",\"OjVuTD\":\"갤러리 리소스 파일 선택\",\"bDEDWB\":\"사전 설정 대상 선택\",\"96JQhm\":\"색상 선택\",\"5uBl9m\":\"옵션\",\"pIlyJr\":\"옵션 목록\",\"3UJ7SP\":\"옵션 이름\",\"ionQHN\":\"선택지 버튼\",\"p4SMUC\":\"선택지 버튼(비활성화)\",\"hjiIez\":\"선택지 버튼 외층\",\"sM2BIi\":\"투명도(0-1)\",\"oNWnYZ\":\"일반\",\"o+zYhS\":\"선택지를 통해 다른 장면으로 진입합니다\",\"8uQYfH\":\"구성\",\"4GHpC0\":\"구성은 항상 통합된 .webgal_terre 디렉터리에 저장됩니다. 게임, 커스텀 템플릿, 커스텀 엔진은 다른 디스크로 이동 할 수 있습니다.\",\"9/7aYu\":\"템플릿 구성\",\"DwDd05\":\"설정 목록\",\"nlSg/s\":\"이름 바꾸기\",\"utQhKE\":\"템플릿 목록의 이름 바꾸기\",\"oLjZxR\":\"게임 목록의 이름 바꾸기\",\"NUIley\":\"감상(갤러리)\",\"YjDE9d\":\"갤러리\",\"VL0Lml\":\"이미지 감상\",\"tQwgmO\":\"감상 정렬\",\"N5WNMM\":\"시리즈 감상\",\"6jwl0g\":\"감상 리소스 파일\",\"IxlRXl\":\"음악 감상\",\"gvQ8W0\":\"장기\",\"Nfy7bQ\":\"장기 변수\",\"PZzhTE\":\"장기 변수 용도\",\"zqVxgb\":\"다문 입\",\"mlaDy3\":\"감은 눈\",\"YiB7xn\":\"눈 감은 시간(밀리초)\",\"DmRnQt\":\"임계값(Threshold)\",\"aP3Sr2\":\"사이드바 숨기기\",\"Xrn1+k\":\"대화창 숨기기\",\"Y6FOz1\":\"캐릭터 이름 숨기기\",\"Ero88N\":\"비\",\"u/Wc2v\":\"눈\",\"RTl+vt\":\"정적\",\"ioXuTK\":\"위쪽 정렬\",\"45SR3o\":\"아래쪽 정렬\",\"WK1sjs\":\"오른쪽 정렬\",\"tLjs3i\":\"왼쪽 정렬\",\"VpqMXM\":\"음악\",\"Sojbxt\":\"프로젝트 홈페이지\",\"/nWgLT\":\"사전 반전\",\"4P9ckh\":\"사전 설정 템플릿\",\"BGUvk3\":\"사전 설정 템플릿은 설치 디렉터리에 저장됩니다. 편집하려면 먼저 커스텀 템플릿으로 복사하세요.\",\"g4jjs+\":\"미리보기\",\"sOx1hS\":\"미리보기 아이콘\",\"sXJqG4\":\"미리보기 창\",\"L3omXQ\":\"색상\",\"2Jfux7\":\"색상 조정\",\"yjBCOs\":\"채도\",\"1I7Hpb\":\"높이\",\"kduvmj\":\"가우시안 흐림\",\"ozxBe2\":\"기본값\",\"5oYHM9\":\"기본값 default\",\"tsdOxl\":\"기본값\",\"LeACf8\":\"기본값 0\",\"fzevFI\":\"기본값 1\",\"o3LaEy\":\"기본값 100\",\"g8d5Vh\":\"기본값 1000\",\"O/vKJn\":\"기본값 150\",\"FOOWl6\":\"기본값 24시간\",\"Wdn4MI\":\"기본값 50\",\"wbrcoe\":\"기본 변환 및 효과\",\"BYO6xr\":\"기본 언어\",\"Nr4gXI\":\"기본 옵션\",\"4sxeqQ\":\"마우스 호버 스타일\",\"6Q3X1x\":\"마우스 포인터\",\"Zqpl0E\":\"마우스 클릭 스타일\"}")as Messages; \ No newline at end of file diff --git a/packages/origine2/src/locales/zhCn.po b/packages/origine2/src/locales/zhCn.po index 18830df8..e30a928b 100644 --- a/packages/origine2/src/locales/zhCn.po +++ b/packages/origine2/src/locales/zhCn.po @@ -641,6 +641,18 @@ msgstr "可选项" msgid "右侧" msgstr "右侧" +msgid "右侧 1/3" +msgstr "右侧 1/3" + +msgid "右侧 1/3 立绘" +msgstr "右侧 1/3 立绘" + +msgid "右侧 1/4" +msgstr "右侧 1/4" + +msgid "右侧 1/4 立绘" +msgstr "右侧 1/4 立绘" + msgid "右侧立绘" msgstr "右侧立绘" @@ -905,6 +917,18 @@ msgstr "属性" msgid "左侧" msgstr "左侧" +msgid "左侧 1/3" +msgstr "左侧 1/3" + +msgid "左侧 1/3 立绘" +msgstr "左侧 1/3 立绘" + +msgid "左侧 1/4" +msgstr "左侧 1/4" + +msgid "左侧 1/4 立绘" +msgstr "左侧 1/4 立绘" + msgid "左侧立绘" msgstr "左侧立绘" From c15535ab3e19bd1f702e4047b4e320ef577d1e2a Mon Sep 17 00:00:00 2001 From: Mahiru Date: Fri, 31 Jul 2026 21:14:51 +0800 Subject: [PATCH 07/13] Editor support multiple line --- packages/origine2/package.json | 2 +- .../origine2/src/config/highlighting/hl.json | 54 +++++ .../GraphicalEditor/GraphicalEditor.tsx | 223 +++++++++++------- .../utils/sceneTextProcessor.ts | 19 ++ .../origine2/src/utils/combineSubmitString.ts | 67 +++++- packages/terre2/package.json | 2 +- .../src/Modules/lsp/completion/commandArgs.ts | 1 + .../src/Modules/lsp/completion/index.ts | 44 +++- .../terre2/src/Modules/lsp/lastVariables.ts | 7 + .../terre2/src/Modules/lsp/semanticToken.ts | 186 +-------------- .../terre2/src/Modules/lsp/sentenceLocator.ts | 108 +++++++++ .../src/Modules/lsp/sentenceTokens.spec.ts | 48 ++++ .../terre2/src/Modules/lsp/sentenceTokens.ts | 154 ++++++++++++ packages/terre2/src/Modules/lsp/webgalLsp.ts | 2 - yarn.lock | 8 +- 15 files changed, 648 insertions(+), 277 deletions(-) create mode 100644 packages/terre2/src/Modules/lsp/lastVariables.ts create mode 100644 packages/terre2/src/Modules/lsp/sentenceLocator.ts create mode 100644 packages/terre2/src/Modules/lsp/sentenceTokens.spec.ts create mode 100644 packages/terre2/src/Modules/lsp/sentenceTokens.ts diff --git a/packages/origine2/package.json b/packages/origine2/package.json index 381bbead..3ad227bd 100644 --- a/packages/origine2/package.json +++ b/packages/origine2/package.json @@ -63,7 +63,7 @@ "sass": "^1.51.0", "swr": "^2.2.4", "uuid": "^13.0.0", - "webgal-parser": "4.6.2", + "webgal-parser": "4.6.3-beta.2", "zustand": "^4.5.2" }, "devDependencies": { diff --git a/packages/origine2/src/config/highlighting/hl.json b/packages/origine2/src/config/highlighting/hl.json index 80a26dc1..48fc6c64 100644 --- a/packages/origine2/src/config/highlighting/hl.json +++ b/packages/origine2/src/config/highlighting/hl.json @@ -245,8 +245,62 @@ } } }, + "continuation-arguments": { + "comment": "多行语句的参数续行,ie ` -kwarg0=val0 -kwarg1`。行首空白提供了 argument-list 需要的分隔空格", + "match": "^(\\s+\\-(?:\\\\.|[^\\n;])*)($|(? void; + onAddBefore: (titleText: string, insertLine: number) => void; onDelete: (index: number) => void; onSync: (index: number) => void; onToggleShow: (index: number) => void; onUpdate: (newContent: string, updateIndex: number) => void; } +/** 找到覆盖指定文件行(0-based)的语句 */ +const findSentenceByLine = (sentences: ISentence[], line: number) => + sentences.find(sentence => line >= sentence.startLine && line <= sentence.endLine); + interface SentenceRowContentProps extends SentenceRowProps { provided: DraggableProvided; } @@ -66,7 +77,7 @@ export default function GraphicalEditor(props: IGraphicalEditorProps) { const [draggingId, setDraggingId] = useState(null); const [addSentenceDialog, setAddSentenceDialog] = useState<{ titleText: string; - insertIndex: number; + insertLine: number; } | null>(null); const updateSentenceData = useCallback((newSentences: SentenceItem[]) => { @@ -126,20 +137,47 @@ export default function GraphicalEditor(props: IGraphicalEditorProps) { }; }, [fetchScene]); - const submitScene = useCallback((newSentences: SentenceItem[], index: number) => { + const mergedSceneText = useMemo(() => mergeToString(sentenceData.map(item => item.content)), [sentenceData]); + + const parsedScene = useMemo( + () => mergedSceneText === "" ? { sentenceList: [] } : parseScene(mergedSceneText), + [mergedSceneText] + ); + + /** + * 图形编辑器展示的语句列表。多行语句的续行在解析结果里只是补齐行数的占位, + * 不单独成行;它们所占的行由所属语句的 startLine / endLine 描述。 + */ + const sentences = useMemo( + () => parsedScene.sentenceList.filter(sentence => !sentence.isLineBreakHolder), + [parsedScene] + ); + + /** + * 预处理后的行:多行语句的内容全部折叠到它的首行。 + * 按文本改写参数的编辑器(如 SentenceArgOption)拿到的是这份折叠后的单行。 + */ + const foldedLines = useMemo(() => splitToArray(sceneTextPreProcess(mergedSceneText)), [mergedSceneText]); + + /** + * 写回场景,并让预览执行到刚编辑的语句。 + * + * lineNumber 的语义是「执行完目标语句后的停止指针」,所以取语句末行 + 1, + * 这样多行语句的续行占位也会被一并跨过。 + */ + const submitScene = useCallback((newSentences: SentenceItem[], startLine: number, endLine: number) => { const newScene = mergeToString(newSentences.map(item => item.content)); - const updateIndex = index + 1; - editorLineHolder.recordSceneEditingLine(props.targetPath, updateIndex); + const lineNumber = endLine + 1; + editorLineHolder.recordSceneEditingLine(props.targetPath, lineNumber); api.assetsControllerEditTextFile({ textFile: newScene, path: props.targetPath }).then(() => { - const targetValue = newSentences[index]?.content ?? ""; EditorPreviewClient.sendSyncScene({ scenePath: props.targetPath, - lineNumber: updateIndex, - lineCommandString: targetValue, + lineNumber, + lineCommandString: mergeToString(newSentences.slice(startLine, endLine + 1).map(item => item.content)), }); eventBus.emit('editor:update-scene', { scene: newScene }); }).catch(() => { @@ -148,51 +186,70 @@ export default function GraphicalEditor(props: IGraphicalEditorProps) { }, [fetchScene, props.targetPath]); const updateSentenceByIndex = useCallback((newContent: string, updateIndex: number) => { - const newSentences = [...sentenceDataRef.current]; - newSentences[updateIndex] = { ...newSentences[updateIndex], content: newContent }; + const target = sentences[updateIndex]; + if (!target) return; + const newLines = splitToArray(newContent).map(generateSentenceItem); + const newSentences = replaceLineRange(sentenceDataRef.current, target, newLines); updateSentenceData(newSentences); - submitScene(newSentences, updateIndex); - }, [submitScene, updateSentenceData]); + submitScene(newSentences, target.startLine, target.startLine + newLines.length - 1); + }, [generateSentenceItem, sentences, submitScene, updateSentenceData]); // 判断是否为空 (识别含唯一空行的文件) - function isEmpty(sentences: SentenceItem[]): boolean { - return !sentences || (sentences.length === 1 && sentences[0].content === ""); + function isEmpty(lines: SentenceItem[]): boolean { + return !lines || (lines.length === 1 && lines[0].content === ""); } - const addOneSentence = useCallback((newContent: string, insertIndex: number) => { - const newSentence = generateSentenceItem(newContent); - const newSentences = [...sentenceDataRef.current]; - const shouldReplaceEmptyLine = isEmpty(newSentences); - const targetInsertIndex = shouldReplaceEmptyLine ? 0 : insertIndex; - const deleteCount = shouldReplaceEmptyLine ? 1 : 0; - - newSentences.splice(targetInsertIndex, deleteCount, newSentence); + /** insertLine 是插入位置的文件行号(0-based) */ + const addOneSentence = useCallback((newContent: string, insertLine: number) => { + const newLines = splitToArray(newContent).map(generateSentenceItem); + const lines = sentenceDataRef.current; + // 整个文件只有一个空行时,新语句直接顶替这一行,而不是插在它前面 + const shouldReplaceEmptyLine = isEmpty(lines); + const startLine = shouldReplaceEmptyLine ? 0 : insertLine; + const endLine = shouldReplaceEmptyLine ? 0 : insertLine - 1; // 纯插入不覆盖任何已有行 + const newSentences = replaceLineRange(lines, { startLine, endLine }, newLines); updateSentenceData(newSentences); - submitScene(newSentences, targetInsertIndex); + submitScene(newSentences, startLine, startLine + newLines.length - 1); }, [generateSentenceItem, submitScene, updateSentenceData]); const deleteOneSentence = useCallback((index: number) => { - const newSentences = [...sentenceDataRef.current]; - newSentences.splice(index, 1); + const target = sentences[index]; + if (!target) return; + const newSentences = replaceLineRange(sentenceDataRef.current, target, []); updateSentenceData(newSentences); - submitScene(newSentences, Math.min(index, newSentences.length - 1)); - }, [submitScene, updateSentenceData]); + // 删除后原位置落到了下一条语句,同步到那里即可 + const focusLine = Math.min(target.startLine, newSentences.length - 1); + submitScene(newSentences, focusLine, focusLine); + }, [sentences, submitScene, updateSentenceData]); const changeShowSentence = useCallback((index: number) => { + const target = sentences[index]; const newSentences = [...sentenceDataRef.current]; - newSentences[index] = { ...newSentences[index], show: !newSentences[index].show }; + // 折叠状态记在语句的首行上 + const head = target && newSentences[target.startLine]; + if (!target || !head) return; + newSentences[target.startLine] = { ...head, show: !head.show }; updateSentenceData(newSentences); - }, [updateSentenceData]); - - // 重新记录数组顺序 - const reorder = useCallback((startIndex: number, endIndex: number) => { - const newSentences = [...sentenceDataRef.current]; - const [removed] = newSentences.splice(startIndex, 1); - newSentences.splice(endIndex, 0, removed); + }, [sentences, updateSentenceData]); + + // 重新记录数组顺序。拖拽以语句为单位,多行语句要整块搬走。 + const reorder = useCallback((sourceIndex: number, destinationIndex: number) => { + const source = sentences[sourceIndex]; + const destination = sentences[destinationIndex]; + if (!source || !destination) return; + + const movedLines = sentenceDataRef.current.slice(source.startLine, source.endLine + 1); + const restLines = replaceLineRange(sentenceDataRef.current, source, []); + // 往后拖时,目标位置的行号要减去已被移走的行数 + const insertLine = destination.startLine > source.startLine + ? destination.endLine + 1 - movedLines.length + : destination.startLine; + + const newSentences = replaceLineRange(restLines, { startLine: insertLine, endLine: insertLine - 1 }, movedLines); updateSentenceData(newSentences); - submitScene(newSentences, endIndex); - }, [submitScene, updateSentenceData]); + submitScene(newSentences, insertLine, insertLine + movedLines.length - 1); + }, [sentences, submitScene, updateSentenceData]); const onDragStart = useCallback((start: DragStart) => { setDraggingId(start.draggableId); @@ -203,22 +260,29 @@ export default function GraphicalEditor(props: IGraphicalEditorProps) { if (!result.destination) { return; } - editorLineHolder.recordSceneEditingLine(props.targetPath, result.destination.index); reorder( result.source.index, result.destination.index ); - }, [props.targetPath, reorder]); + }, [reorder]); - const syncToIndex = useCallback((index: number) => { - const targetValue = sentenceDataRef.current[index]?.content || ""; + /** 让预览执行到某条语句。行范围是文件行号(0-based,含首尾) */ + const syncToLineRange = useCallback((startLine: number, endLine: number) => { EditorPreviewClient.sendSyncScene({ scenePath: props.targetPath, - lineNumber: index + 1, - lineCommandString: targetValue, + lineNumber: endLine + 1, + lineCommandString: mergeToString( + sentenceDataRef.current.slice(startLine, endLine + 1).map(item => item.content) + ), force: true, }); - editorLineHolder.recordSceneEditingLine(props.targetPath, index + 1); + editorLineHolder.recordSceneEditingLine(props.targetPath, endLine + 1); + }, [props.targetPath]); + + const syncToIndex = useCallback((index: number) => { + const target = sentences[index]; + if (!target) return; + syncToLineRange(target.startLine, target.endLine); // 传递假消息,为了在不使用此功能的时候清除拖拽框 eventBus.emit('editor:pixi-sync-command', { targetPath: '', @@ -226,17 +290,14 @@ export default function GraphicalEditor(props: IGraphicalEditorProps) { lineContent: "", lineSentence: null, }); - }, [props.targetPath]); + }, [sentences, syncToLineRange]); const syncCurrentLine = useCallback(() => { - const lineNumber = editorLineHolder.getSceneLine(props.targetPath) || 1; - EditorPreviewClient.sendSyncScene({ - scenePath: props.targetPath, - lineNumber, - lineCommandString: sentenceDataRef.current[lineNumber - 1]?.content || "", - force: true, - }); - }, [props.targetPath]); + // 记录的是「停止指针」,它指向的语句在上一行 + const recordedLine = (editorLineHolder.getSceneLine(props.targetPath) || 1) - 1; + const target = findSentenceByLine(sentences, recordedLine); + syncToLineRange(target?.startLine ?? recordedLine, target?.endLine ?? recordedLine); + }, [props.targetPath, sentences, syncToLineRange]); useEffect(() => { eventBus.on('editor:sync-current-line', syncCurrentLine); @@ -260,46 +321,43 @@ export default function GraphicalEditor(props: IGraphicalEditorProps) { }; }, [handleAdd]); - const openAddSentenceDialog = useCallback((titleText: string, insertIndex: number) => { - setAddSentenceDialog({ titleText, insertIndex }); + const openAddSentenceDialog = useCallback((titleText: string, insertLine: number) => { + setAddSentenceDialog({ titleText, insertLine }); }, []); const handleChooseSentence = useCallback((newSentence: string) => { if (!addSentenceDialog) { return; } - addOneSentence(newSentence, addSentenceDialog.insertIndex); + addOneSentence(newSentence, addSentenceDialog.insertLine); setAddSentenceDialog(null); }, [addOneSentence, addSentenceDialog]); - const mergedSceneText = useMemo(() => mergeToString(sentenceData.map(item => item.content)), [sentenceData]); - - const parsedScene = useMemo( - () => mergedSceneText === "" ? { sentenceList: [] } : parseScene(mergedSceneText), - [mergedSceneText] - ); const sceneLabels = useMemo( - () => parsedScene.sentenceList + () => sentences .filter(sentence => sentence.command === commandType.label) .map(sentence => sentence.content.trim()) .filter(Boolean), - [parsedScene] + [sentences] ); const rowVirtualizer = useVirtualizer({ - count: parsedScene.sentenceList.length, + count: sentences.length, getScrollElement: () => scrollElementRef.current, - estimateSize: (index) => sentenceData[index]?.show ? 120 : 48, - getItemKey: (index) => sentenceData[index]?.id ?? index, + estimateSize: (index) => sentenceData[sentences[index]?.startLine]?.show ? 120 : 48, + getItemKey: (index) => sentenceData[sentences[index]?.startLine]?.id ?? index, overscan: 8, }); const virtualRows = rowVirtualizer.getVirtualItems(); + // 语句条数变化(打开场景、增删语句)后,把上次编辑的位置滚回视野 useEffect(() => { const targetLine = editorLineHolder.getSceneLine(props.targetPath); - if (targetLine > 3 && targetLine <= parsedScene.sentenceList.length) { - rowVirtualizer.scrollToIndex(targetLine - 1, { align: 'start' }); + const rowIndex = sentences.findIndex(sentence => targetLine - 1 <= sentence.endLine); + if (targetLine > 3 && rowIndex >= 0) { + rowVirtualizer.scrollToIndex(rowIndex, { align: 'start' }); } - }, [parsedScene.sentenceList.length, props.targetPath, rowVirtualizer]); + // sentences 每次解析都是新数组,这里只依赖条数,避免编辑时反复滚动 + }, [sentences.length, props.targetPath, rowVirtualizer]); useEffect(() => { const handleDragUpdate = (data: any) => { @@ -317,14 +375,17 @@ export default function GraphicalEditor(props: IGraphicalEditorProps) { }, [fetchScene]); const getSentenceRowProps = (i: number): SentenceRowProps | null => { - const sentence = parsedScene.sentenceList[i]; - const sentenceItem = sentenceData[i]; + const sentence = sentences[i]; + // 语句的 id 与折叠状态都记在它的首行上 + const sentenceItem = sentence && sentenceData[sentence.startLine]; if (!sentence || !sentenceItem) return null; return { sentence, sentenceItem, index: i, - linkedWithPrevious: i > 0 && getArgByKey(parsedScene.sentenceList[i - 1], "next") === true, + startLine: sentence.startLine, + foldedSentence: foldedLines[sentence.startLine] ?? sentenceItem.content, + linkedWithPrevious: i > 0 && getArgByKey(sentences[i - 1], "next") === true, targetPath: props.targetPath, sceneLabels, onAddBefore: openAddSentenceDialog, @@ -398,13 +459,15 @@ export default function GraphicalEditor(props: IGraphicalEditorProps) { const StableGlobalTerrePanel = memo(GlobalTerrePanel); const SentenceRowContent = (props: SentenceRowContentProps) => { - const { provided, sentence, sentenceItem, index: i, linkedWithPrevious, targetPath, sceneLabels } = props; + const { provided, sentence, sentenceItem, index: i, startLine, linkedWithPrevious, targetPath, sceneLabels } = props; + // index 只作为面板展开状态的标识,展示给用户的是语句首行在文件中的行号 const index = i + 1; + const lineNumber = startLine + 1; const sentenceConfig = sentenceEditorConfig.find((e) => e.type === sentence.command) ?? sentenceEditorDefault; const SentenceEditor = sentenceConfig.component; const argOption = sentenceConfig !== sentenceEditorDefault && sentence.command !== commandType.comment && { />; const inlineArgOption = inlineArgOptionCommands.has(sentence.command); - return
@@ -426,13 +489,13 @@ const SentenceRowContent = (props: SentenceRowContentProps) => { props.onAddBefore(t`本句前插入句子`, i)} + onClick={() => props.onAddBefore(t`本句前插入句子`, startLine)} />
-
{index} +
{lineNumber}
@@ -487,6 +550,8 @@ const SentenceRow = memo((props: SentenceRowProps) => { prev.sentence === next.sentence && prev.sentenceItem === next.sentenceItem && prev.index === next.index && + prev.startLine === next.startLine && + prev.foldedSentence === next.foldedSentence && prev.linkedWithPrevious === next.linkedWithPrevious && prev.targetPath === next.targetPath && prev.sceneLabels === next.sceneLabels diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/utils/sceneTextProcessor.ts b/packages/origine2/src/pages/editor/GraphicalEditor/utils/sceneTextProcessor.ts index 6dd5662b..aed32a09 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/utils/sceneTextProcessor.ts +++ b/packages/origine2/src/pages/editor/GraphicalEditor/utils/sceneTextProcessor.ts @@ -5,3 +5,22 @@ export const splitToArray = (rawText:string)=>{ export const mergeToString = (stringArray:string[])=>{ return stringArray.join('\n'); }; + +/** + * 用新的行替换脚本里的一段行范围(含首尾)。 + * + * 图形编辑器改动一条语句时,脚本被切成「语句前 / 语句 / 语句后」三段, + * 只重写中间一段,其余行原样保留。一条语句可能占据多行,所以这里按范围而非单行替换。 + * + * 纯插入传 `endLine = startLine - 1`(即不覆盖任何已有行),纯删除传空的 `newLines`。 + * range 的字段与解析结果里的 ISentence 一致,可以直接把语句传进来。 + */ +export const replaceLineRange = ( + lines: T[], + range: { startLine: number; endLine: number }, + newLines: T[], +): T[] => [ + ...lines.slice(0, range.startLine), + ...newLines, + ...lines.slice(range.endLine + 1), + ]; diff --git a/packages/origine2/src/utils/combineSubmitString.ts b/packages/origine2/src/utils/combineSubmitString.ts index 3255a511..98698bf7 100644 --- a/packages/origine2/src/utils/combineSubmitString.ts +++ b/packages/origine2/src/utils/combineSubmitString.ts @@ -26,7 +26,12 @@ export function combineSubmitString ( if (nArg.value === "") { nArg.value = false; } - argStrings.push(argToSimplifiedString(nArg.key, nArg.value)); + const simplified = argToSimplifiedString(nArg.key, nArg.value); + // 关闭的布尔参数会得到空串。它并不是一个参数,必须丢掉: + // 单行输出时它只是被 join 吞掉,但多行输出会让它白白占据一行。 + if (simplified !== "") { + argStrings.push(simplified); + } } else { argStrings.push(argToString(nArg.key, nArg.value)); } @@ -37,17 +42,63 @@ export function combineSubmitString ( argStrings.push(argToString(k, v)); }); - let combinedString = ""; - if (commandStr !== undefined) { - combinedString += `${commandStr}:`; + const head = commandStr === undefined ? content : `${commandStr}:${content}`; + const tail = inlineComment && inlineComment.trim().length > 0 ? `; ${inlineComment.trim()}` : ";"; + + const singleLine = `${head}${argStrings.join("")}${tail}`; + if (displayWidth(singleLine) <= MULTILINE_THRESHOLD) { + return singleLine; } - combinedString += `${content}${argStrings.join("")};`; - if (inlineComment && inlineComment.trim().length > 0) { - combinedString += ` ${inlineComment.trim()}`; + return foldToMultiline(commandStr, head, argStrings, tail) ?? singleLine; +} + +/** 合成结果超过这个显示宽度就折成多行,短语句保持单行以免产生大量两行语句 */ +const MULTILINE_THRESHOLD = 80; + +/** 全角字符(CJK、假名、全角标点等),在等宽字体下占两列 */ +const FULL_WIDTH_CHAR = /[ᄀ-ᅟ⺀-꓏가-힣豈-﫿︰-﹯＀-⦆¢-₩]/; + +/** + * 按显示宽度而非字符数衡量长度。 + * WebGAL 的对白多是中日文,按字符数算的话一行中文要写到 80 字才会折行,实际早已过长。 + */ +const displayWidth = (text: string) => + [...text].reduce((width, char) => width + (FULL_WIDTH_CHAR.test(char) ? 2 : 1), 0); + +/** 续行的缩进。解析器要求续行以空白开头,且首字符为 `-` 或 `|` */ +const CONTINUATION_INDENT = " "; + +/** + * 把过长的语句折成多行:首行放「命令 + 内容」,其后每个参数缩进独占一行。 + * + * 分号与行内注释只能留在最后一行 —— 写在首行会把后面的续行吞进注释里。 + * 含 `-concat` 的语句不折行,因为解析器遇到 `-concat` 会关闭多行折叠。 + * + * @returns 折行后的语句;无法安全折行时返回 undefined,调用方回退到单行 + */ +// eslint-disable-next-line max-params +function foldToMultiline( + commandStr: string | undefined, + head: string, + argStrings: string[], + tail: string, +): string | undefined { + if (argStrings.some(argString => argString.startsWith(" -concat")) || head.includes("\n")) { + return undefined; + } + + // intro 的内容用 `|` 分隔多段文字,是最容易超长的命令,一并拆开(转义过的 `\|` 不算分隔符) + const contentSegments = commandStr === "intro" ? head.split(/(? lines.push(`${CONTINUATION_INDENT}|${segment}`)); + argStrings.forEach(argString => lines.push(`${CONTINUATION_INDENT}${argString.trim()}`)); + + if (lines.length === 1) { + return undefined; // 没有可以拆到续行的部分 } - return combinedString; + return lines.join("\n") + tail; } /** diff --git a/packages/terre2/package.json b/packages/terre2/package.json index 6b6aaefc..2d2eaf88 100644 --- a/packages/terre2/package.json +++ b/packages/terre2/package.json @@ -63,7 +63,7 @@ "vscode-languageserver-textdocument": "^1.0.11", "vscode-ws-jsonrpc-webgal": "0.2.1", "webgal-engine": "4.6.2", - "webgal-parser": "4.6.2", + "webgal-parser": "4.6.3-beta.2", "webpack": "5.76.0", "ws": "8.18.0" }, diff --git a/packages/terre2/src/Modules/lsp/completion/commandArgs.ts b/packages/terre2/src/Modules/lsp/completion/commandArgs.ts index 5eff2e5c..64dcd545 100644 --- a/packages/terre2/src/Modules/lsp/completion/commandArgs.ts +++ b/packages/terre2/src/Modules/lsp/completion/commandArgs.ts @@ -41,6 +41,7 @@ export enum commandType { applyStyle = 32, wait = 33, callSteam = 34, + return = 35, } export function markdown(content: string): MarkupContent { diff --git a/packages/terre2/src/Modules/lsp/completion/index.ts b/packages/terre2/src/Modules/lsp/completion/index.ts index ea76779b..4a72f808 100644 --- a/packages/terre2/src/Modules/lsp/completion/index.ts +++ b/packages/terre2/src/Modules/lsp/completion/index.ts @@ -5,16 +5,19 @@ import { TextDocumentChangeEvent, } from 'vscode-languageserver'; import { TextDocument } from 'vscode-languageserver-textdocument'; -import { IScene } from 'webgal-parser/build/types/interface/sceneInterface'; +import { + IScene, + ISentence, +} from 'webgal-parser/build/types/interface/sceneInterface'; import { webgalParser } from '../../../util/webgal-parser'; import { handleAnimationFileSuggestions, handleFileSuggestions, } from './fileSuggestion'; import { commandType } from './commandArgs'; -import { lastVariables } from '../webgalLsp'; import { getCommands } from '../suggestionRules/getCommands'; import { getArgsKey } from '../suggestionRules/getArgsKey'; +import { findSentenceByLine, parseSentences } from '../sentenceLocator'; /** * Cache the last document lines @@ -89,6 +92,35 @@ function suggestVariables(params: CompletionParams) { return result; } +/** + * 取出光标所在的语句。 + * + * 光标落在多行语句的续行上时,这一行单独看既没有命令也不成句,必须回到整条语句 + * 的解析结果,参数建议才会对应正确的命令。其余情况仍然只解析光标之前的这一段, + * 这样对还没写完的语句(例如刚敲下 `changeFigure:`)也能给出建议。 + */ +function resolveSentencesAtCursor( + params: CompletionParams, + document: TextDocument, + linePrefix: string, +): ISentence[] { + const sentence = findSentenceByLine( + parseSentences(document.getText(), params.textDocument.uri), + params.position.line, + ); + + if (sentence && sentence.startLine !== params.position.line) { + return [sentence]; + } + + const scene: IScene = webgalParser.parse( + linePrefix, + 'scene.txt', + params.textDocument.uri, + ); + return scene.sentenceList; +} + export async function complete( params: CompletionParams, document: TextDocument, @@ -119,15 +151,11 @@ export async function complete( // FIXME: Known bug: `getUserInput` returns commandType 0 (say) // FIXME: Known bug: `setTransition` returns commandType 0 (say) - const scene: IScene = webgalParser.parse( - line, - 'scene.txt', - params.textDocument.uri, - ); + const sentences = resolveSentencesAtCursor(params, document, line); // Currently, there SHOULD be only one sentence. But we still handle // potential modifications to the language specification. - for (const sentence of scene.sentenceList) { + for (const sentence of sentences) { let newSuggestions: CompletionItem[] = []; if (line.includes(' -')) { diff --git a/packages/terre2/src/Modules/lsp/lastVariables.ts b/packages/terre2/src/Modules/lsp/lastVariables.ts new file mode 100644 index 00000000..032dd96e --- /dev/null +++ b/packages/terre2/src/Modules/lsp/lastVariables.ts @@ -0,0 +1,7 @@ +/** + * 上一次扫描文档时收集到的变量名 -> 定义所在行号。 + * + * 语义高亮遍历语句时写入,补全据此建议已经定义过的变量。 + * 单独成模块,是为了让两侧都只依赖这一份状态,而不必牵扯 LSP 服务器的启动逻辑。 + */ +export const lastVariables = new Map(); diff --git a/packages/terre2/src/Modules/lsp/semanticToken.ts b/packages/terre2/src/Modules/lsp/semanticToken.ts index 9a51c8be..79cfba3b 100644 --- a/packages/terre2/src/Modules/lsp/semanticToken.ts +++ b/packages/terre2/src/Modules/lsp/semanticToken.ts @@ -1,13 +1,12 @@ -import { IScene } from 'webgal-parser/build/types/interface/sceneInterface'; -import { webgalParser } from '../../util/webgal-parser'; import { SemanticTokensBuilder, SemanticTokensParams, uinteger, } from 'vscode-languageserver'; -import { commandType } from './completion/commandArgs'; import { TextDocument } from 'vscode-languageserver-textdocument'; -import { lastVariables } from './webgalLsp'; +import { lastVariables } from './lastVariables'; +import { parseSentences } from './sentenceLocator'; +import { collectSentenceTokens, IParsedToken } from './sentenceTokens'; const TOKEN_TYPES = ['variable', 'keyword', 'value', 'string']; @@ -43,181 +42,20 @@ function buildSemanticTokens(tokens: IParsedToken[]) { return builder.build(); } -interface IParsedToken { - line: number; - startCharacter: number; - length: number; - tokenType: string; - tokenModifiers: string[]; -} - -function consumeArg( - arg: { - key: string; - value: string | number | boolean; - }, - line: string, - lineNumber: number, - currentOffset, -): [IParsedToken[], number] { - if (arg.key === 'speaker') { - return [[], currentOffset]; - } - - const tokens: IParsedToken[] = []; - - // console.log(arg); - - if (arg.key.toString() !== '') { - const argKeyOffset = line.indexOf(arg.key, currentOffset); - if (argKeyOffset !== -1 && argKeyOffset >= currentOffset) { - tokens.push({ - line: lineNumber, - startCharacter: argKeyOffset, - length: arg.key.length, - tokenType: 'parameter', - tokenModifiers: [], - }); - // console.log(`[line ${lineNumber}] key: ${arg.key}: ${argKeyOffset}`); - currentOffset = argKeyOffset + arg.key.length; - } - } - - if (arg.value.toString() !== '') { - const argValueOffset = line.indexOf(arg.value.toString(), currentOffset); - if (argValueOffset !== -1 && argValueOffset >= currentOffset) { - tokens.push({ - line: lineNumber, - startCharacter: argValueOffset, - length: arg.value.toString().length, - tokenType: 'value', - tokenModifiers: [], - }); - // console.log( - // `[line ${lineNumber}]value: ${arg.value.toString()}: ${argValueOffset}`, - // ); - currentOffset = argValueOffset + arg.value.toString().length; - } - } - - return [tokens, currentOffset]; -} - +/** + * 整篇文档一次性解析,再逐条语句取 token。 + * + * 这里不能逐行解析:多行语句的参数写在续行上,单独看那一行既拿不到命令, + * 也拿不到参数,着色会断在续行处。 + */ function parseSemanticTokens(text: string, uri: string): IParsedToken[] { - const r: IParsedToken[] = []; const lines = text.split(/\r\n|\r|\n/); lastVariables.clear(); - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const scene: IScene = webgalParser.parse(line, 'scene.txt', uri); - const sentence = scene.sentenceList[0]; - - let currentOffset = 0; - - // intro has special syntax - if (sentence.command === commandType.intro) { - const values = sentence.content.split(/\|/); - - for (const value of values) { - const valueOffset = line.indexOf(value, currentOffset); - if (valueOffset === -1) { - break; - } - - r.push({ - line: i, - startCharacter: valueOffset, - length: value.length, - tokenType: 'string', - tokenModifiers: [], - }); - } - } - - // setVar has special syntax - if (sentence.command === commandType.setVar) { - if (sentence.content.match(/=/)) { - const key = sentence.content.split(/=/)[0]; - const value = sentence.content.split(/=/)[1]; - - // record variables (for completion) - if (!lastVariables.has(key)) { - lastVariables.set(key, i); - } - - let newTokens: IParsedToken[]; - [newTokens, currentOffset] = consumeArg( - { key, value }, - line, - i, - currentOffset, - ); - - newTokens.forEach((t) => r.push(t)); - } - } - - // say has special syntax - if (sentence.command === commandType.say) { - do { - const contentOffset = line.indexOf(sentence.content, currentOffset); - if (contentOffset === -1) { - break; - } - const openOffset = line.indexOf('{', contentOffset); - if (openOffset === -1) { - break; - } - const closeOffset = line.indexOf('}', openOffset); - if (closeOffset === -1) { - break; - } - - r.push({ - line: i, - startCharacter: openOffset, - length: 1, - tokenType: 'keyword', - tokenModifiers: [], - }); - - r.push({ - line: i, - startCharacter: openOffset + 1, - length: closeOffset - openOffset - 1, - tokenType: 'variable', - tokenModifiers: [], - }); - - r.push({ - line: i, - startCharacter: closeOffset, - length: 1, - tokenType: 'keyword', - tokenModifiers: [], - }); - - currentOffset = closeOffset; - } while (true); - } - - currentOffset = 0; - - const args = sentence.args.sort( - (a, b) => line.indexOf(a.key) - line.indexOf(b.key), - ); - - // other commands - for (const arg of args) { - let newTokens: IParsedToken[]; - [newTokens, currentOffset] = consumeArg(arg, line, i, currentOffset); - - newTokens.forEach((t) => r.push(t)); - } - } - return r; + return parseSentences(text, uri).flatMap((sentence) => + collectSentenceTokens(sentence, lines), + ); } function encodeTokenType(tokenType: string): uinteger { diff --git a/packages/terre2/src/Modules/lsp/sentenceLocator.ts b/packages/terre2/src/Modules/lsp/sentenceLocator.ts new file mode 100644 index 00000000..738245b6 --- /dev/null +++ b/packages/terre2/src/Modules/lsp/sentenceLocator.ts @@ -0,0 +1,108 @@ +import { + IScene, + ISentence, +} from 'webgal-parser/build/types/interface/sceneInterface'; +import { webgalParser } from '../../util/webgal-parser'; + +/** + * 一条语句可能跨越脚本文件的多行:首行写命令和内容,其后若干条以空白开头、 + * 首字符为 `-` 或 `|` 的续行承载剩余的参数或文本。 + * + * 解析器会把续行折叠进首行,并在解析结果里留下占位语句来补齐行数, + * 因此「语句 index == 文件行号」始终成立,而真正的语句用 startLine / endLine + * 描述自己占据的行范围。 + * + * 本模块把「按行找语句」和「在语句的行范围内定位文本」这两件事集中起来, + * 供语义高亮与补全共用,避免各自再按单行去解析。 + */ + +/** 解析整篇文档,只返回真正的语句(跳过续行占位) */ +export function parseSentences(text: string, uri: string): ISentence[] { + const scene: IScene = webgalParser.parse(text, 'scene.txt', uri); + return scene.sentenceList.filter((sentence) => !sentence.isLineBreakHolder); +} + +/** 找到覆盖指定行(0-based)的语句。行在注释或空行上时返回 undefined */ +export function findSentenceByLine( + sentences: ISentence[], + line: number, +): ISentence | undefined { + return sentences.find( + (sentence) => line >= sentence.startLine && line <= sentence.endLine, + ); +} + +/** 文本在文档中的位置 */ +export interface TextPosition { + line: number; + startCharacter: number; +} + +/** + * 在一条语句占据的行范围内顺序查找文本的游标。 + * + * 语句的参数可能分散在多条续行里,所以查找必须能跨行向后推进; + * 同时游标只前进不后退,保证同名文本不会被重复匹配到同一处。 + */ +export class SentenceCursor { + private line: number; + private offset = 0; + + constructor( + private readonly lines: string[], + private readonly sentence: ISentence, + ) { + this.line = sentence.startLine; + } + + /** 从当前位置向后查找,命中则把游标推到匹配内容之后 */ + find(text: string): TextPosition | undefined { + if (text === '') { + return undefined; + } + + for (let line = this.line; line <= this.sentence.endLine; line++) { + const from = line === this.line ? this.offset : 0; + const startCharacter = this.lines[line]?.indexOf(text, from) ?? -1; + + if (startCharacter !== -1) { + this.line = line; + this.offset = startCharacter + text.length; + return { line, startCharacter }; + } + } + + return undefined; + } + + /** 回到语句开头,用于换一类 token 重新扫描 */ + rewind() { + this.line = this.sentence.startLine; + this.offset = 0; + } +} + +/** + * 文本在语句行范围内首次出现的「拉平」位置,用于给参数排序。 + * + * 解析器产出的参数顺序不一定是源码顺序(命令自带的参数会排在前面), + * 而游标只能向前推进,所以定位前要先按源码顺序排好。找不到时返回 -1, + * 让这类参数排在最前,不会挡住后面的查找。 + */ +export function flatIndexInSentence( + lines: string[], + sentence: ISentence, + text: string, +): number { + let flatOffset = 0; + + for (let line = sentence.startLine; line <= sentence.endLine; line++) { + const found = lines[line]?.indexOf(text) ?? -1; + if (found !== -1) { + return flatOffset + found; + } + flatOffset += lines[line]?.length ?? 0; + } + + return -1; +} diff --git a/packages/terre2/src/Modules/lsp/sentenceTokens.spec.ts b/packages/terre2/src/Modules/lsp/sentenceTokens.spec.ts new file mode 100644 index 00000000..53fa182a --- /dev/null +++ b/packages/terre2/src/Modules/lsp/sentenceTokens.spec.ts @@ -0,0 +1,48 @@ +import { parseSentences } from './sentenceLocator'; +import { collectSentenceTokens } from './sentenceTokens'; +import { commandType } from './completion/commandArgs'; + +const MULTILINE_SCENE = [ + 'changeFigure:stand.webp', + ' -left', + ' -id=fig1 -next;', + 'intro:第一段', + ' |第二段 -hold;', +].join('\n'); + +const lines = MULTILINE_SCENE.split('\n'); + +/** 把 token 还原成它在源码里覆盖的文本,便于直观断言 */ +const tokenTexts = (line: number, tokenType?: string) => + parseSentences(MULTILINE_SCENE, 'test') + .flatMap((sentence) => collectSentenceTokens(sentence, lines)) + .filter( + (token) => + token.line === line && (!tokenType || token.tokenType === tokenType), + ) + .map((token) => + lines[token.line].substr(token.startCharacter, token.length), + ); + +describe('多行语句的语义高亮', () => { + it('续行占位不会成为独立语句', () => { + const sentences = parseSentences(MULTILINE_SCENE, 'test'); + + expect(sentences).toHaveLength(2); + expect(sentences[0].command).toBe(commandType.changeFigure); + expect(sentences[0].startLine).toBe(0); + expect(sentences[0].endLine).toBe(2); + expect(sentences[1].startLine).toBe(3); + expect(sentences[1].endLine).toBe(4); + }); + + it('续行上的参数同样能被着色', () => { + expect(tokenTexts(1)).toEqual(['left']); + expect(tokenTexts(2)).toEqual(['id', 'fig1', 'next']); + }); + + it('intro 续行上的文字按字符串着色', () => { + expect(tokenTexts(3, 'string')).toEqual(['第一段']); + expect(tokenTexts(4, 'string')).toEqual(['第二段']); + }); +}); diff --git a/packages/terre2/src/Modules/lsp/sentenceTokens.ts b/packages/terre2/src/Modules/lsp/sentenceTokens.ts new file mode 100644 index 00000000..0cdf382a --- /dev/null +++ b/packages/terre2/src/Modules/lsp/sentenceTokens.ts @@ -0,0 +1,154 @@ +import { ISentence } from 'webgal-parser/build/types/interface/sceneInterface'; +import { commandType } from './completion/commandArgs'; +import { lastVariables } from './lastVariables'; +import { + flatIndexInSentence, + SentenceCursor, + TextPosition, +} from './sentenceLocator'; + +export interface IParsedToken { + line: number; + startCharacter: number; + length: number; + tokenType: string; + tokenModifiers: string[]; +} + +const token = ( + at: TextPosition, + length: number, + tokenType: string, +): IParsedToken => ({ ...at, length, tokenType, tokenModifiers: [] }); + +/** + * 取出一条语句的全部语义 token。 + * + * 语句可能跨多行,所有定位都通过游标在它的行范围内向后推进完成, + * 因此续行里的参数同样能被正确着色。 + */ +export function collectSentenceTokens( + sentence: ISentence, + lines: string[], +): IParsedToken[] { + const tokens: IParsedToken[] = []; + const cursor = new SentenceCursor(lines, sentence); + + if (sentence.command === commandType.intro) { + tokens.push(...collectIntroTokens(sentence, cursor)); + } + + if (sentence.command === commandType.setVar) { + tokens.push(...collectSetVarTokens(sentence, cursor)); + } + + if (sentence.command === commandType.say) { + tokens.push(...collectInterpolationTokens(sentence, cursor)); + } + + cursor.rewind(); + tokens.push(...collectArgTokens(sentence, lines, cursor)); + + return tokens; +} + +/** intro 的内容由 `|` 分段,每段单独作为字符串着色 */ +function collectIntroTokens( + sentence: ISentence, + cursor: SentenceCursor, +): IParsedToken[] { + const tokens: IParsedToken[] = []; + + for (const segment of sentence.content.split(/(? + flatIndexInSentence(lines, sentence, a.key) - + flatIndexInSentence(lines, sentence, b.key), + ); + + return argsInSourceOrder + .filter((arg) => arg.key !== 'speaker') + .flatMap((arg) => + collectKeyValueTokens(arg.key, arg.value.toString(), cursor), + ); +} + +function collectKeyValueTokens( + key: string, + value: string, + cursor: SentenceCursor, +): IParsedToken[] { + const tokens: IParsedToken[] = []; + + const keyAt = cursor.find(key); + if (keyAt) { + tokens.push(token(keyAt, key.length, 'parameter')); + } + + const valueAt = cursor.find(value); + if (valueAt) { + tokens.push(token(valueAt, value.length, 'value')); + } + + return tokens; +} diff --git a/packages/terre2/src/Modules/lsp/webgalLsp.ts b/packages/terre2/src/Modules/lsp/webgalLsp.ts index 52faba8a..df6c9040 100644 --- a/packages/terre2/src/Modules/lsp/webgalLsp.ts +++ b/packages/terre2/src/Modules/lsp/webgalLsp.ts @@ -20,8 +20,6 @@ import { } from './semanticToken'; import { complete, checkTriggerCompletion } from './completion'; -export const lastVariables = new Map(); - export function createWsConnection( reader: MessageReader, writer: MessageWriter, diff --git a/yarn.lock b/yarn.lock index c308eabe..eb3d7958 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13437,10 +13437,10 @@ webgal-engine@4.6.2: uuid "^9.0.0" vite-plugin-package-version "^1.0.2" -webgal-parser@4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/webgal-parser/-/webgal-parser-4.6.2.tgz#6bdae1423e16223f3ca7e9cda36e8e38d1b5b391" - integrity sha512-GGTrama9fUUrXmgpvxwMzk9m7CQZtm38ueZQdXzR8cJaGge0bjmMQZQBmy2Cu6/Mz7wdwg8QPyZ4KOlE1s8+WA== +webgal-parser@4.6.3-beta.2: + version "4.6.3-beta.2" + resolved "https://registry.yarnpkg.com/webgal-parser/-/webgal-parser-4.6.3-beta.2.tgz#306e02fad9e48061292fa60d0140b18b945993c5" + integrity sha512-44TXG7HqhUjIwdfF5HCYUFGBCNb2XSzQl5wU1NxgbXiG2HgPXGJ4NrWER+ncWrfC9iXxybZCf4RvJARytxaA4g== webidl-conversions@^3.0.0: version "3.0.1" From ee9ab7461ebdf5eb0dca86c9473c110b414d6cf1 Mon Sep 17 00:00:00 2001 From: Mahiru Date: Sat, 1 Aug 2026 21:20:38 +0800 Subject: [PATCH 08/13] feat: setVar supports local variable --- packages/origine2/src/locales/en.po | 58 ++++++++- packages/origine2/src/locales/ja.po | 58 ++++++++- packages/origine2/src/locales/ko.po | 58 ++++++++- packages/origine2/src/locales/zhCn.po | 58 ++++++++- .../SentenceEditor/BasicCommands.tsx | 66 +++++++++-- .../SentenceEditor/ChangeCallScene.tsx | 112 +++++++++++++++++- .../GraphicalEditor/SentenceEditor/index.tsx | 8 ++ .../SentenceEditor/sentenceEditor.module.scss | 36 ++++++ .../tabs/AddSentence/AddSentenceTab.tsx | 2 +- .../terre2/src/Modules/lsp/diagnostics.ts | 68 +++++++++++ .../Modules/lsp/suggestionRules/getArgsKey.ts | 42 ++++++- .../lsp/suggestionRules/getCommands.ts | 21 +++- packages/terre2/src/Modules/lsp/webgalLsp.ts | 8 +- 13 files changed, 556 insertions(+), 39 deletions(-) create mode 100644 packages/terre2/src/Modules/lsp/diagnostics.ts diff --git a/packages/origine2/src/locales/en.po b/packages/origine2/src/locales/en.po index 6fa3daee..2e99fb38 100644 --- a/packages/origine2/src/locales/en.po +++ b/packages/origine2/src/locales/en.po @@ -128,6 +128,9 @@ msgstr "playVideo: Choose a video file;" msgid "Portable 模式" msgstr "Portable mode" +msgid "return:;" +msgstr "return:;" + msgid "RGB电影滤镜" msgstr "RGB film" @@ -296,6 +299,9 @@ msgstr "Code Editor" msgid "以下目标路径已存在,未覆盖:" msgstr "The following target paths already exist and were not overwritten:" +msgid "传入的参数会成为被调用场景的局部变量,在那边用花括号插值取用,场景结束后即消失。" +msgstr "Arguments passed here become local variables of the called scene. Read them there with curly-brace interpolation; they disappear when the scene ends." + msgid "伽马值" msgstr "Gamma" @@ -482,14 +488,11 @@ msgstr "Min Height" msgid "最新版本" msgstr "Latest Version" -msgid "写入全局变量" -msgstr "Write to global variables" - msgid "写入变量" msgstr "Write Variable" -msgid "写入当前存档变量" -msgstr "Write to current save variables" +msgid "写入哪里" +msgstr "Write to" msgid "分支选择" msgstr "Choose branch" @@ -533,6 +536,9 @@ msgstr "Create new game" msgid "删除" msgstr "Delete" +msgid "删除参数" +msgstr "Delete argument" + msgid "删除属性" msgstr "Delete Property" @@ -596,6 +602,12 @@ msgstr "Original style->new style, separate multiple entries with commas" msgid "参数值" msgstr "Parameter Value" +msgid "参数值,用花括号插值可取调用方的变量" +msgstr "Argument value. Use curly-brace interpolation to read a variable of the caller" + +msgid "参数名" +msgstr "Argument name" + msgid "双击节点名称可编辑" msgstr "Double-click a node name to edit it" @@ -743,6 +755,9 @@ msgstr "Scenes and branches" msgid "场景文件" msgstr "Scene file" +msgid "场景返回" +msgstr "Return from scene" + msgid "填写参数值" msgstr "Fill in parameter value" @@ -800,6 +815,9 @@ msgstr "Letter spacing" msgid "字重" msgstr "Font Weight" +msgid "存档变量" +msgstr "Save-data variable" + msgid "定位方式" msgstr "Positioning" @@ -896,6 +914,12 @@ msgstr "No fonts added yet" msgid "尚未配置任何字体" msgstr "No fonts configured yet" +msgid "局部变量" +msgstr "Local variable" + +msgid "局部变量即 callScene 传入的参数,随场景调用结束而消失。要改传进来的参数,必须选择局部变量。" +msgstr "Local variables are the arguments passed in by callScene, and they vanish when the scene call ends. To modify an argument that was passed in, you must choose \"Local variable\"." + msgid "层级顺序" msgstr "Z Index" @@ -1127,6 +1151,12 @@ msgstr "Submit" msgid "提交修改" msgstr "Submit changes" +msgid "提前结束当前被调用的场景并回到调用处。返回值会写入调用方 callScene 指定的变量。" +msgstr "Ends the current called scene early and goes back to the call site. The return value is written into the variable specified by the caller's callScene." + +msgid "提前结束被调用的场景并回到调用处,可带回一个返回值" +msgstr "End the called scene early and go back to the call site, optionally carrying a return value" + msgid "提示" msgstr "Tip" @@ -1433,6 +1463,9 @@ msgstr "Label" msgid "标签名" msgstr "Label name" +msgid "标红的参数名是引擎保留的通用参数名,不会作为局部变量传入被调用的场景,请换一个名字。" +msgstr "The argument names in red are generic argument names reserved by the engine. They are not passed into the called scene as local variables, so please rename them." + msgid "标题" msgstr "Title" @@ -1601,6 +1634,9 @@ msgstr "Add a music to music appreciation gallery" msgid "添加动画帧" msgstr "Add animation frame" +msgid "添加参数" +msgstr "Add argument" + msgid "添加属性" msgstr "Add Property" @@ -1718,6 +1754,9 @@ msgstr "Film mode value" msgid "留空使用默认值" msgstr "Leave empty to use default" +msgid "留空表示不返回值。可填数字、true/false、字符串或表达式" +msgstr "Leave empty to return nothing. Accepts a number, true/false, a string or an expression" + msgid "百分比。 0-100 有效" msgstr "Percentage. 0-100 valid" @@ -2015,6 +2054,9 @@ msgstr "Write default" msgid "表达式" msgstr "Expression" +msgid "被调用场景 return 的值写入哪个变量,留空则丢弃" +msgstr "Which variable receives the value returned by the called scene. Leave empty to discard it" + msgid "裁剪形状" msgstr "Clip Shape" @@ -2216,6 +2258,12 @@ msgstr "Platform" msgid "返回" msgstr "Return" +msgid "返回值" +msgstr "Return value" + +msgid "返回值写入" +msgstr "Return value goes to" + msgid "进出场动画" msgstr "Entry and exit animation" diff --git a/packages/origine2/src/locales/ja.po b/packages/origine2/src/locales/ja.po index 03cf04bc..f51c410a 100644 --- a/packages/origine2/src/locales/ja.po +++ b/packages/origine2/src/locales/ja.po @@ -128,6 +128,9 @@ msgstr "playVideo: 動画を選択;" msgid "Portable 模式" msgstr "Portable モード" +msgid "return:;" +msgstr "return:;" + msgid "RGB电影滤镜" msgstr "RGB映画" @@ -296,6 +299,9 @@ msgstr "コードエディタ" msgid "以下目标路径已存在,未覆盖:" msgstr "次の移行先パスは既に存在するため、上書きされませんでした:" +msgid "传入的参数会成为被调用场景的局部变量,在那边用花括号插值取用,场景结束后即消失。" +msgstr "渡した引数は呼び出されたシーンのローカル変数になります。向こうでは波かっこ補間で参照でき、シーンが終わると消滅します。" + msgid "伽马值" msgstr "ガンマ" @@ -482,14 +488,11 @@ msgstr "最小高さ" msgid "最新版本" msgstr "最新バージョン" -msgid "写入全局变量" -msgstr "グローバル変数に書き込む" - msgid "写入变量" msgstr "変数を書く" -msgid "写入当前存档变量" -msgstr "現在のセーブ変数に書き込む" +msgid "写入哪里" +msgstr "書き込み先" msgid "分支选择" msgstr "選択肢" @@ -533,6 +536,9 @@ msgstr "新しいゲームを作成" msgid "删除" msgstr "削除" +msgid "删除参数" +msgstr "引数を削除" + msgid "删除属性" msgstr "属性を削除" @@ -596,6 +602,12 @@ msgstr "元のスタイル->新しいスタイル、複数はカンマで区切 msgid "参数值" msgstr "パラメータ値" +msgid "参数值,用花括号插值可取调用方的变量" +msgstr "引数の値。波かっこ補間で呼び出し元の変数を参照できます" + +msgid "参数名" +msgstr "引数名" + msgid "双击节点名称可编辑" msgstr "ノード名をダブルクリックすると編集できます" @@ -743,6 +755,9 @@ msgstr "シーンとブランチ" msgid "场景文件" msgstr "シーンファイル" +msgid "场景返回" +msgstr "シーンから戻る" + msgid "填写参数值" msgstr "パラメータ値を入力" @@ -800,6 +815,9 @@ msgstr "文字間隔" msgid "字重" msgstr "フォントの太さ" +msgid "存档变量" +msgstr "セーブデータ変数" + msgid "定位方式" msgstr "位置決め方法" @@ -896,6 +914,12 @@ msgstr "まだフォントが追加されていません" msgid "尚未配置任何字体" msgstr "まだフォントが設定されていません" +msgid "局部变量" +msgstr "ローカル変数" + +msgid "局部变量即 callScene 传入的参数,随场景调用结束而消失。要改传进来的参数,必须选择局部变量。" +msgstr "ローカル変数は callScene で渡された引数で、シーン呼び出しの終了とともに消滅します。渡された引数を書き換えるには「ローカル変数」を選んでください。" + msgid "层级顺序" msgstr "階層順序" @@ -1127,6 +1151,12 @@ msgstr "提出" msgid "提交修改" msgstr "変更を送信" +msgid "提前结束当前被调用的场景并回到调用处。返回值会写入调用方 callScene 指定的变量。" +msgstr "呼び出されたシーンを途中で終了し、呼び出し元へ戻ります。戻り値は呼び出し元の callScene で指定した変数に書き込まれます。" + +msgid "提前结束被调用的场景并回到调用处,可带回一个返回值" +msgstr "呼び出されたシーンを途中で終了して呼び出し元へ戻る。戻り値を持ち帰ることもできる" + msgid "提示" msgstr "ヒント" @@ -1433,6 +1463,9 @@ msgstr "ラベル" msgid "标签名" msgstr "ラベル名" +msgid "标红的参数名是引擎保留的通用参数名,不会作为局部变量传入被调用的场景,请换一个名字。" +msgstr "赤い引数名はエンジンが予約している共通パラメータ名です。ローカル変数として呼び出されたシーンに渡されないため、別の名前に変更してください。" + msgid "标题" msgstr "タイトル" @@ -1601,6 +1634,9 @@ msgstr "BGM鑑賞に任意のBGMを追加する" msgid "添加动画帧" msgstr "アニメーションフレームを追加" +msgid "添加参数" +msgstr "引数を追加" + msgid "添加属性" msgstr "属性を追加" @@ -1718,6 +1754,9 @@ msgstr "映画モードの値" msgid "留空使用默认值" msgstr "空欄で既定値を使用" +msgid "留空表示不返回值。可填数字、true/false、字符串或表达式" +msgstr "空欄なら戻り値なし。数値、true/false、文字列、式が使えます" + msgid "百分比。 0-100 有效" msgstr "百分率。 0-100 有効" @@ -2015,6 +2054,9 @@ msgstr "デフォルト値を補う" msgid "表达式" msgstr "式" +msgid "被调用场景 return 的值写入哪个变量,留空则丢弃" +msgstr "呼び出されたシーンの戻り値をどの変数に書き込むか。空欄なら破棄されます" + msgid "裁剪形状" msgstr "クリップ形状" @@ -2216,6 +2258,12 @@ msgstr "プラットフォーム" msgid "返回" msgstr "戻る" +msgid "返回值" +msgstr "戻り値" + +msgid "返回值写入" +msgstr "戻り値の書き込み先" + msgid "进出场动画" msgstr "入退場アニメーション" diff --git a/packages/origine2/src/locales/ko.po b/packages/origine2/src/locales/ko.po index cd627505..920ffdba 100644 --- a/packages/origine2/src/locales/ko.po +++ b/packages/origine2/src/locales/ko.po @@ -128,6 +128,9 @@ msgstr "playVideo:영상 파일 선택;" msgid "Portable 模式" msgstr "Portable 모드" +msgid "return:;" +msgstr "return:;" + msgid "RGB电影滤镜" msgstr "RGB 영화 필터" @@ -296,6 +299,9 @@ msgstr "코드 에디터" msgid "以下目标路径已存在,未覆盖:" msgstr "다음 대상의 경로가 이미 존재하므로 덮어쓰지 않았습니다:" +msgid "传入的参数会成为被调用场景的局部变量,在那边用花括号插值取用,场景结束后即消失。" +msgstr "전달한 인자는 호출된 씬의 지역 변수가 됩니다. 그쪽에서는 중괄호 보간으로 사용하며, 씬이 끝나면 사라집니다." + msgid "伽马值" msgstr "감마값" @@ -482,14 +488,11 @@ msgstr "최소 높이" msgid "最新版本" msgstr "최신 버전" -msgid "写入全局变量" -msgstr "전역 변수 저장하기" - msgid "写入变量" msgstr "변수에 저장하기" -msgid "写入当前存档变量" -msgstr "현재 세이브 변수에 저장하기" +msgid "写入哪里" +msgstr "기록 위치" msgid "分支选择" msgstr "분기 선택" @@ -533,6 +536,9 @@ msgstr "새 게임 생성" msgid "删除" msgstr "삭제" +msgid "删除参数" +msgstr "인자 삭제" + msgid "删除属性" msgstr "속성 삭제" @@ -596,6 +602,12 @@ msgstr "기존 스타일->새 스타일, 여러 개는 쉼표로 구분" msgid "参数值" msgstr "파라미터 값" +msgid "参数值,用花括号插值可取调用方的变量" +msgstr "인자 값. 중괄호 보간으로 호출한 쪽의 변수를 사용할 수 있습니다" + +msgid "参数名" +msgstr "인자 이름" + msgid "双击节点名称可编辑" msgstr "노드 이름을 두 번 클릭하면 편집할 수 있습니다" @@ -743,6 +755,9 @@ msgstr "장면 및 분기" msgid "场景文件" msgstr "장면 파일" +msgid "场景返回" +msgstr "씬 반환" + msgid "填写参数值" msgstr "파라미터 값 작성" @@ -800,6 +815,9 @@ msgstr "글자 간격" msgid "字重" msgstr "글꼴 두께" +msgid "存档变量" +msgstr "세이브 변수" + msgid "定位方式" msgstr "위치 지정 방식" @@ -896,6 +914,12 @@ msgstr "아직 폰트가 추가되지 않았습니다" msgid "尚未配置任何字体" msgstr "아직 구성된 폰트가 없습니다" +msgid "局部变量" +msgstr "지역 변수" + +msgid "局部变量即 callScene 传入的参数,随场景调用结束而消失。要改传进来的参数,必须选择局部变量。" +msgstr "지역 변수는 callScene으로 전달된 인자이며, 씬 호출이 끝나면 사라집니다. 전달된 인자를 수정하려면 \"지역 변수\"를 선택해야 합니다." + msgid "层级顺序" msgstr "계층 순서" @@ -1127,6 +1151,12 @@ msgstr "제출" msgid "提交修改" msgstr "수정 제출" +msgid "提前结束当前被调用的场景并回到调用处。返回值会写入调用方 callScene 指定的变量。" +msgstr "호출된 씬을 미리 끝내고 호출 지점으로 돌아갑니다. 반환값은 호출한 쪽의 callScene이 지정한 변수에 기록됩니다." + +msgid "提前结束被调用的场景并回到调用处,可带回一个返回值" +msgstr "호출된 씬을 미리 끝내고 호출 지점으로 돌아가며, 반환값을 가져올 수 있습니다" + msgid "提示" msgstr "힌트" @@ -1433,6 +1463,9 @@ msgstr "태그" msgid "标签名" msgstr "태그명" +msgid "标红的参数名是引擎保留的通用参数名,不会作为局部变量传入被调用的场景,请换一个名字。" +msgstr "빨간색 인자 이름은 엔진이 예약한 공통 매개변수 이름입니다. 지역 변수로 호출된 씬에 전달되지 않으니 다른 이름으로 바꿔 주세요." + msgid "标题" msgstr "타이틀" @@ -1601,6 +1634,9 @@ msgstr "음악 감상 인터페이스에 음악 한 곡 추가" msgid "添加动画帧" msgstr "애니메이션 프레임 추가" +msgid "添加参数" +msgstr "인자 추가" + msgid "添加属性" msgstr "속성 추가" @@ -1718,6 +1754,9 @@ msgstr "시네마틱 모드" msgid "留空使用默认值" msgstr "비워두면 기본값 사용" +msgid "留空表示不返回值。可填数字、true/false、字符串或表达式" +msgstr "비워 두면 반환값이 없습니다. 숫자, true/false, 문자열, 식을 사용할 수 있습니다" + msgid "百分比。 0-100 有效" msgstr "백분율. 0-100 유효" @@ -2015,6 +2054,9 @@ msgstr "기본값 보충" msgid "表达式" msgstr "표현식" +msgid "被调用场景 return 的值写入哪个变量,留空则丢弃" +msgstr "호출된 씬의 반환값을 기록할 변수. 비워 두면 버립니다" + msgid "裁剪形状" msgstr "자르기 모양" @@ -2216,6 +2258,12 @@ msgstr "실행 플랫폼" msgid "返回" msgstr "뒤로" +msgid "返回值" +msgstr "반환값" + +msgid "返回值写入" +msgstr "반환값 기록 대상" + msgid "进出场动画" msgstr "입/퇴장 애니메이션" diff --git a/packages/origine2/src/locales/zhCn.po b/packages/origine2/src/locales/zhCn.po index e30a928b..bc77e1bf 100644 --- a/packages/origine2/src/locales/zhCn.po +++ b/packages/origine2/src/locales/zhCn.po @@ -128,6 +128,9 @@ msgstr "playVideo:选择视频文件;" msgid "Portable 模式" msgstr "Portable 模式" +msgid "return:;" +msgstr "return:;" + msgid "RGB电影滤镜" msgstr "RGB电影滤镜" @@ -296,6 +299,9 @@ msgstr "代码编辑器" msgid "以下目标路径已存在,未覆盖:" msgstr "以下目标路径已存在,未覆盖:" +msgid "传入的参数会成为被调用场景的局部变量,在那边用花括号插值取用,场景结束后即消失。" +msgstr "传入的参数会成为被调用场景的局部变量,在那边用花括号插值取用,场景结束后即消失。" + msgid "伽马值" msgstr "伽马值" @@ -482,14 +488,11 @@ msgstr "最小高度" msgid "最新版本" msgstr "最新版本" -msgid "写入全局变量" -msgstr "写入全局变量" - msgid "写入变量" msgstr "写入变量" -msgid "写入当前存档变量" -msgstr "写入当前存档变量" +msgid "写入哪里" +msgstr "写入哪里" msgid "分支选择" msgstr "分支选择" @@ -533,6 +536,9 @@ msgstr "创建新游戏" msgid "删除" msgstr "删除" +msgid "删除参数" +msgstr "删除参数" + msgid "删除属性" msgstr "删除属性" @@ -596,6 +602,12 @@ msgstr "原样式->新样式,多个用逗号分隔" msgid "参数值" msgstr "参数值" +msgid "参数值,用花括号插值可取调用方的变量" +msgstr "参数值,用花括号插值可取调用方的变量" + +msgid "参数名" +msgstr "参数名" + msgid "双击节点名称可编辑" msgstr "双击节点名称可编辑" @@ -743,6 +755,9 @@ msgstr "场景与分支" msgid "场景文件" msgstr "场景文件" +msgid "场景返回" +msgstr "场景返回" + msgid "填写参数值" msgstr "填写参数值" @@ -800,6 +815,9 @@ msgstr "字母间距" msgid "字重" msgstr "字重" +msgid "存档变量" +msgstr "存档变量" + msgid "定位方式" msgstr "定位方式" @@ -896,6 +914,12 @@ msgstr "尚未添加任何字体" msgid "尚未配置任何字体" msgstr "尚未配置任何字体" +msgid "局部变量" +msgstr "局部变量" + +msgid "局部变量即 callScene 传入的参数,随场景调用结束而消失。要改传进来的参数,必须选择局部变量。" +msgstr "局部变量即 callScene 传入的参数,随场景调用结束而消失。要改传进来的参数,必须选择局部变量。" + msgid "层级顺序" msgstr "层级顺序" @@ -1127,6 +1151,12 @@ msgstr "提交" msgid "提交修改" msgstr "提交修改" +msgid "提前结束当前被调用的场景并回到调用处。返回值会写入调用方 callScene 指定的变量。" +msgstr "提前结束当前被调用的场景并回到调用处。返回值会写入调用方 callScene 指定的变量。" + +msgid "提前结束被调用的场景并回到调用处,可带回一个返回值" +msgstr "提前结束被调用的场景并回到调用处,可带回一个返回值" + msgid "提示" msgstr "提示" @@ -1433,6 +1463,9 @@ msgstr "标签" msgid "标签名" msgstr "标签名" +msgid "标红的参数名是引擎保留的通用参数名,不会作为局部变量传入被调用的场景,请换一个名字。" +msgstr "标红的参数名是引擎保留的通用参数名,不会作为局部变量传入被调用的场景,请换一个名字。" + msgid "标题" msgstr "标题" @@ -1601,6 +1634,9 @@ msgstr "添加一首音乐到音乐鉴赏界面" msgid "添加动画帧" msgstr "添加动画帧" +msgid "添加参数" +msgstr "添加参数" + msgid "添加属性" msgstr "添加属性" @@ -1718,6 +1754,9 @@ msgstr "电影模式值" msgid "留空使用默认值" msgstr "留空使用默认值" +msgid "留空表示不返回值。可填数字、true/false、字符串或表达式" +msgstr "留空表示不返回值。可填数字、true/false、字符串或表达式" + msgid "百分比。 0-100 有效" msgstr "百分比。 0-100 有效" @@ -2015,6 +2054,9 @@ msgstr "补充默认值" msgid "表达式" msgstr "表达式" +msgid "被调用场景 return 的值写入哪个变量,留空则丢弃" +msgstr "被调用场景 return 的值写入哪个变量,留空则丢弃" + msgid "裁剪形状" msgstr "裁剪形状" @@ -2216,6 +2258,12 @@ msgstr "运行平台" msgid "返回" msgstr "返回" +msgid "返回值" +msgstr "返回值" + +msgid "返回值写入" +msgstr "返回值写入" + msgid "进出场动画" msgstr "进出场动画" diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/BasicCommands.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/BasicCommands.tsx index a42fc072..91bcc4da 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/BasicCommands.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/BasicCommands.tsx @@ -99,20 +99,38 @@ function JumpLabelCommand(props: ISentenceEditorProps) {
; } +/** 三种作用域互斥,用一个下拉框而不是两个开关,避免出现同时勾选的无效状态 */ +type VarScope = "stage" | "global" | "local"; + +const getVarScope = (props: ISentenceEditorProps): VarScope => { + if (getArgByKey(props.sentence, "global") === true) return "global"; + return getArgByKey(props.sentence, "local") === true ? "local" : "stage"; +}; + function SetVarCommand(props: ISentenceEditorProps) { const expression = useValue(props.sentence.content); - const isGlobal = useValue(getArgByKey(props.sentence, "global") === true); + const scope = useValue(getVarScope(props)); + const scopeOptions = new Map([ + ["stage", t`存档变量`], + ["global", t`全局变量`], + ["local", t`局部变量`], + ]); const submit = () => { props.onSubmit(combineSubmitString( props.sentence.commandRaw, expression.value, props.sentence.args, - [{ key: "global", value: isGlobal.value }], + [ + { key: "global", value: scope.value === "global" }, + { key: "local", value: scope.value === "local" }, + ], props.sentence.inlineComment, )); }; return
+ {scope.value === "local" + && }
- - { - isGlobal.set(newValue); + + { + scope.set((newValue?.toString() ?? "stage") as VarScope); submit(); }} - onText={t`写入全局变量`} - offText={t`写入当前存档变量`} - isChecked={isGlobal.value} />
; } +function ReturnCommand(props: ISentenceEditorProps) { + // 不写冒号时(`return;`)解析器会把整条命令留在 content 里,此时视为没有返回值 + const isEmptyReturn = props.sentence.content === props.sentence.commandRaw; + const returnValue = useValue(isEmptyReturn ? "" : props.sentence.content); + const submit = () => { + props.onSubmit(combineSubmitString( + props.sentence.commandRaw, + returnValue.value, + props.sentence.args, + [], + props.sentence.inlineComment, + )); + }; + + return
+ +
+ returnValue.set(value)} + onBlur={submit} + placeholder={t`留空表示不返回值。可填数字、true/false、字符串或表达式`} + /> +
+
; +} + function SetComplexAnimationCommand(props: ISentenceEditorProps) { const animationName = useValue(props.sentence.content); const target = useValue(getArgByKey(props.sentence, "target")?.toString() ?? ""); @@ -268,6 +312,8 @@ export default function BasicCommands(props: ISentenceEditorProps) { return ; case commandType.setVar: return ; + case commandType.return: + return ; case commandType.setComplexAnimation: return ; default: diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeCallScene.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeCallScene.tsx index 90b981d1..3ec1fbc7 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeCallScene.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeCallScene.tsx @@ -1,33 +1,110 @@ import CommonOptions from "../components/CommonOption"; +import CommonTips from "../components/CommonTips"; import { ISentenceEditorProps } from "./index"; import styles from "./sentenceEditor.module.scss"; -import { commandType } from "webgal-parser/src/interface/sceneInterface"; +import { arg, commandType } from "webgal-parser/src/interface/sceneInterface"; import { useValue } from "../../../../hooks/useValue"; import ChooseFile from "../../ChooseFile/ChooseFile"; import TerreToggle from "../../../../components/terreToggle/TerreToggle"; import { t } from "@lingui/macro"; import { combineSubmitString } from "@/utils/combineSubmitString"; import { extNameMap } from "../../ChooseFile/chooseFileConfig"; +import { getArgByKey } from "../utils/getArgByKey"; +import { Button } from "@fluentui/react-components"; +import { Add20Filled, Add20Regular, bundleIcon, Delete20Filled, Delete20Regular } from "@fluentui/react-icons"; +import { cloneDeep } from "lodash"; + +const AddIcon = bundleIcon(Add20Filled, Add20Regular); +const DeleteIcon = bundleIcon(Delete20Filled, Delete20Regular); + +/** 所有命令通用的参数,由引擎自己消费,不会作为局部变量传给被调用的场景 */ +const GENERIC_ARG_KEYS = ["when", "next", "continue"]; +/** callScene 自己的参数,下面有专门的输入框,不算自定义传参 */ +const CALL_SCENE_ARG_KEYS = ["writeReturnTo"]; + +const isReservedArgKey = (key: string) => + GENERIC_ARG_KEYS.includes(key.trim()) || CALL_SCENE_ARG_KEYS.includes(key.trim()); + +interface ISceneArg { + key: string; + value: string; +} + +/** 除通用参数和 callScene 自己的参数外,其余都是传给被调用场景的自定义参数 */ +const pickCustomArgs = (args: arg[]): ISceneArg[] => + args + .filter(item => !isReservedArgKey(item.key)) + .map(item => ({ key: item.key, value: item.value.toString() })); export default function ChangeCallScene(props: ISentenceEditorProps) { const isCallScene = useValue(props.sentence.command === commandType.callScene); const fileName = useValue(props.sentence.content); + const writeReturnTo = useValue(getArgByKey(props.sentence, "writeReturnTo").toString()); + const sceneArgs = useValue(pickCustomArgs(props.sentence.args)); + const submit = () => { const submitString = combineSubmitString( isCallScene.value ? "callScene" : "changeScene", fileName.value, - props.sentence.args, - [], + // 自定义参数和 writeReturnTo 完全由本组件掌控,只把通用参数原样带回去。 + // 否则被删掉的参数会被当作「未识别参数」重新追加,删不掉。 + props.sentence.args.filter(item => GENERIC_ARG_KEYS.includes(item.key)), + isCallScene.value + ? [ + ...sceneArgs.value + .filter(item => item.key.trim() !== "") + .map(item => ({ key: item.key.trim(), value: item.value })), + { key: "writeReturnTo", value: writeReturnTo.value.trim() }, + ] + : [], // changeScene 不支持传参,切过去时一并丢掉 props.sentence.inlineComment, ); props.onSubmit(submitString); }; + const updateArg = (index: number, patch: Partial) => { + const newArgs = cloneDeep(sceneArgs.value); + newArgs[index] = { ...newArgs[index], ...patch }; + sceneArgs.set(newArgs); + }; + + const hasReservedArgKey = sceneArgs.value.some(item => isReservedArgKey(item.key)); + + const sceneArgList = sceneArgs.value.map((item, index) =>
+
); + return
<> - {fileName.value}{'\u00a0'} + {fileName.value}{' '} { fileName.set(file?.name ?? ""); submit(); @@ -41,6 +118,33 @@ export default function ChangeCallScene(props: ISentenceEditorProps) { }} onText={t`调用场景,新场景结束后返回父场景`} offText={t`切换场景,新场景直接替换父场景`} isChecked={isCallScene.value} /> + {isCallScene.value && + writeReturnTo.set(ev.target.value)} + onBlur={submit} + className={styles.sayInput} + placeholder={t`被调用场景 return 的值写入哪个变量,留空则丢弃`} + style={{ width: "100%" }} + /> + }
+ {isCallScene.value && <> + + {hasReservedArgKey && } + {sceneArgList} +
+
+ }
; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/index.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/index.tsx index fd01adea..2a8ee2ca 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/index.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/index.tsx @@ -170,6 +170,14 @@ export const sentenceEditorConfig: ISentenceEditorConfig[] = [ icon: , descText: () => t`调用一段场景文件,在结束后返回父场景` }, + { + type: commandType.return, + title: () => t`场景返回`, + initialText: () => t`return:;`, + component: BasicCommands, + icon: , + descText: () => t`提前结束被调用的场景并回到调用处,可带回一个返回值` + }, { type: commandType.changeScene, title: () => t`切换场景`, diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/sentenceEditor.module.scss b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/sentenceEditor.module.scss index 8db49c04..f46f8719 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/sentenceEditor.module.scss +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/sentenceEditor.module.scss @@ -106,6 +106,42 @@ max-width: 440px; } +.sceneArgItem { + display: flex; + width: 100%; + max-width: 100%; + box-sizing: border-box; + align-self: stretch; + align-items: center; + flex-wrap: wrap; + gap: 8px; + padding: 4px 0; +} + +.sceneArgAddRow { + display: flex; + width: 100%; + max-width: 100%; + box-sizing: border-box; + align-self: stretch; + padding: 4px 0; +} + +.sceneArgInput { + width: 30%; + min-width: 160px; + max-width: 320px; +} + +.sceneArgInputInvalid { + border: var(--border-danger-md); +} + +.sceneArgEqual { + color: var(--text-sub); + flex: 0 0 auto; +} + .chooseTargetArrow { color: var(--text-sub); display: inline-flex; diff --git a/packages/origine2/src/pages/editor/Topbar/tabs/AddSentence/AddSentenceTab.tsx b/packages/origine2/src/pages/editor/Topbar/tabs/AddSentence/AddSentenceTab.tsx index 02f7bf58..094df7f4 100644 --- a/packages/origine2/src/pages/editor/Topbar/tabs/AddSentence/AddSentenceTab.tsx +++ b/packages/origine2/src/pages/editor/Topbar/tabs/AddSentence/AddSentenceTab.tsx @@ -34,7 +34,7 @@ export function AddSentenceTab() { const btsSpecial = pickSentenceType([commandType.pixi, commandType.pixiInit, commandType.intro]); const btsSpecial2 = pickSentenceType([commandType.miniAvatar, commandType.setTextbox, commandType.filmMode]); const btsBranch = pickSentenceType([commandType.callScene, commandType.changeScene, commandType.choose]); - const btsBranch2 = pickSentenceType([commandType.label, commandType.jumpLabel]); + const btsBranch2 = pickSentenceType([commandType.label, commandType.jumpLabel, commandType.return]); const btsExtra = pickSentenceType([commandType.unlockCg, commandType.unlockBgm]); const btsSystem = pickSentenceType([commandType.getUserInput, commandType.setVar, commandType.showVars]); const btsSystem2 = pickSentenceType([commandType.wait, commandType.applyStyle, commandType.callSteam]); diff --git a/packages/terre2/src/Modules/lsp/diagnostics.ts b/packages/terre2/src/Modules/lsp/diagnostics.ts new file mode 100644 index 00000000..f4793932 --- /dev/null +++ b/packages/terre2/src/Modules/lsp/diagnostics.ts @@ -0,0 +1,68 @@ +import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver'; +import { ISentence } from 'webgal-parser/build/types/interface/sceneInterface'; +import { commandType } from './completion/commandArgs'; +import { parseSentences, SentenceCursor } from './sentenceLocator'; + +/** + * callScene 的参数会原样成为被调用场景的局部变量,唯独通用参数的名字会先被引擎自己消费掉。 + * + * 这里只挑布尔型的通用参数:它们裸写(`-next`)才是正常用法,一旦写成 `-next=5`, + * 作者要的显然是传一个叫 next 的参数,而引擎只会把它当成通用参数,值悄悄丢掉。 + * `-when` 不在其列,因为 `callScene:b.txt -when=a>0;` 本来就是合法的条件调用。 + */ +const RESERVED_BOOLEAN_ARG_KEYS = new Set(['next', 'continue']); + +/** + * 整篇文档的诊断。规则按语句维度组织,后续规则同样追加到 collectSentenceDiagnostics。 + */ +export function collectDiagnostics(text: string, uri: string): Diagnostic[] { + const lines = text.split(/\r\n|\r|\n/); + return parseSentences(text, uri).flatMap((sentence) => + collectSentenceDiagnostics(sentence, lines), + ); +} + +function collectSentenceDiagnostics( + sentence: ISentence, + lines: string[], +): Diagnostic[] { + if (sentence.command !== commandType.callScene) { + return []; + } + return sentence.args + .filter( + (arg) => + RESERVED_BOOLEAN_ARG_KEYS.has(arg.key) && + typeof arg.value !== 'boolean', + ) + .map((arg) => makeReservedArgDiagnostic(sentence, lines, arg.key)) + .filter((diagnostic): diagnostic is Diagnostic => !!diagnostic); +} + +/** + * 在源码里定位 `-参数名` 以确定波浪线的范围。定位不到就不报, + * 免得把警告标到一个和作者所写无关的位置上。 + */ +function makeReservedArgDiagnostic( + sentence: ISentence, + lines: string[], + key: string, +): Diagnostic | undefined { + const argText = `-${key}`; + const position = new SentenceCursor(lines, sentence).find(argText); + if (!position) { + return undefined; + } + return { + severity: DiagnosticSeverity.Warning, + range: { + start: { line: position.line, character: position.startCharacter }, + end: { + line: position.line, + character: position.startCharacter + argText.length, + }, + }, + source: 'WebGAL', + message: `“${key}”是所有命令通用的参数名,会被引擎自己使用,不会作为局部变量传给被调用的场景。请给参数换一个名字。`, + }; +} diff --git a/packages/terre2/src/Modules/lsp/suggestionRules/getArgsKey.ts b/packages/terre2/src/Modules/lsp/suggestionRules/getArgsKey.ts index 76376dfa..ca8a0bbb 100644 --- a/packages/terre2/src/Modules/lsp/suggestionRules/getArgsKey.ts +++ b/packages/terre2/src/Modules/lsp/suggestionRules/getArgsKey.ts @@ -127,9 +127,12 @@ export function getArgsKey( return []; } case commandType.setVar: { - return [whenKey, globalKey]; + return [whenKey, globalKey, localKey]; } case commandType.callScene: { + return [whenKey, writeReturnToKey]; + } + case commandType.return: { return [whenKey]; } case commandType.showVars: { @@ -918,6 +921,43 @@ changeScene:二周目剧情.txt; `), }; +const localKey: CompletionItem = { + kind: CompletionItemKind.Constant, + label: 'local', + insertText: 'local', + detail: '局部变量', + documentation: markdown(` +写入当前场景的局部变量,也就是 \`callScene\` 传进来的参数所在的那个命名空间。局部变量随场景调用结束而消失,不会影响调用方的同名变量。 + +\`\`\`ws +; battle.txt,由 callScene:battle.txt -hp=100 调用 +setVar:hp=hp-30 -local; +旁白:受到攻击,剩余血量 {hp}。; +\`\`\` + +不加 \`-local\` 写的是普通变量,而读取时局部变量优先,这次写入将读不出来。要改传进来的参数,必须加 \`-local\`。 + +\`-local\` 与 \`-global\` 互斥,同时写时按 \`-global\` 处理。 + `), +}; + +const writeReturnToKey: CompletionItem = { + kind: CompletionItemKind.Constant, + label: 'writeReturnTo', + insertText: 'writeReturnTo=', + detail: '返回值写回的变量', + documentation: markdown(` +指定被调用场景的返回值写回调用方的哪个变量,写入方式与不带参数的 \`setVar\` 相同。 + +\`\`\`ws +callScene:battle.txt -enemy=史莱姆 -writeReturnTo=result; +旁白:战斗结果是 {result}。; +\`\`\` + +被调用的场景没有执行 \`return\` 而自然结束时,写回的是空字符串。 + `), +}; + const nameKey: CompletionItem = { kind: CompletionItemKind.Constant, label: 'name', diff --git a/packages/terre2/src/Modules/lsp/suggestionRules/getCommands.ts b/packages/terre2/src/Modules/lsp/suggestionRules/getCommands.ts index 1bf41ba0..b52a585c 100644 --- a/packages/terre2/src/Modules/lsp/suggestionRules/getCommands.ts +++ b/packages/terre2/src/Modules/lsp/suggestionRules/getCommands.ts @@ -247,9 +247,28 @@ setVar:; detail: `场景调用`, documentation: markdown( `如果你需要在执行完调用的场景后回到先前的场景(即父场景),你可以使用 callScene 来调用场景 + +\`-参数名=值\` 会成为被调用场景的局部变量,\`-writeReturnTo\` 指定返回值写回调用方的哪个变量 \`\`\` callScene:Chapter-2.txt; -callScene:; +callScene:battle.txt -enemy=史莱姆 -hp=100 -writeReturnTo=result; +callScene: [-参数名=值] [-writeReturnTo=variableName]; +\`\`\``, + ), + }, + { + kind: CompletionItemKind.Function, + label: 'return', + insertText: makeInsertText('return'), + detail: `从调用的场景返回`, + documentation: markdown( + `提前结束当前被 callScene 调用的场景,回到调用位置继续执行。冒号后是返回值,求值规则同 setVar + +被调用的场景执行到末尾时会自动返回,只有需要提前返回时才用得上 return +\`\`\` +return; +return:胜利; +return:{hp}>0; \`\`\``, ), }, diff --git a/packages/terre2/src/Modules/lsp/webgalLsp.ts b/packages/terre2/src/Modules/lsp/webgalLsp.ts index df6c9040..2802bfec 100644 --- a/packages/terre2/src/Modules/lsp/webgalLsp.ts +++ b/packages/terre2/src/Modules/lsp/webgalLsp.ts @@ -19,6 +19,7 @@ import { tokenTypeMap, } from './semanticToken'; import { complete, checkTriggerCompletion } from './completion'; +import { collectDiagnostics } from './diagnostics'; export function createWsConnection( reader: MessageReader, @@ -201,7 +202,7 @@ export function createWsConnection( // when the text document first opened or when its content has changed. documents.onDidChangeContent(async (change) => { // TODO: Errors (e.g., variable undefined) & linting (e.g., ending`;`) - // await validateTextDocument(change.document); + await validateTextDocument(change.document); }); async function validateTextDocument( @@ -211,7 +212,10 @@ export function createWsConnection( const settings = await getDocumentSettings(textDocument.uri); // connection.console.log(JSON.stringify(settings)); - const diagnostics: Diagnostic[] = []; + const diagnostics: Diagnostic[] = collectDiagnostics( + textDocument.getText(), + textDocument.uri, + ); // Send the computed diagnostics to VS Code. connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); From a3b1f73436aa784c7db4afebdea5bedc39e49059 Mon Sep 17 00:00:00 2001 From: Mahiru Date: Sat, 1 Aug 2026 21:55:35 +0800 Subject: [PATCH 09/13] =?UTF-8?q?fix:=20=E5=9B=BE=E5=BD=A2=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=9D=97=E7=9A=84=20next=20=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E5=BC=BA=E5=88=B6=E6=8D=A2=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editor/GraphicalEditor/GraphicalEditor.tsx | 14 +------------- .../SentenceEditor/BasicCommands.tsx | 7 +++++-- .../editor/GraphicalEditor/SentenceEditor/Bgm.tsx | 1 + .../GraphicalEditor/SentenceEditor/CallSteam.tsx | 1 + .../GraphicalEditor/SentenceEditor/ChangeBg.tsx | 2 -- .../SentenceEditor/ChangeCallScene.tsx | 1 + .../SentenceEditor/ChangeFigure.tsx | 2 -- .../GraphicalEditor/SentenceEditor/Choose.tsx | 3 +++ .../editor/GraphicalEditor/SentenceEditor/End.tsx | 3 +++ .../SentenceEditor/GetUserInput.tsx | 1 + .../GraphicalEditor/SentenceEditor/Intro.tsx | 3 +++ .../GraphicalEditor/SentenceEditor/MiniAvatar.tsx | 1 + .../GraphicalEditor/SentenceEditor/PixiPerform.tsx | 1 + .../GraphicalEditor/SentenceEditor/PlayEffect.tsx | 1 + .../GraphicalEditor/SentenceEditor/PlayVideo.tsx | 1 + .../editor/GraphicalEditor/SentenceEditor/Say.tsx | 1 + .../SentenceEditor/SetAnimation.tsx | 2 -- .../SentenceEditor/SetTempAnimation.tsx | 2 -- .../GraphicalEditor/SentenceEditor/SetTextbox.tsx | 1 + .../SentenceEditor/SetTransform.tsx | 2 -- .../SentenceEditor/SetTransition.tsx | 1 + .../GraphicalEditor/SentenceEditor/UnlockExtra.tsx | 1 + .../editor/GraphicalEditor/SentenceEditor/Wait.tsx | 1 + .../SentenceEditor/sentenceEditor.module.scss | 8 -------- .../components/SentenceArgOption.tsx | 6 ++---- 25 files changed, 30 insertions(+), 37 deletions(-) diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/GraphicalEditor.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/GraphicalEditor.tsx index ca518553..7c313072 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/GraphicalEditor.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/GraphicalEditor.tsx @@ -61,15 +61,6 @@ interface SentenceRowContentProps extends SentenceRowProps { provided: DraggableProvided; } -const inlineArgOptionCommands = new Set([ - commandType.changeBg, - commandType.changeFigure, - commandType.setAnimation, - commandType.setComplexAnimation, - commandType.setTransform, - commandType.setTempAnimation, -]); - export default function GraphicalEditor(props: IGraphicalEditorProps) { const [sentenceData, setSentenceData] = useState([]); const sentenceDataRef = useRef([]); @@ -474,9 +465,7 @@ const SentenceRowContent = (props: SentenceRowContentProps) => { disabledText={t`不使用 when 条件`} placeholder={t`例如:a>0 && flag==true`} onSubmit={(newSentence) => props.onUpdate(newSentence, i)} - inline={inlineArgOptionCommands.has(sentence.command)} />; - const inlineArgOption = inlineArgOptionCommands.has(sentence.command); return
{ {sentenceItem.show &&
{ props.onUpdate(newSentence, i); - }} targetPath={targetPath} sceneLabels={sceneLabels} extraOptions={inlineArgOption ? argOption : undefined} /> - {!inlineArgOption && argOption} + }} targetPath={targetPath} sceneLabels={sceneLabels} extraOptions={argOption} />
}
diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/BasicCommands.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/BasicCommands.tsx index 91bcc4da..149e0261 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/BasicCommands.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/BasicCommands.tsx @@ -59,6 +59,7 @@ function LabelCommand(props: ISentenceEditorProps) { onBlur={submit} placeholder={t`本场景内唯一的 label 名称`} /> + {props.extraOptions} ; } @@ -95,6 +96,7 @@ function JumpLabelCommand(props: ISentenceEditorProps) { }} /> } + {props.extraOptions} ; } @@ -149,6 +151,7 @@ function SetVarCommand(props: ISentenceEditorProps) { }} /> + {props.extraOptions} ; } @@ -177,6 +180,7 @@ function ReturnCommand(props: ISentenceEditorProps) { onBlur={submit} placeholder={t`留空表示不返回值。可填数字、true/false、字符串或表达式`} /> + {props.extraOptions} ; } @@ -245,8 +249,6 @@ function SetComplexAnimationCommand(props: ISentenceEditorProps) { onBlur={submit} placeholder={t`留空使用默认值`} /> - -
+ {props.extraOptions}
; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Bgm.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Bgm.tsx index 2975b137..0bfc70a7 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Bgm.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Bgm.tsx @@ -107,6 +107,7 @@ export default function Bgm(props: ISentenceEditorProps) { placeholder={t`默认 default`} /> } + {props.extraOptions} ; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/CallSteam.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/CallSteam.tsx index 96df7f92..a1943dbb 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/CallSteam.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/CallSteam.tsx @@ -65,6 +65,7 @@ export default function CallSteam(props: ISentenceEditorProps) { style={{ width: "100%" }} /> + {props.extraOptions} ; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeBg.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeBg.tsx index cfdb5cfc..30d442b2 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeBg.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeBg.tsx @@ -157,8 +157,6 @@ export default function ChangeBg(props: ISentenceEditorProps) { ignoreDefault.set(value); submit(); }} /> - -
{ isGoNext.set(newValue); diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeCallScene.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeCallScene.tsx index 3ec1fbc7..72712e86 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeCallScene.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeCallScene.tsx @@ -128,6 +128,7 @@ export default function ChangeCallScene(props: ISentenceEditorProps) { style={{ width: "100%" }} /> } + {props.extraOptions}
{isCallScene.value && <> diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeFigure.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeFigure.tsx index d78140eb..b5b961b0 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeFigure.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/ChangeFigure.tsx @@ -652,8 +652,6 @@ export default function ChangeFigure(props: ISentenceEditorProps) { > {renderMoreOptions()} } - -
+
+ {props.extraOptions} +
; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/End.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/End.tsx index 94390d25..101aa13c 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/End.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/End.tsx @@ -7,5 +7,8 @@ export default function End(props: ISentenceEditorProps) { return
+
+ {props.extraOptions} +
; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/GetUserInput.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/GetUserInput.tsx index f53ddfe4..72b8cd98 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/GetUserInput.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/GetUserInput.tsx @@ -136,6 +136,7 @@ export default function GetUserInput(props: ISentenceEditorProps) { style={{width: "100%"}} /> + {props.extraOptions} ; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Intro.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Intro.tsx index 3acc0528..20b6689c 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Intro.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Intro.tsx @@ -264,6 +264,9 @@ export default function Intro(props: ISentenceEditorProps) { onClick={() => updateExpand(props.index)} /> +
+ {props.extraOptions} +
diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/MiniAvatar.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/MiniAvatar.tsx index a4660641..9916b029 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/MiniAvatar.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/MiniAvatar.tsx @@ -43,6 +43,7 @@ export default function MiniAvatar(props: ISentenceEditorProps) { extNames={extNameMap.get('image')?.filter(item => item !== '.jpg')} /> } + {props.extraOptions}
; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/PixiPerform.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/PixiPerform.tsx index 0283e9f6..1eff5dda 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/PixiPerform.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/PixiPerform.tsx @@ -71,6 +71,7 @@ export default function PixiPerform(props: ISentenceEditorProps) { style={{ width: "100%" }} /> } + {props.extraOptions} ; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/PlayEffect.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/PlayEffect.tsx index 5287dd60..fbb8b23e 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/PlayEffect.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/PlayEffect.tsx @@ -73,6 +73,7 @@ export default function PlayEffect(props: ISentenceEditorProps) { style={{ width: "100%" }} /> + {props.extraOptions} ; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/PlayVideo.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/PlayVideo.tsx index 36357ac3..b940fab1 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/PlayVideo.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/PlayVideo.tsx @@ -43,6 +43,7 @@ export default function PlayVideo(props: ISentenceEditorProps) { submit(); }} onText={t`禁止跳过视频`} offText={t`启用视频跳过`} isChecked={isSkipOff.value} /> + {props.extraOptions} ; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Say.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Say.tsx index 920c5cf0..ee8cf3b9 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Say.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Say.tsx @@ -320,6 +320,7 @@ export default function Say(props: ISentenceEditorProps) { }} /> + {props.extraOptions} ); diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetAnimation.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetAnimation.tsx index 91e5530d..3166f69e 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetAnimation.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetAnimation.tsx @@ -105,8 +105,6 @@ export default function SetAnimation(props: ISentenceEditorProps) { ignoreDefault.set(value); submit(); }} /> - -
{ isGoNext.set(newValue); diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTempAnimation.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTempAnimation.tsx index ac9cd07e..3cb6ab7d 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTempAnimation.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTempAnimation.tsx @@ -280,8 +280,6 @@ export default function SetTempAnimation(props: ISentenceEditorProps) { ignoreDefault.set(value); submit(); }} /> -
-
{ isGoNext.set(newValue); diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTextbox.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTextbox.tsx index 658a81ad..10f961ef 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTextbox.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTextbox.tsx @@ -27,6 +27,7 @@ export default function SetTextbox(props: ISentenceEditorProps) { submit(); }} onText={t`隐藏文本框`} offText={t`显示文本框`} isChecked={isHideTextbox.value} /> + {props.extraOptions}
; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransform.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransform.tsx index ab58ccb6..f58f0c56 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransform.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransform.tsx @@ -157,8 +157,6 @@ export default function SetTransform(props: ISentenceEditorProps) { ignoreDefault.set(value); submit(); }} /> - -
{ isGoNext.set(newValue); diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransition.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransition.tsx index caa87c45..e47ba59e 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransition.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/SetTransition.tsx @@ -88,6 +88,7 @@ export default function SetTransition(props: ISentenceEditorProps) { ignoreDefault.set(value); submit(); }} /> + {props.extraOptions}
; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/UnlockExtra.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/UnlockExtra.tsx index 5d472244..93a1f43f 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/UnlockExtra.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/UnlockExtra.tsx @@ -106,6 +106,7 @@ export default function UnlockExtra(props: ISentenceEditorProps) { placeholder={t`默认值0`} /> } + {props.extraOptions} ; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Wait.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Wait.tsx index b4404607..e29d1e9e 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Wait.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Wait.tsx @@ -47,6 +47,7 @@ export default function Wait(props: ISentenceEditorProps) { isChecked={nobreak.value} /> + {props.extraOptions} ; } diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/sentenceEditor.module.scss b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/sentenceEditor.module.scss index f46f8719..07e3acf6 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/sentenceEditor.module.scss +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/sentenceEditor.module.scss @@ -15,14 +15,6 @@ gap: 4px; } -.commonArgItem { - display: flex; - align-items: stretch; - flex-wrap: wrap; - gap: 4px; - width: 100%; -} - .assetPreview { width: 44px; height: 32px; diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/components/SentenceArgOption.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/components/SentenceArgOption.tsx index 86b08017..e30ed9cf 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/components/SentenceArgOption.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/components/SentenceArgOption.tsx @@ -15,7 +15,6 @@ interface ISentenceArgOptionProps { disabledText: string; placeholder: string; onSubmit: (newSentence: string) => void; - inline?: boolean; } function isEscaped(text: string, index: number) { @@ -99,7 +98,8 @@ export default function SentenceArgOption(props: ISentenceArgOptionProps) { })); }; - const content = <> + // 直接作为选项渲染在语句自己的选项行里,不额外套容器,避免被挤到单独一行 + return <> } ; - - return props.inline ? content :
{content}
; } From bd2fe416e10dffa9b6ecebd452c6806e65d0a7d5 Mon Sep 17 00:00:00 2001 From: Mahiru Date: Sat, 1 Aug 2026 22:04:24 +0800 Subject: [PATCH 10/13] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20Say=20?= =?UTF-8?q?=E5=9B=BE=E5=BD=A2=E7=BC=96=E8=BE=91=E5=99=A8=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editor/GraphicalEditor/SentenceEditor/Say.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Say.tsx b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Say.tsx index ee8cf3b9..636c58db 100644 --- a/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Say.tsx +++ b/packages/origine2/src/pages/editor/GraphicalEditor/SentenceEditor/Say.tsx @@ -202,27 +202,27 @@ export default function Say(props: ISentenceEditorProps) { }}>{t`添加新行`} )}
- + { isNoSpeaker.set(newValue); submit(); }} - onText={t`隐藏角色名`} - offText={t`显示角色名`} + onText={t`旁白模式,不显示角色名`} + offText={t`普通对话,显示角色名`} isChecked={isNoSpeaker.value} /> - + { isClearSpeaker.set(newValue); submit(); }} - onText={t`执行后清除角色名`} - offText={t`保留角色名`} + onText={t`清除角色名,留空角色名的语句不再显示角色名`} + offText={t`保留角色名,留空角色名的语句沿用上句的角色名`} isChecked={isClearSpeaker.value} /> From 950b4dd74bb26ab8f760fc9977263c3eb172a3f1 Mon Sep 17 00:00:00 2001 From: Mahiru Date: Sat, 1 Aug 2026 22:30:01 +0800 Subject: [PATCH 11/13] i18n --- packages/origine2/src/locales/en.po | 28 +++++++++++++-------------- packages/origine2/src/locales/ja.po | 28 +++++++++++++-------------- packages/origine2/src/locales/ko.po | 28 +++++++++++++-------------- packages/origine2/src/locales/zhCn.po | 28 +++++++++++++-------------- 4 files changed, 56 insertions(+), 56 deletions(-) diff --git a/packages/origine2/src/locales/en.po b/packages/origine2/src/locales/en.po index 2e99fb38..2903ed43 100644 --- a/packages/origine2/src/locales/en.po +++ b/packages/origine2/src/locales/en.po @@ -377,8 +377,8 @@ msgstr "Save flowchart" msgid "保持闭眼(毫秒)" msgstr "Closed duration (ms)" -msgid "保留角色名" -msgstr "Keep role name" +msgid "保留角色名,留空角色名的语句沿用上句的角色名" +msgstr "Keep the role name; lines that leave the role name blank inherit it from the previous line" msgid "修改时间" msgstr "Last modified" @@ -1334,6 +1334,12 @@ msgstr "New game" msgid "方形" msgstr "Square" +msgid "旁白模式" +msgstr "Narration mode" + +msgid "旁白模式,不显示角色名" +msgstr "Narration mode, no role name shown" + msgid "旁白模式,无角色名" msgstr "Voiceover mode, no role name" @@ -1391,15 +1397,15 @@ msgstr "Show figure" msgid "显示背景" msgstr "Show background" -msgid "显示角色名" -msgstr "Show role name" - msgid "显示预览窗口" msgstr "Show preview window" msgid "普通对话" msgstr "General dialogue" +msgid "普通对话,显示角色名" +msgstr "Normal dialogue, role name shown" + msgid "暂无变量快照" msgstr "No variable snapshot" @@ -1670,6 +1676,9 @@ msgstr "Clear effects" msgid "清除立绘" msgstr "Clear figure" +msgid "清除角色名,留空角色名的语句不再显示角色名" +msgstr "Clear the role name; lines that leave the role name blank no longer show one" + msgid "游戏" msgstr "Games" @@ -2075,9 +2084,6 @@ msgstr "Role name, leave blank to inherit from previous sentence: Dialogue;" msgid "角色名内层文本" msgstr "Role Name Inner Text" -msgid "角色名处理" -msgstr "Role name handling" - msgid "角色名外层文本" msgstr "Role Name Outer Text" @@ -2090,9 +2096,6 @@ msgstr "Role Name Text Box" msgid "角色名文本框背景" msgstr "Role Name Text Box Background" -msgid "角色名显示" -msgstr "Role name display" - msgid "角落头像" msgstr "Corner avatar" @@ -2456,9 +2459,6 @@ msgstr "Hide Sidebar" msgid "隐藏文本框" msgstr "Hide Text Box" -msgid "隐藏角色名" -msgstr "Hide character name" - msgid "雨" msgstr "Rain" diff --git a/packages/origine2/src/locales/ja.po b/packages/origine2/src/locales/ja.po index f51c410a..ba8619ca 100644 --- a/packages/origine2/src/locales/ja.po +++ b/packages/origine2/src/locales/ja.po @@ -377,8 +377,8 @@ msgstr "フローチャートを保存" msgid "保持闭眼(毫秒)" msgstr "目を閉じたままにする時間(ミリ秒)" -msgid "保留角色名" -msgstr "キャラクター名を保持" +msgid "保留角色名,留空角色名的语句沿用上句的角色名" +msgstr "キャラクター名を保持し、キャラクター名が空欄の文は前の文のキャラクター名を引き継ぐ" msgid "修改时间" msgstr "最終更新" @@ -1334,6 +1334,12 @@ msgstr "新しいゲーム" msgid "方形" msgstr "方形" +msgid "旁白模式" +msgstr "ナレーションモード" + +msgid "旁白模式,不显示角色名" +msgstr "ナレーションモード、キャラクター名を表示しない" + msgid "旁白模式,无角色名" msgstr "キャラクター名は設定不可" @@ -1391,15 +1397,15 @@ msgstr "立ち絵を表示" msgid "显示背景" msgstr "背景画像を表示" -msgid "显示角色名" -msgstr "キャラクター名を設定" - msgid "显示预览窗口" msgstr "プレビューウィンドウを表示" msgid "普通对话" msgstr "ダイアログ" +msgid "普通对话,显示角色名" +msgstr "通常の会話、キャラクター名を表示" + msgid "暂无变量快照" msgstr "変数スナップショットはありません" @@ -1670,6 +1676,9 @@ msgstr "特殊効果をクリア" msgid "清除立绘" msgstr "立ち絵をクリア" +msgid "清除角色名,留空角色名的语句不再显示角色名" +msgstr "キャラクター名を消去し、キャラクター名が空欄の文ではキャラクター名を表示しない" + msgid "游戏" msgstr "ゲーム" @@ -2075,9 +2084,6 @@ msgstr "キャラクター名を空白のままにすると前の文から継承 msgid "角色名内层文本" msgstr "キャラクター名内側テキスト" -msgid "角色名处理" -msgstr "キャラクター名の処理" - msgid "角色名外层文本" msgstr "キャラクター名外側テキスト" @@ -2090,9 +2096,6 @@ msgstr "キャラクター名テキストボックス" msgid "角色名文本框背景" msgstr "キャラクター名テキストボックスの背景" -msgid "角色名显示" -msgstr "キャラクター名表示" - msgid "角落头像" msgstr "コーナーアバター" @@ -2456,9 +2459,6 @@ msgstr "サイドバーを隠す" msgid "隐藏文本框" msgstr "テキストボックスを非表示" -msgid "隐藏角色名" -msgstr "キャラクター名を非表示" - msgid "雨" msgstr "雨" diff --git a/packages/origine2/src/locales/ko.po b/packages/origine2/src/locales/ko.po index 920ffdba..15bbeee0 100644 --- a/packages/origine2/src/locales/ko.po +++ b/packages/origine2/src/locales/ko.po @@ -377,8 +377,8 @@ msgstr "플로차트 저장" msgid "保持闭眼(毫秒)" msgstr "눈 감기 유지(밀리초)" -msgid "保留角色名" -msgstr "캐릭터 이름 유지" +msgid "保留角色名,留空角色名的语句沿用上句的角色名" +msgstr "캐릭터 이름을 유지하며, 캐릭터 이름을 비워 둔 문장은 이전 문장의 캐릭터 이름을 이어받음" msgid "修改时间" msgstr "수정 시간" @@ -1334,6 +1334,12 @@ msgstr "새로운 게임" msgid "方形" msgstr "사각형" +msgid "旁白模式" +msgstr "나레이션 모드" + +msgid "旁白模式,不显示角色名" +msgstr "나레이션 모드, 캐릭터 이름을 표시하지 않음" + msgid "旁白模式,无角色名" msgstr "내레이션 모드, 캐릭터 이름 없음" @@ -1391,15 +1397,15 @@ msgstr "스탠딩 표시" msgid "显示背景" msgstr "배경 표시" -msgid "显示角色名" -msgstr "캐릭터 이름 표시" - msgid "显示预览窗口" msgstr "미리보기 창 표시" msgid "普通对话" msgstr "일반 대화" +msgid "普通对话,显示角色名" +msgstr "일반 대화, 캐릭터 이름 표시" + msgid "暂无变量快照" msgstr "변수 스냅샷 없음" @@ -1670,6 +1676,9 @@ msgstr "특수 효과 초기화" msgid "清除立绘" msgstr "스탠딩 초기화" +msgid "清除角色名,留空角色名的语句不再显示角色名" +msgstr "캐릭터 이름을 초기화하며, 캐릭터 이름을 비워 둔 문장에는 캐릭터 이름이 표시되지 않음" + msgid "游戏" msgstr "게임" @@ -2075,9 +2084,6 @@ msgstr "캐릭터 이름, 이전 문장과 이으려면 빈칸:대화;" msgid "角色名内层文本" msgstr "캐릭터 이름 내부 텍스트" -msgid "角色名处理" -msgstr "캐릭터 이름 처리" - msgid "角色名外层文本" msgstr "캐릭터 이름 외부 텍스트" @@ -2090,9 +2096,6 @@ msgstr "캐릭터 이름 텍스트 창" msgid "角色名文本框背景" msgstr "캐릭터 이름 텍스트 창 배경" -msgid "角色名显示" -msgstr "캐릭터 이름 표시" - msgid "角落头像" msgstr "구석 미니 프로필" @@ -2456,9 +2459,6 @@ msgstr "사이드바 숨기기" msgid "隐藏文本框" msgstr "대화창 숨기기" -msgid "隐藏角色名" -msgstr "캐릭터 이름 숨기기" - msgid "雨" msgstr "비" diff --git a/packages/origine2/src/locales/zhCn.po b/packages/origine2/src/locales/zhCn.po index bc77e1bf..455010c6 100644 --- a/packages/origine2/src/locales/zhCn.po +++ b/packages/origine2/src/locales/zhCn.po @@ -377,8 +377,8 @@ msgstr "保存流程图" msgid "保持闭眼(毫秒)" msgstr "保持闭眼(毫秒)" -msgid "保留角色名" -msgstr "保留角色名" +msgid "保留角色名,留空角色名的语句沿用上句的角色名" +msgstr "保留角色名,留空角色名的语句沿用上句的角色名" msgid "修改时间" msgstr "修改时间" @@ -1334,6 +1334,12 @@ msgstr "新的游戏" msgid "方形" msgstr "方形" +msgid "旁白模式" +msgstr "旁白模式" + +msgid "旁白模式,不显示角色名" +msgstr "旁白模式,不显示角色名" + msgid "旁白模式,无角色名" msgstr "旁白模式,无角色名" @@ -1391,15 +1397,15 @@ msgstr "显示立绘" msgid "显示背景" msgstr "显示背景" -msgid "显示角色名" -msgstr "显示角色名" - msgid "显示预览窗口" msgstr "显示预览窗口" msgid "普通对话" msgstr "普通对话" +msgid "普通对话,显示角色名" +msgstr "普通对话,显示角色名" + msgid "暂无变量快照" msgstr "暂无变量快照" @@ -1670,6 +1676,9 @@ msgstr "清除特效" msgid "清除立绘" msgstr "清除立绘" +msgid "清除角色名,留空角色名的语句不再显示角色名" +msgstr "清除角色名,留空角色名的语句不再显示角色名" + msgid "游戏" msgstr "游戏" @@ -2075,9 +2084,6 @@ msgstr "角色名,留空以继承上句:对话;" msgid "角色名内层文本" msgstr "角色名内层文本" -msgid "角色名处理" -msgstr "角色名处理" - msgid "角色名外层文本" msgstr "角色名外层文本" @@ -2090,9 +2096,6 @@ msgstr "角色名文本框" msgid "角色名文本框背景" msgstr "角色名文本框背景" -msgid "角色名显示" -msgstr "角色名显示" - msgid "角落头像" msgstr "角落头像" @@ -2456,9 +2459,6 @@ msgstr "隐藏侧边栏" msgid "隐藏文本框" msgstr "隐藏文本框" -msgid "隐藏角色名" -msgstr "隐藏角色名" - msgid "雨" msgstr "雨" From c83091f8e1659e0b48de16ec14de0b14748d7e56 Mon Sep 17 00:00:00 2001 From: Mahiru Date: Sat, 1 Aug 2026 22:55:28 +0800 Subject: [PATCH 12/13] fix: flowchart UI and tips --- packages/origine2/src/locales/en.po | 15 -------- packages/origine2/src/locales/ja.po | 15 -------- packages/origine2/src/locales/ko.po | 15 -------- packages/origine2/src/locales/zhCn.po | 15 -------- .../FlowchartEditor.module.scss | 23 ------------ .../FlowchartEditor/FlowchartEditor.tsx | 36 +++++++++---------- 6 files changed, 16 insertions(+), 103 deletions(-) diff --git a/packages/origine2/src/locales/en.po b/packages/origine2/src/locales/en.po index 2903ed43..aa657f97 100644 --- a/packages/origine2/src/locales/en.po +++ b/packages/origine2/src/locales/en.po @@ -275,9 +275,6 @@ msgstr "Set multiple animations for figure or background" msgid "为立绘或背景图片调用动画效果" msgstr "Call animation for figure or background" -msgid "主线流程图不可删除" -msgstr "The main flowchart cannot be deleted" - msgid "主要样式" msgstr "Main Style" @@ -608,9 +605,6 @@ msgstr "Argument value. Use curly-brace interpolation to read a variable of the msgid "参数名" msgstr "Argument name" -msgid "双击节点名称可编辑" -msgstr "Double-click a node name to edit it" - msgid "反射电影滤镜" msgstr "Reflection film filter" @@ -1160,9 +1154,6 @@ msgstr "End the called scene early and go back to the call site, optionally carr msgid "提示" msgstr "Tip" -msgid "提示:" -msgstr "Tip:" - msgid "提示:先设置立绘/背景,再应用动画,否则找不到目标。" msgstr "Tip: Set the figure/background first, then apply the animation, otherwise the target cannot be found." @@ -1721,9 +1712,6 @@ msgstr "Filter" msgid "点击或拖拽文件至此上传" msgstr "Click or drag file to this area to upload" -msgid "点击节点选择场景文件" -msgstr "Click a node to select a scene file" - msgid "点状电影滤镜" msgstr "Dot film filter" @@ -2276,9 +2264,6 @@ msgstr "Execute Continuously" msgid "退场时长(单位为毫秒)" msgstr "Exit duration (milliseconds)" -msgid "选中节点或连线后按退格键或 Delete 键可删除" -msgstr "Select a node or edge and press Backspace or Delete to delete it" - msgid "选择" msgstr "Choose" diff --git a/packages/origine2/src/locales/ja.po b/packages/origine2/src/locales/ja.po index ba8619ca..52f6133d 100644 --- a/packages/origine2/src/locales/ja.po +++ b/packages/origine2/src/locales/ja.po @@ -275,9 +275,6 @@ msgstr "立ち絵や背景画像に複数のアニメーション効果を設定 msgid "为立绘或背景图片调用动画效果" msgstr "立ち絵や背景画像にアニメーション効果を適用する" -msgid "主线流程图不可删除" -msgstr "メインフローチャートは削除できません" - msgid "主要样式" msgstr "メインスタイル" @@ -608,9 +605,6 @@ msgstr "引数の値。波かっこ補間で呼び出し元の変数を参照で msgid "参数名" msgstr "引数名" -msgid "双击节点名称可编辑" -msgstr "ノード名をダブルクリックすると編集できます" - msgid "反射电影滤镜" msgstr "反射映画" @@ -1160,9 +1154,6 @@ msgstr "呼び出されたシーンを途中で終了して呼び出し元へ戻 msgid "提示" msgstr "ヒント" -msgid "提示:" -msgstr "ヒント:" - msgid "提示:先设置立绘/背景,再应用动画,否则找不到目标。" msgstr "ヒント: 最初に立ち絵の描画/背景を設定してから、アニメーションを適用します。そうしないと、ターゲットが見つかりません。" @@ -1721,9 +1712,6 @@ msgstr "フィルター" msgid "点击或拖拽文件至此上传" msgstr "ここまでクリックかドラッグでアップロードします" -msgid "点击节点选择场景文件" -msgstr "ノードをクリックしてシーンファイルを選択" - msgid "点状电影滤镜" msgstr "ドット映画" @@ -2276,9 +2264,6 @@ msgstr "連続実行" msgid "退场时长(单位为毫秒)" msgstr "退場時間(ミリ秒)" -msgid "选中节点或连线后按退格键或 Delete 键可删除" -msgstr "ノードまたは線を選択し、Backspace キーまたは Delete キーを押すと削除できます" - msgid "选择" msgstr "選択" diff --git a/packages/origine2/src/locales/ko.po b/packages/origine2/src/locales/ko.po index 15bbeee0..61ae4831 100644 --- a/packages/origine2/src/locales/ko.po +++ b/packages/origine2/src/locales/ko.po @@ -275,9 +275,6 @@ msgstr "스탠딩 또는 배경 이미지에 다중 구간 애니메이션 이 msgid "为立绘或背景图片调用动画效果" msgstr "스탠딩 또는 배경 이미지에 애니메이션 불러오기 이펙트를 적용" -msgid "主线流程图不可删除" -msgstr "메인 플로차트는 삭제할 수 없습니다" - msgid "主要样式" msgstr "메인 스타일" @@ -608,9 +605,6 @@ msgstr "인자 값. 중괄호 보간으로 호출한 쪽의 변수를 사용할 msgid "参数名" msgstr "인자 이름" -msgid "双击节点名称可编辑" -msgstr "노드 이름을 두 번 클릭하면 편집할 수 있습니다" - msgid "反射电影滤镜" msgstr "반사 영화 필터" @@ -1160,9 +1154,6 @@ msgstr "호출된 씬을 미리 끝내고 호출 지점으로 돌아가며, 반 msgid "提示" msgstr "힌트" -msgid "提示:" -msgstr "힌트:" - msgid "提示:先设置立绘/背景,再应用动画,否则找不到目标。" msgstr "힌트: 스탠딩/배경을 먼저 설정한 후 애니메이션을 적용하세요. 아니면 대상을 찾을 수 없습니다." @@ -1721,9 +1712,6 @@ msgstr "필터" msgid "点击或拖拽文件至此上传" msgstr "파일을 클릭하거나 여기로 드래그하여 업로드" -msgid "点击节点选择场景文件" -msgstr "노드를 클릭하여 장면 파일 선택" - msgid "点状电影滤镜" msgstr "점박이 영화 필터" @@ -2276,9 +2264,6 @@ msgstr "연속 실행" msgid "退场时长(单位为毫秒)" msgstr "퇴장 시간(단위: 밀리초)" -msgid "选中节点或连线后按退格键或 Delete 键可删除" -msgstr "노드나 연결선을 선택한 뒤 Backspace 또는 Delete 키를 누르면 삭제할 수 있습니다" - msgid "选择" msgstr "선택" diff --git a/packages/origine2/src/locales/zhCn.po b/packages/origine2/src/locales/zhCn.po index 455010c6..64b96677 100644 --- a/packages/origine2/src/locales/zhCn.po +++ b/packages/origine2/src/locales/zhCn.po @@ -275,9 +275,6 @@ msgstr "为立绘或背景图片设置多段动画效果" msgid "为立绘或背景图片调用动画效果" msgstr "为立绘或背景图片调用动画效果" -msgid "主线流程图不可删除" -msgstr "主线流程图不可删除" - msgid "主要样式" msgstr "主要样式" @@ -608,9 +605,6 @@ msgstr "参数值,用花括号插值可取调用方的变量" msgid "参数名" msgstr "参数名" -msgid "双击节点名称可编辑" -msgstr "双击节点名称可编辑" - msgid "反射电影滤镜" msgstr "反射电影滤镜" @@ -1160,9 +1154,6 @@ msgstr "提前结束被调用的场景并回到调用处,可带回一个返回 msgid "提示" msgstr "提示" -msgid "提示:" -msgstr "提示:" - msgid "提示:先设置立绘/背景,再应用动画,否则找不到目标。" msgstr "提示:先设置立绘/背景,再应用动画,否则找不到目标。" @@ -1721,9 +1712,6 @@ msgstr "滤镜" msgid "点击或拖拽文件至此上传" msgstr "点击或拖拽文件至此上传" -msgid "点击节点选择场景文件" -msgstr "点击节点选择场景文件" - msgid "点状电影滤镜" msgstr "点状电影滤镜" @@ -2276,9 +2264,6 @@ msgstr "连续执行" msgid "退场时长(单位为毫秒)" msgstr "退场时长(单位为毫秒)" -msgid "选中节点或连线后按退格键或 Delete 键可删除" -msgstr "选中节点或连线后按退格键或 Delete 键可删除" - msgid "选择" msgstr "选择" diff --git a/packages/origine2/src/pages/editor/FlowchartEditor/FlowchartEditor.module.scss b/packages/origine2/src/pages/editor/FlowchartEditor/FlowchartEditor.module.scss index 091550b0..7b28f424 100644 --- a/packages/origine2/src/pages/editor/FlowchartEditor/FlowchartEditor.module.scss +++ b/packages/origine2/src/pages/editor/FlowchartEditor/FlowchartEditor.module.scss @@ -78,29 +78,6 @@ gap: 4px; } } - - .info { - margin: 16px; - padding: 12px; - background: #f5f5f5; - border-radius: 4px; - font-size: 12px; - - p { - margin: 0 0 8px 0; - font-weight: 500; - } - - ul { - margin: 0; - padding-left: 16px; - - li { - margin: 4px 0; - color: #666; - } - } - } } .canvas { diff --git a/packages/origine2/src/pages/editor/FlowchartEditor/FlowchartEditor.tsx b/packages/origine2/src/pages/editor/FlowchartEditor/FlowchartEditor.tsx index bdab25b5..4253f5ed 100644 --- a/packages/origine2/src/pages/editor/FlowchartEditor/FlowchartEditor.tsx +++ b/packages/origine2/src/pages/editor/FlowchartEditor/FlowchartEditor.tsx @@ -17,7 +17,7 @@ import { import '@xyflow/react/dist/style.css'; import styles from './FlowchartEditor.module.scss'; import { t } from '@lingui/macro'; -import { Button, Input, Dropdown, Option, Switch } from '@fluentui/react-components'; +import { Button, Input, Dropdown, Option, Switch, Toast, Toaster, ToastTitle, useToastController } from '@fluentui/react-components'; import { AddFilled, AddRegular, @@ -43,6 +43,8 @@ const nodeTypes = { custom: CustomNode, }; +const flowchartToasterId = 'flowchart-toaster'; + function FlowchartEditorContent() { const gameDir = useEditorStore.use.subPage(); const isAutoSaveFlowchart = useEditorStore.use.isAutoSaveFlowchart(); @@ -60,6 +62,12 @@ function FlowchartEditorContent() { const reactFlowInstance = useReactFlow(); + const { dispatchToast } = useToastController(flowchartToasterId); + /** 操作被规则拒绝时的提示,说明为什么这一步没有生效 */ + const warn = useCallback((message: string) => { + dispatchToast({message}, { intent: 'warning' }); + }, [dispatchToast]); + // 当前流程图 const currentFlowchart = flowcharts.find(f => f.id === currentFlowchartId); @@ -302,11 +310,9 @@ function FlowchartEditorContent() { // 添加新节点 const addNode = useCallback((isRoot = false) => { // 检查是否已有根节点 - if (isRoot) { - const hasRoot = nodes.some(n => (n.data as any).isRoot); - if (hasRoot) { - return; - } + if (isRoot && nodes.some(n => n.data.isRoot)) { + warn(t`每个流程图只能有一个根节点`); + return; } const newNode: Node = { @@ -334,7 +340,7 @@ function FlowchartEditorContent() { } setNextNodeId(nextNodeId + 1); - }, [nodes, nextNodeId]); + }, [nodes, nextNodeId, warn]); // 检查是否会形成环 const wouldCreateCycle = (source: string, target: string, currentEdges: Edge[]): boolean => { @@ -362,6 +368,7 @@ function FlowchartEditorContent() { // 检查是否会形成环 if (wouldCreateCycle(params.source, params.target, edges)) { + warn(t`不能创建循环连接`); return; } @@ -371,7 +378,7 @@ function FlowchartEditorContent() { target: params.target, }; setEdges((eds) => addEdge(newEdge, eds)); - }, [edges]); + }, [edges, warn]); // 更新节点数据 const updateNodeData = useCallback((nodeId: string, newData: any) => { @@ -519,18 +526,6 @@ function FlowchartEditorContent() { )}
- -
-

{t`提示:`}

-
    -
  • {t`主线流程图不可删除`}
  • -
  • {t`每个流程图只能有一个根节点`}
  • -
  • {t`双击节点名称可编辑`}
  • -
  • {t`点击节点选择场景文件`}
  • -
  • {t`选中节点或连线后按退格键或 Delete 键可删除`}
  • -
  • {t`不能创建循环连接`}
  • -
-
+ ); } From 522cad15f908bdf07ee37b0225e92e4478062087 Mon Sep 17 00:00:00 2001 From: Mahiru Date: Sat, 1 Aug 2026 23:39:53 +0800 Subject: [PATCH 13/13] v4.6.3 --- package.json | 2 +- packages/origine2/package.json | 4 +- .../WebGAL_Default_Template/template.json | 2 +- .../game/template/template.json | 2 +- .../WebGAL_Template/webgal-engine.json | 4 +- packages/terre2/package.json | 6 +- packages/terre2/src/version.ts | 2 +- releasenote.md | 72 ++++++++++--------- yarn.lock | 16 ++--- 9 files changed, 58 insertions(+), 52 deletions(-) diff --git a/package.json b/package.json index 1f2ba1aa..e217e2fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webgal-terre", - "version": "4.6.2", + "version": "4.6.3", "private": true, "scripts": { "dev": "concurrently \"yarn dev:terre\" \"yarn dev:origine\" \"yarn dev:start-dev-server\"", diff --git a/packages/origine2/package.json b/packages/origine2/package.json index 3ad227bd..e7c40e13 100644 --- a/packages/origine2/package.json +++ b/packages/origine2/package.json @@ -1,7 +1,7 @@ { "name": "webgal-origine-2", "private": true, - "version": "4.6.2", + "version": "4.6.3", "license": "MPL-2.0", "scripts": { "sync:editor-preview-protocol": "yarn workspace @webgal/editor-preview-protocol build", @@ -63,7 +63,7 @@ "sass": "^1.51.0", "swr": "^2.2.4", "uuid": "^13.0.0", - "webgal-parser": "4.6.3-beta.2", + "webgal-parser": "4.6.3", "zustand": "^4.5.2" }, "devDependencies": { diff --git a/packages/terre2/assets/templates/WebGAL_Default_Template/template.json b/packages/terre2/assets/templates/WebGAL_Default_Template/template.json index 981384f0..cb8bbbc9 100644 --- a/packages/terre2/assets/templates/WebGAL_Default_Template/template.json +++ b/packages/terre2/assets/templates/WebGAL_Default_Template/template.json @@ -1,4 +1,4 @@ { "name":"WebGAL Refine 2026", - "webgal-version":"4.6.2" + "webgal-version":"4.6.3" } diff --git a/packages/terre2/assets/templates/WebGAL_Template/game/template/template.json b/packages/terre2/assets/templates/WebGAL_Template/game/template/template.json index 981384f0..cb8bbbc9 100644 --- a/packages/terre2/assets/templates/WebGAL_Template/game/template/template.json +++ b/packages/terre2/assets/templates/WebGAL_Template/game/template/template.json @@ -1,4 +1,4 @@ { "name":"WebGAL Refine 2026", - "webgal-version":"4.6.2" + "webgal-version":"4.6.3" } diff --git a/packages/terre2/assets/templates/WebGAL_Template/webgal-engine.json b/packages/terre2/assets/templates/WebGAL_Template/webgal-engine.json index 7cdd18a1..4668fda4 100644 --- a/packages/terre2/assets/templates/WebGAL_Template/webgal-engine.json +++ b/packages/terre2/assets/templates/WebGAL_Template/webgal-engine.json @@ -2,9 +2,9 @@ "schemaVersion": "1.0.0", "id": "open-webgal.webgal", "name": "WebGAL", - "version": "4.6.2", + "version": "4.6.3", "type": "official", - "webgalVersion": "4.6.2", + "webgalVersion": "4.6.3", "description": "界面美观、功能强大、易于开发的全新网页端视觉小说引擎", "descriptions": { "en": "A brand new web Visual Novel engine with a beautiful interface, powerful features, and easy development", diff --git a/packages/terre2/package.json b/packages/terre2/package.json index 2d2eaf88..306ddcc5 100644 --- a/packages/terre2/package.json +++ b/packages/terre2/package.json @@ -1,6 +1,6 @@ { "name": "webgal-terre-2", - "version": "4.6.2", + "version": "4.6.3", "description": "", "author": "", "private": true, @@ -62,8 +62,8 @@ "vscode-languageserver": "^8.1.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-ws-jsonrpc-webgal": "0.2.1", - "webgal-engine": "4.6.2", - "webgal-parser": "4.6.3-beta.2", + "webgal-engine": "4.6.3", + "webgal-parser": "4.6.3", "webpack": "5.76.0", "ws": "8.18.0" }, diff --git a/packages/terre2/src/version.ts b/packages/terre2/src/version.ts index 805d0e55..49021562 100644 --- a/packages/terre2/src/version.ts +++ b/packages/terre2/src/version.ts @@ -1,2 +1,2 @@ -export const TERRE_VERSION = '4.6.2'; +export const TERRE_VERSION = '4.6.3'; export const version_number = TERRE_VERSION; diff --git a/releasenote.md b/releasenote.md index 7ffad21b..89a75980 100644 --- a/releasenote.md +++ b/releasenote.md @@ -4,27 +4,29 @@ #### 新功能 -新增实验性流程图功能,可在游戏配置中启用,创建主线和支线流程图,并为节点关联场景文件。 +较长的语句会自动分成多行显示,脚本编辑中的高亮和补全同样支持。 -流程图编辑器支持新增、重命名、删除流程图和节点,支持拖拽连接节点,并可自动保存。 +新增场景调用传值。可以为调用的场景传入数据,并指定把返回的结果保存到哪个变量,新增用于提前结束被调用场景的返回语句。 -新增回收站设置,删除游戏、模板或素材文件时可选择移动到系统回收站,降低误删风险。 +设置变量时可以选择只在当前场景内有效,场景结束后自动消失。 + +立绘位置新增左侧 1/4、左侧 1/3、右侧 1/3 和右侧 1/4,立绘、对话和预览调整框均已支持。 -优化 Android 版运行方式,启动时会自动准备运行环境,提供更明确的启动、停止和文件入口。 +新增回收站设置,删除游戏、模板或素材文件时可选择移动到系统回收站,降低误删风险。 -优化预览窗口拖拽调整体验,立绘和变换目标的定位、缩放和旋转结果更贴近实时预览中的实际显示。 +支持 opus 格式的音频。 -#### 修复 +脚本编辑新增检查提示,调用场景时使用了引擎已占用的名称会给出提醒。 -修复预览窗口拖拽调整在部分素材、预设目标或特殊 ID 下可能取得错误尺寸或位置的问题。 +优化图形编辑块的排版,连续执行和条件判断不再单独占一行。 -修复拖拽调整时继承的变换参数可能被写入为默认值,导致效果配置被意外改变的问题。 +优化对话编辑器的选项说明。 -修复启用实验性快速同步时,实时预览可能未按预期同步的问题。 +精简流程图编辑器的界面,移除侧边栏中多余的说明文字。 -修复默认模板文字在部分环境下可能出现颜色显示异常的问题。 +#### 修复 -修复 Android 版在准备内置资源时可能启动失败的问题。 +修复流程图编辑器中重复添加根节点或连出循环时没有提示的问题。 ## Release Notes @@ -33,27 +35,29 @@ #### New Features -Added an experimental flowchart feature that can be enabled in game settings, with support for main and branch flowcharts linked to scene files. +Longer statements are now split across several lines automatically, and script editing highlights and completes them as well. -The flowchart editor now supports creating, renaming, and deleting flowcharts and nodes, connecting nodes by dragging, and auto-saving changes. +Added value passing for scene calls. Data can be passed into a called scene, the returned result can be stored in a chosen variable, and a new return statement ends a called scene early. -Added a Recycle Bin setting so deleted games, templates, and asset files can be moved to the system recycle bin instead of being removed permanently. +Variables can now be set to be valid only inside the current scene and disappear when the scene ends. -Improved the Android runtime flow. The app now prepares its runtime environment automatically on startup and provides clearer start, stop, and file entry points. +Added four figure positions: left 1/4, left 1/3, right 1/3, and right 1/4, supported by figures, dialogue, and the preview adjustment box. -Improved preview drag adjustments so figure and transform target position, scale, and rotation better match the actual live preview display. +Added a Recycle Bin setting so deleted games, templates, and asset files can be moved to the system recycle bin instead of being removed permanently. -#### Fixes +Added support for opus audio. -Fixed preview drag adjustment boxes possibly using the wrong size or position for some assets, preset targets, or special IDs. +Script editing now warns when a scene call uses a name already taken by the engine. -Fixed inherited transform values possibly being written as default values during drag adjustment and unexpectedly changing effect settings. +Improved the layout of graphical editing blocks so continuous execution and conditions no longer take up a line of their own. -Fixed live preview possibly not using the expected sync mode when experimental fast sync was enabled. +Improved the option descriptions in the dialogue editor. -Fixed default template text color possibly displaying incorrectly in some environments. +Simplified the flowchart editor by removing the extra explanatory text from the sidebar. -Fixed Android startup possibly failing while preparing bundled resources. +#### Fixes + +Fixed adding a duplicate root node or drawing a loop in the flowchart editor giving no feedback. ## リリースノート @@ -62,24 +66,26 @@ Fixed Android startup possibly failing while preparing bundled resources. #### 新機能 -実験的なフローチャート機能を追加し、ゲーム設定から有効化して、メインとサブのフローチャートを作成し、ノードにシーンファイルを関連付けられるようにしました。 +長い文が自動的に複数行に分かれて表示されるようになりました。スクリプト編集でのハイライトと補完にも対応しています。 -フローチャートエディターでは、フローチャートとノードの追加、名前変更、削除、ドラッグによるノード接続、自動保存に対応しました。 +シーン呼び出しの値の受け渡しを追加しました。呼び出すシーンにデータを渡し、返ってきた結果を保存する変数を指定できます。呼び出したシーンを途中で終了する戻り文も追加しました。 -ごみ箱設定を追加し、ゲーム、テンプレート、素材ファイルの削除時にシステムのごみ箱へ移動できるようにしました。 +変数を設定するときに、現在のシーンの中だけで有効にする選択肢を追加しました。シーンが終わると自動的に消えます。 -Android 版の実行フローを改善し、起動時に実行環境を自動で準備し、開始、停止、ファイル操作の入口を分かりやすくしました。 +立ち絵の位置に左 1/4、左 1/3、右 1/3、右 1/4 を追加しました。立ち絵、会話、プレビューの調整枠のいずれも対応しています。 -プレビュー画面でのドラッグ調整を改善し、立ち絵や変形対象の位置、拡大率、回転がリアルタイムプレビューの表示により近くなるようにしました。 +ごみ箱設定を追加し、ゲーム、テンプレート、素材ファイルの削除時にシステムのごみ箱へ移動できるようにしました。 -#### 修正 +opus 形式の音声に対応しました。 + +スクリプト編集にチェック機能を追加し、シーンの呼び出しでエンジンが使用済みの名前を使っている場合に知らせるようにしました。 -一部の素材、プリセットターゲット、特殊 ID で、ドラッグ調整枠のサイズや位置が誤って取得されることがある問題を修正しました。 +グラフィカル編集ブロックのレイアウトを改善し、連続実行と条件判断が単独で 1 行を占めないようにしました。 -ドラッグ調整時に継承された変形値がデフォルト値として書き込まれ、エフェクト設定が意図せず変わることがある問題を修正しました。 +会話エディターの選択肢の説明を改善しました。 -実験的な高速同期を使用している場合に、リアルタイムプレビューが期待した同期方式で動作しないことがある問題を修正しました。 +フローチャートエディターの画面を整理し、サイドバーの余分な説明文を削除しました。 -一部の環境でデフォルトテンプレートの文字色が正しく表示されないことがある問題を修正しました。 +#### 修正 -Android 版で同梱リソースの準備中に起動が失敗することがある問題を修正しました。 +フローチャートエディターでルートノードを重ねて追加したり、循環する接続を作ろうとしても何も反応がない問題を修正しました。 diff --git a/yarn.lock b/yarn.lock index eb3d7958..644bcee2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13408,10 +13408,10 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -webgal-engine@4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/webgal-engine/-/webgal-engine-4.6.2.tgz#12fde317c8f4bb2fd0369f063002c0bc15a9d807" - integrity sha512-vIp5tY9caaIfmrfV7EL+z/uRWaWsuW8sU26C6JRqeH4AxCbVoS5i20OoNfxT+QAs2hTbwBEKyor8Vd+tQq6ppA== +webgal-engine@4.6.3: + version "4.6.3" + resolved "https://registry.yarnpkg.com/webgal-engine/-/webgal-engine-4.6.3.tgz#85d6fa58ab8d999cf21e2340675fc08698f4fc10" + integrity sha512-poOmnzavW0qOSXHUyyZi0E9SL7bnzdUC7JP7t9m0yAsI3NwH1vreYDCVYL8cJJkhdV3nrUF/2BpzJJtToLz+zg== dependencies: "@emotion/css" "^11.11.2" "@icon-park/react" "^1.4.2" @@ -13437,10 +13437,10 @@ webgal-engine@4.6.2: uuid "^9.0.0" vite-plugin-package-version "^1.0.2" -webgal-parser@4.6.3-beta.2: - version "4.6.3-beta.2" - resolved "https://registry.yarnpkg.com/webgal-parser/-/webgal-parser-4.6.3-beta.2.tgz#306e02fad9e48061292fa60d0140b18b945993c5" - integrity sha512-44TXG7HqhUjIwdfF5HCYUFGBCNb2XSzQl5wU1NxgbXiG2HgPXGJ4NrWER+ncWrfC9iXxybZCf4RvJARytxaA4g== +webgal-parser@4.6.3: + version "4.6.3" + resolved "https://registry.yarnpkg.com/webgal-parser/-/webgal-parser-4.6.3.tgz#f15d2af02e22c799e13928fb7fd883c8574d0ef7" + integrity sha512-edfz2pROjUEtZXQqqTS/2RIFocU5Att8Ei82Dl2Gl0FBQAhvmyP1v+aWOhQIW55nTy0IZ54L5tR3BXopM9SC9w== webidl-conversions@^3.0.0: version "3.0.1"