diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..c6c8b3621
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..ba38e6271
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+# These are supported funding model platforms
+custom: https://docs.openwebgal.com/sponsor/
+patreon: WebGAL
diff --git a/.github/workflows/deploy-demo-page.yml b/.github/workflows/deploy-demo-page.yml
new file mode 100644
index 000000000..3b7162205
--- /dev/null
+++ b/.github/workflows/deploy-demo-page.yml
@@ -0,0 +1,39 @@
+name: Deploy WebGAL Demo Page
+
+on:
+ push:
+ branches:
+ - main
+
+# 任务
+jobs:
+ build-webgal-static-webpage :
+ # 服务器环境:最新版 Ubuntu
+ runs-on: ubuntu-latest
+ steps:
+ # 拉取代码
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ persist-credentials: false
+ - name: Setup Node.js environment
+ uses: actions/setup-node@v3
+ with:
+ node-version: 16
+ cache: 'yarn'
+
+ # 安装依赖
+ - name: Install
+ run: npm install yarn -g && yarn install
+
+ # 生成静态文件
+ - name: Build
+ run: yarn build && cd ./packages/webgal && touch dist/.nojekyll
+
+ # 部署到 GitHub Pages
+ - name: Deploy
+ uses: JamesIves/github-pages-deploy-action@releases/v4
+ with:
+ ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} # 也就是我们刚才生成的 secret
+ BRANCH: live-demo-page
+ FOLDER: packages/webgal/dist
diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml
new file mode 100644
index 000000000..f87b8a35d
--- /dev/null
+++ b/.github/workflows/pr-check.yml
@@ -0,0 +1,32 @@
+name: Build Check
+
+on:
+ pull_request:
+ types:
+ - opened
+ - synchronize
+
+# 任务
+jobs:
+ build-webgal-static-webpage :
+ # 服务器环境:最新版 Ubuntu
+ runs-on: ubuntu-latest
+ steps:
+ # 拉取代码
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ persist-credentials: false
+ - name: Setup Node.js environment
+ uses: actions/setup-node@v3
+ with:
+ node-version: 16
+ cache: 'yarn'
+
+ # 安装依赖
+ - name: Install
+ run: npm install yarn -g && yarn install
+
+ # 生成静态文件
+ - name: Build
+ run: yarn build && cd ./packages/webgal && touch dist/.nojekyll
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 000000000..c2fef307b
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,26 @@
+name: Release WebGAL
+
+on:
+ push:
+ tags:
+ - '*.*'
+
+jobs:
+ release:
+ name: Release
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Create Release
+ id: create_release
+ uses: actions/create-release@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ tag_name: ${{ github.ref }}
+ release_name: WebGAL ${{ github.ref }}
+ body_path: releasenote.md
+ draft: true
+ prerelease: false
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..061329161
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,30 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+.eslintcache
+stats.html
+packages/webgal/public/game.old
+packages/parser/build
+packages/parser_legacy/build
+packages/webgal/src/Core/util/pixiPerformManager/initRegister.ts
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 73f69e095..000000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/.idea/WebGAL.iml b/.idea/WebGAL.iml
deleted file mode 100644
index 0c8867d7e..000000000
--- a/.idea/WebGAL.iml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index aacebe776..000000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7f4..000000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index 5b951c88e..e833c364d 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,75 @@
+![WebGAL Slogan](https://github.com/OpenWebGAL/WebGAL/assets/30483415/ede38a39-d054-4fee-a3e9-fc5e764f358d)
+
+### **[English Version](/README_EN.md)** | **[日本語版](/README_JP.md)** | **[한국어](/README_KO.md)** | **[Français](/README_FR.md)**
+
+**[Help us with translation | 协助翻译 | 翻訳のお手伝い | 번역을 도와주세요](https://github.com/MakinoharaShoko/WebGAL/tree/dev/packages/webgal/src/translations)**
+
+**[Join Discord Server | 加入 Discord 讨论 | Discordのディスカッションに参加する](https://discord.gg/kPrQkJttJy)**
+
+
+
# WebGAL
- Create your galgame on web quickly and easily.
+
+**界面美观、功能强大、易于开发的全新网页端视觉小说引擎**
+
+# WebGAL 提供可视化编辑器
+
+**创作视觉小说,何须会编程?欢迎体验 [WebGAL 图形化编辑器](https://github.com/MakinoharaShoko/WebGAL_Terre/)**
+
+## 在线体验
+
+#### WebGAL 示例游戏,一般会演示最新开发的功能
+
+https://demo.openwebgal.com
+
+#### 完整的游戏
+
+[铃色☆记忆](http://hoshinasuzu.cn/) by 星奈组 [备用链接](http://hoshinasuzu.cc/)
+
+[Elf of Era Idols Project](https://store.steampowered.com/app/2414730/Elf_of_Era_Idols_Project/) (通过 Steam 获取)
+
+## 使用 WebGAL 制作游戏
+
+[WebGAL 开发文档](https://docs.openwebgal.com/)
+
+[下载 WebGAL 图形化编辑器](https://github.com/MakinoharaShoko/WebGAL_Terre/releases)
+
+你也可以使用源代码或 [WebGAL 调试工具](https://github.com/MakinoharaShoko/WebGAL/releases) 制作游戏,并使用 [WebGAL Script VS Code 插件](https://marketplace.visualstudio.com/items?itemName=c6h5-no2.webgal-script-basics) 来启用语法高亮
+
+## WebGAL 优势与特色
+
+一次编写,处处运行,无需网页开发基础,3 分钟即可学会所有的语法,只要你有灵感,就可以立刻开始开始创作你自己的视觉小说!
+
+### 界面美观
+
+美观优雅的图形用户界面与交互效果,一切都是为了更好的用户体验。
+
+### 功能强大
+
+不仅支持主流视觉小说引擎所具有的几乎全部功能,你还可以使用 Pixi.js 为你的游戏添加自定义效果。
+
+### 易于开发
+
+无论是使用 WebGAL 脚本还是使用可视化编辑器进行开发,一切都是那么简单自然。
+
+### 参与 WebGAL 的开发工作
+
+**想要参与引擎开发的开发者请阅读 [此项目的参与指南](https://docs.openwebgal.com/developers/)**
+
+### 赞助
+
+WebGAL 是一款开源软件,因此你可以免费在 MPL-2.0 开源协议的范畴下使用本软件,并可用于商业使用。
+
+但即便如此,你的赞助也可以给予开发者前进的动力,让这个项目变得更好。
+
+[赞助本项目](https://docs.openwebgal.com/sponsor/)
+
+# Sponsors
+
+
+
+
+
+## Stargazers over time
+
+[![Stargazers over time](https://starchart.cc/MakinoharaShoko/WebGAL.svg)](https://starchart.cc/MakinoharaShoko/WebGAL)
diff --git a/README_EN.md b/README_EN.md
new file mode 100644
index 000000000..7de3132ed
--- /dev/null
+++ b/README_EN.md
@@ -0,0 +1,73 @@
+![WebGAL Slogan](https://github.com/OpenWebGAL/WebGAL/assets/30483415/ede38a39-d054-4fee-a3e9-fc5e764f358d)
+
+**[中文版本](/README.md)**
+
+**[Help us with translation | 协助翻译 | 翻訳のお手伝い ](https://github.com/MakinoharaShoko/WebGAL/tree/dev/packages/webgal/src/translations)**
+
+**[Join Discord Server](https://discord.gg/kPrQkJttJy)**
+
+
+
+# WebGAL
+
+**A visually appealing, feature-rich, and easy-to-develop new web-based visual novel engine**
+
+# WebGAL Provides Visual Editor
+
+**Who needs to code to create visual novels? Welcome to experience [WebGAL Visual Editor](https://github.com/MakinoharaShoko/WebGAL_Terre/)**
+
+Demo video: https://www.bilibili.com/video/BV1jS4y1i7Wz/
+
+## Online Experience
+
+A short example:
+
+https://demo.openwebgal.com
+
+A complete game:
+
+[铃色☆记忆](http://hoshinasuzu.cn/) by Hoshinasuzu [备用链接](http://hoshinasuzu.cc/)
+
+### Creating Games with WebGAL
+
+[WebGAL Development Documentation](https://docs.openwebgal.com/en)
+
+[Download WebGAL Graphical Editor](https://github.com/MakinoharaShoko/WebGAL_Terre/releases)
+
+## WebGAL Advantages and Features
+
+Write once, run everywhere, no web development background needed, learn all syntax in 3 minutes, start creating your own visual novel as soon as inspiration strikes!
+
+### Visually Appealing Interface
+
+Beautiful and elegant graphical user interface and interaction effects, all for a better user experience.
+
+### Feature-Rich
+
+Supports almost all features of mainstream visual novel engines, plus you can use Pixi.js to add custom effects to your game.
+
+### Easy to Develop
+
+Whether using WebGAL scripts or the visual editor for development, everything is simple and natural.
+
+### Participate in WebGAL Development
+
+**Developers who want to participate in engine development, please read [the participation guide for this project](https://docs.openwebgal.com/en/developers/)**
+
+### Sponsorship
+
+WebGAL is an open-source software, so you can use this software for free under the scope of the MPL-2.0 open-source license, and it is available for commercial use.
+
+Even so, your sponsorship can provide motivation for the developers to move forward and make this project even better.
+
+[Sponsor this project](https://docs.openwebgal.com/en/sponsor/)
+
+# Sponsors
+
+
+
+
+
+## Stargazers over time
+
+[![Stargazers over time](https://starchart.cc/MakinoharaShoko/WebGAL.svg)](https://starchart.cc/MakinoharaShoko/WebGAL)
diff --git a/README_FR.md b/README_FR.md
new file mode 100644
index 000000000..7f741b390
--- /dev/null
+++ b/README_FR.md
@@ -0,0 +1,74 @@
+![WebGAL Slogan](https://github.com/OpenWebGAL/WebGAL/assets/30483415/ede38a39-d054-4fee-a3e9-fc5e764f358d)
+
+**[中文版本](/README.md)**
+**[English](/README_EN.md)**
+
+**[Aidez-nous avec la traduction | 协助翻译 | 翻訳のお手伝い](https://github.com/MakinoharaShoko/WebGAL/tree/dev/packages/webgal/src/translations)**
+
+**[Rejoindre le serveur Discord](https://discord.gg/kPrQkJttJy)**
+
+
+
+# WebGAL
+
+**Un moteur de visual novel basé sur le web, attrayant visuellement, riche en fonctionnalités et facile à développer**
+
+# WebGAL propose un éditeur visuel
+
+**Qui a besoin de savoir coder pour créer un visual novel ? Découvrez [l'éditeur graphique WebGAL](https://github.com/MakinoharaShoko/WebGAL_Terre/)**
+
+Vidéo de démonstration : https://www.bilibili.com/video/BV1jS4y1i7Wz/
+
+## Expérience en ligne
+
+Un exemple court :
+
+https://demo.openwebgal.com
+
+Un jeu complet :
+
+[铃色☆记忆](http://hoshinasuzu.cn/) de Hoshinasuzu [Lien de secours](http://hoshinasuzu.cc/)
+
+### Créer des jeux avec WebGAL
+
+[Documentation de développement WebGAL](https://docs.openwebgal.com/)
+
+[Télécharger l'éditeur graphique WebGAL](https://github.com/MakinoharaShoko/WebGAL_Terre/releases)
+
+## Avantages et fonctionnalités de WebGAL
+
+Écrivez une fois, exécutez partout, aucune expérience en développement web requise, apprenez toutes les syntaxes en 3 minutes, commencez à créer votre propre visual novel dès que l'inspiration vous frappe !
+
+### Interface visuellement attrayante
+
+Interface utilisateur graphique belle et élégante avec des effets d'interaction, le tout pour une meilleure expérience utilisateur.
+
+### Riche en fonctionnalités
+
+Prend en charge presque toutes les fonctionnalités des moteurs de visual novel populaires, et vous pouvez utiliser Pixi.js pour ajouter des effets personnalisés à votre jeu.
+
+### Facile à développer
+
+Que vous utilisiez les scripts WebGAL ou l'éditeur visuel pour le développement, tout est simple et naturel.
+
+### Participer au développement de WebGAL
+
+**Les développeurs souhaitant participer au développement du moteur, veuillez lire [le guide de participation pour ce projet](https://docs.openwebgal.com/developers/)**
+
+### Soutenir
+
+WebGAL est un logiciel open-source, vous pouvez donc utiliser ce logiciel gratuitement dans le cadre de la licence open-source MPL-2.0, et il est disponible pour une utilisation commerciale.
+
+Cependant, votre soutien peut motiver les développeurs à avancer et à rendre ce projet encore meilleur.
+
+[Soutenir ce projet](https://docs.openwebgal.com/sponsor/)
+
+# Sponsors
+
+
+
+
+
+## Pour les amateurs de Stargazers (Historique GitHub stars)
+
+[![Stargazers au fil du temps](https://starchart.cc/MakinoharaShoko/WebGAL.svg)](https://starchart.cc/MakinoharaShoko/WebGAL)
diff --git a/README_JP.md b/README_JP.md
new file mode 100644
index 000000000..414146719
--- /dev/null
+++ b/README_JP.md
@@ -0,0 +1,66 @@
+![WebGAL Slogan](https://github.com/OpenWebGAL/WebGAL/assets/30483415/ede38a39-d054-4fee-a3e9-fc5e764f358d)
+
+**[中文版本](/README.md)**
+
+**[Help us with translation | 协助翻译 | 翻訳のお手伝い ](https://github.com/MakinoharaShoko/WebGAL/tree/dev/packages/webgal/src/translations)**
+
+**[Join Discord Server](https://discord.gg/kPrQkJttJy)**
+
+
+
+# WebGALとは
+
+**WebGALは、Webベースのビジュアルノベルエンジンです。魅力的な機能が豊富で、ビジュアルノベルゲームの開発が簡単にできます。**
+
+# WebGAL はビジュアルエディターを提供します
+
+**ビジュアルノベルを作るのにプログラミングは必要ですか? [WebGAL グラフィカルエディター](https://github.com/MakinoharaShoko/WebGAL_Terre/) を体験してください**
+
+デモビデオ: https://youtu.be/S7xxVe9MGXk
+
+## ゲーム紹介
+
+デモゲーム(一部AI画像を使用しています):
+
+https://webgal-jp-demo.onrender.com/
+
+現在公開されているゲーム(中国語):
+
+[ベルカラー☆メモリー](http://hoshinasuzu.cn/suzu.html) by Hoshinasuzu [代替リンク](http://hoshinasuzu.cc/)
+
+### WebGALでゲームを作成
+
+[WebGAL 開発ドキュメント](https://docs.openwebgal.com/ja)
+※日本語の開発ドキュメントは準備中です
+
+[WebGAL Webエディターのダウンロードはこちら](https://github.com/MakinoharaShoko/WebGAL_Terre/releases)
+
+## WebGALの魅力と機能
+
+WebGALは、プログラミング知識が不要で、簡単にビジュアルノベルゲームを作ることができます。
+ 作成したゲームをウェブサイトに公開すると、プレイヤーは、パソコンやスマホからいつでもどこでもゲームをプレイすることができます。
+ ウェブサイトだけではなく、Windowsで実行できるファイルとして出力することも可能です。
+
+主流のビジュアルノベルエンジンのほぼすべての機能をサポートしており、アニメーションや特殊効果を使用して、ゲームのエフェクトをカスタマイズすることも可能です。
+
+### WebGALのエンジン開発に参加(オープンソースプロジェクトに参加)
+
+**エンジン開発に参加したい開発者は、[このプロジェクトの参加ガイド](https://docs.openwebgal.com/ja/developers/)をお読みください**
+ 上記のDiscordに参加すると多くの情報を得ることができます。
+
+### スポンサーシップ
+
+WebGAL はオープンソース ソフトウェアであるため、MPL-2.0 オープンソース ライセンスの範囲内で無料で使用でき、商用利用も可能です。
+ スポンサーシップは、開発者が前進し、このプロジェクトをさらに改善する動機となることができます。
+
+[このプロジェクトのスポンサー](https://docs.openwebgal.com/ja/sponsor/)
+
+# Sponsors
+
+
+
+
+
+## Stargazers over time
+
+[![Stargazers over time](https://starchart.cc/MakinoharaShoko/WebGAL.svg)](https://starchart.cc/MakinoharaShoko/WebGAL)
diff --git a/README_KO.md b/README_KO.md
new file mode 100644
index 000000000..5b5c06921
--- /dev/null
+++ b/README_KO.md
@@ -0,0 +1,73 @@
+![WebGAL Slogan](https://github.com/OpenWebGAL/WebGAL/assets/30483415/ede38a39-d054-4fee-a3e9-fc5e764f358d)
+
+**[中文版本](/README.md)**
+
+**[번역을 도와주세요 | 翻译协助 | 翻訳のお手伝い ](https://github.com/MakinoharaShoko/WebGAL/tree/dev/packages/webgal/src/translations)**
+
+**[Discord 서버 참가하기](https://discord.gg/kPrQkJttJy)**
+
+
+
+# WebGAL
+
+**시각적으로 매력적이며, 기능이 풍부하고, 쉽게 개발할 수 있는 새로운 웹 기반 비주얼 노벨 엔진**
+
+# WebGAL 시각화 에디터 제공
+
+**비주얼 노벨 제작, 꼭 프로그래밍을 알아야 할까요? [WebGAL 그래픽 에디터](https://github.com/MakinoharaShoko/WebGAL_Terre/)를 체험해 보세요!**
+
+데모 비디오: https://www.bilibili.com/video/BV1jS4y1i7Wz/
+
+## 온라인 체험
+
+짧은 예시:
+
+https://demo.openwebgal.com
+
+완성된 게임:
+
+[铃色☆记忆](http://hoshinasuzu.cn/) by Hoshinasuzu [백업 링크](http://hoshinasuzu.cc/)
+
+### WebGAL로 게임 만들기
+
+[WebGAL 개발 문서](https://docs.openwebgal.com/)
+
+[WebGAL 그래픽 에디터 다운로드](https://github.com/MakinoharaShoko/WebGAL_Terre/releases)
+
+## WebGAL의 장점 및 특징
+
+한 번 작성하면 어디서든 실행할 수 있으며, 웹 개발 배경이 필요 없으며, 모든 구문을 3분 만에 배울 수 있고, 영감이 떠오르자마자 자신만의 비주얼 노벨을 만들기 시작할 수 있습니다!
+
+### 시각적으로 매력적인 인터페이스
+
+아름답고 우아한 그래픽 사용자 인터페이스와 상호작용 효과, 모두가 더 나은 사용자 경험을 위한 것입니다.
+
+### 특징이 풍부함
+
+주요 비주얼 노벨 엔진의 거의 모든 기능을 지원하며, 게임에 사용자 정의 효과를 추가하기 위해 Pixi.js를 사용할 수 있습니다.
+
+### 개발하기 쉬움
+
+WebGAL 스크립트를 사용하든 비주얼 에디터를 사용하든 개발은 모두 간단하고 자연스럽습니다.
+
+### WebGAL 개발에 참여하기
+
+**엔진 개발에 참여하고 싶은 개발자는 [이 프로젝트에 참여하기 위한 안내서](https://docs.openwebgal.com/developers/)를 읽어주세요.**
+
+### 후원
+
+WebGAL은 오픈소스 소프트웨어이므로 MPL-2.0 오픈소스 라이센스의 범위 내에서 이 소프트웨어를 무료로 사용할 수 있으며, 상업적으로 사용할 수 있습니다.
+
+그럼에도 불구하고, 귀하의 후원은 개발자들에게 동기를 부여하고 이 프로젝트를 더욱 발전시키는 데 도움이 될 수 있습니다.
+
+[이 프로젝트 후원하기](https://docs.openwebgal.com/sponsor/)
+
+# 후원자
+
+
+
+
+
+## 시간에 따른 사용자 수
+
+[![시간에 따른 사용자 수](https://starchart.cc/MakinoharaShoko/WebGAL.svg)](https://starchart.cc/MakinoharaShoko/WebGAL)
diff --git "a/dev-docs/ko/\352\265\254\353\254\270 \353\266\204\354\204\235\354\235\230 \354\230\210.md" "b/dev-docs/ko/\352\265\254\353\254\270 \353\266\204\354\204\235\354\235\230 \354\230\210.md"
new file mode 100644
index 000000000..7cfa78603
--- /dev/null
+++ "b/dev-docs/ko/\352\265\254\353\254\270 \353\266\204\354\204\235\354\235\230 \354\230\210.md"
@@ -0,0 +1,26 @@
+```
+WebGAL:이것은 테스트 문장입니다. -v1.ogg ;
+해석 결과=> {
+ command: commandType.say, //문장 유형
+ content: '이것은 테스트 문장입니다.', //문장 내용
+ args: [
+ {
+ key: 'vocal', //매개변수 키는 음성
+ value: './vocal/v1.ogg' //매개변수 값은 음성 파일 경로( assetSetter에 의해 파싱됨)
+ },
+ {
+ key: 'speaker', //매개변수 키는 화자
+ value: 'WebGAL' //매개변수 값은 화자의 이름
+ }
+ ], //매개변수 목록
+ sentenceAssets: [
+ {
+ name: 'v1.ogg', //자원 이름
+ type: assetType.audio, //자원 유형
+ url: './vocal/v1.ogg', //자원 url
+ lineNumber: 1, //리소스 문장의 행 번호를 트리거
+ }
+ ], // 문장에 포함된 리소스 목록
+ subScene: '' //문장에 포함된 하위 장면
+}
+```
diff --git "a/dev-docs/ko/\354\225\240\353\213\210\353\251\224\354\235\264\354\205\230 \353\260\217 \353\263\200\355\231\230 \354\213\234\354\212\244\355\205\234 \354\204\244\352\263\204.md" "b/dev-docs/ko/\354\225\240\353\213\210\353\251\224\354\235\264\354\205\230 \353\260\217 \353\263\200\355\231\230 \354\213\234\354\212\244\355\205\234 \354\204\244\352\263\204.md"
new file mode 100644
index 000000000..19c32e1ee
--- /dev/null
+++ "b/dev-docs/ko/\354\225\240\353\213\210\353\251\224\354\235\264\354\205\230 \353\260\217 \353\263\200\355\231\230 \354\213\234\354\212\244\355\205\234 \354\204\244\352\263\204.md"
@@ -0,0 +1,21 @@
+## 애니메이션 시스템 설계
+
+WebGAL의 새 버전에서 `애니메이션`은 `PIXI.Ticker` 콜백 함수가 될 것입니다. 반면 `변형`은 특별한 애니메이션으로, 실행 시간이 0ms (애니메이션의 첫 프레임에서 스테이지 객체를 최종 상태로 설정)입니다.
+
+게다가, 모든 스테이지 상태가 애니메이션 관련 퍼포먼스를 포함하지 않기 때문에 애니메이션이 끝난 후에도 스테이지 객체가 애니메이션 종료 후 설정된 위치(또는 적용된 효과)를 유지하도록 "내부 변형"이 필요합니다.
+
+"내부 변형"은 지정된 key의 스테이지 객체에 적용할 효과의 연속된 기록입니다. "내부 변형"은 일반적으로 스테이지 객체의 애니메이션이 끝난 후 또는 스테이지 객체가 변경될 때 호출됩니다.
+
+"내부 변형"이 스테이지 객체의 애니메이션 정상 작동에 영향을 주지 않도록 하기 위해, WebGAL 스테이지 컨트롤러는 애니메이션 중인 스테이지 객체의 key를 기록할 수 있습니다. 이러한 key는 잠금되어 변형을 적용할 수 없습니다. 애니메이션이 끝날 때까지 잠금이 해제되지 않습니다.
+
+모든 애니메이션이 끝난 후, 애니메이션 실행 프로그램은 작동하는 스테이지 객체의 "최종 상태"를 해당 key의 스테이지 객체의 "내부 변형" 기록으로 업데이트합니다.
+
+## 다른 유형의 애니메이션
+
+WebGAL에서 스테이지 객체에 적용할 수 있는 애니메이션은 즉시 애니메이션과 종료 애니메이션으로 나눌 수 있습니다.
+
+즉시 애니메이션은 스크립트가 호출되는 즉시 트리거됩니다. 애니메이션이 설정되지 않은 경우 스테이지 객체는 페이드인이라는 기본값을 가지므로, 사용자 정의 페이드인 애니메이션을 원한다면 캐릭터/배경을 바꾼 후 즉시 사용자 정의 애니메이션을 실행하면 됩니다.
+
+반면에 종료 애니메이션은 캐릭터/배경이 제거될 때 적용됩니다. 기본적으로 페이드아웃이 적용되는데, 종료 애니메이션을 적용하려면 `-off` 접미사를 가진 종료 스테이지 객체에 즉시 애니메이션을 설정하는 것이 좋습니다.
+
+따라서, 입장이나 퇴장 애니메이션은 setFigure 또는 setBg가 호출된 후에 적용해야 합니다. 이 명령어가 캐릭터를 추가하거나 삭제하기 위해 사용되는 경우에도 마찬가지입니다.
diff --git "a/dev-docs/ko/\354\235\270\355\204\260\355\216\230\354\235\264\354\212\244 \352\263\204\354\270\265 \352\265\254\354\241\260.md" "b/dev-docs/ko/\354\235\270\355\204\260\355\216\230\354\235\264\354\212\244 \352\263\204\354\270\265 \352\265\254\354\241\260.md"
new file mode 100644
index 000000000..ee9b71405
--- /dev/null
+++ "b/dev-docs/ko/\354\235\270\355\204\260\355\216\230\354\235\264\354\212\244 \352\263\204\354\270\265 \352\265\254\354\241\260.md"
@@ -0,0 +1,26 @@
+# 인터페이스 계층 구조(초안)
+
+```
+z-index: 컴포넌트
+ 0: 예비
+ 1: 스테이지 배경
+ 2: 예비
+ 3: pixi 퍼포먼스
+ 4: 캐릭터 일러스트
+ 5: pixi 퍼포먼스의 또 다른 선택적 계층
+ 6: 텍스트 박스
+ 7: 예비
+ 8: 선택 및 트리거 스테이지 상호작용
+ 9: 텍스트 박스 아래의 control Panel
+10: control Panel에서 트리거되는 UI (예: 빠른 저장/불러오기)
+11: 비디오 및 전체 화면 퍼포먼스
+12: 전체 화면 클릭
+13: Title 계층, 전체 화면 클릭의 퍼포먼스 계층을 덮는다
+14: Title 계층에서 트리거되는 UI
+15: 예비
+16: Menu 계층
+17: Menu 계층에서 트리거되는 UI
+18: 예비
+19: 게임 엔진 진입 애니메이션
+20: 전역 대화 상자
+```
diff --git "a/dev-docs/ko/\355\215\274\355\217\254\353\250\274\354\212\244 \355\230\270\354\266\234 \354\247\200\354\271\250.md" "b/dev-docs/ko/\355\215\274\355\217\254\353\250\274\354\212\244 \355\230\270\354\266\234 \354\247\200\354\271\250.md"
new file mode 100644
index 000000000..1621d2f84
--- /dev/null
+++ "b/dev-docs/ko/\355\215\274\355\217\254\353\250\274\354\212\244 \355\230\270\354\266\234 \354\247\200\354\271\250.md"
@@ -0,0 +1,31 @@
+## 퍼포먼스 호출 지침
+
+첫 번째 단계, 문장 호출기 `runScript.ts`는 문장 유형에 따라 문장을 실행하며, 문장은 IPerform 객체를 반환하고 현재 문장의 퍼포먼스를 장면 상태에 기록합니다.
+
+두 번째 단계, 문장 호출기 `runScript.ts`는 객체를 종료하고 언로드하기 위한 자동 타이머를 설정합니다.
+
+```typescript
+export interface IPerform {
+ performName: string, // 퍼포먼스 이름, 이후에 수동으로 퍼포먼스를 제거하기 위해 사용, 표시가 없다면 이는 지속 퍼포먼스가 아니며 랜덤 문자열을 부여받습니다.
+ duration: number, // 지속 시간, 단위는 ms, 지속 시간 후에 이 퍼포먼스를 "이미 끝난" 상태로 강제 설정합니다.
+ isOver: boolean, // 퍼포먼스가 이미 끝났는지 여부
+ isHoldOn: boolean, // 퍼포먼스가 유지 타입의 퍼포먼스인지 여부
+ stopFunction: Function, // 퍼포먼스를 언로드하는 함수, 이 함수는 반드시 퍼포먼스를 언로드할 필요는 없지만, 퍼포먼스가 끝날 때 기본적으로 호출됩니다.
+ blockingNext: Function // 퍼포먼스가 게임 프로세스를 차단하는지 여부 (함수, boolean 타입의 결과를 반환, 차단할지 여부를 판단)
+ blockingAuto: Function // 퍼포먼스가 자동 모드를 차단하는지 여부 (함수, boolean 타입의 결과를 반환, 차단할지 여부를 판단)
+}
+```
+
+세 번째 단계, 퍼포먼스 시간이 도달하면 퍼포먼스를 언로드합니다(문장 호출기는 지정된 시간에 자동으로 언로드됩니다), 퍼포먼스 목록에 퍼포먼스가 없을 때까지(유지 퍼포먼스는 언로드되지 않습니다)
+
+**참고: `isOver`가 `true`인 경우 auto와 next를 차단하지 않으며, 이때는 `blockingNext`와 `blockingAuto`를 확인하지 않습니다**
+
+퍼포먼스 중에 auto 호출이 발생할 때: auto 함수는 퍼포먼스 목록을 확인하고, auto를 차단하지 않는 퍼포먼스만 존재하면 auto를 호출합니다.
+
+퍼포먼스 중에 next 호출이 발생할 때: next 함수는 모든 중단 가능한 퍼포먼스를 종료하고 퍼포먼스를 언로드합니다. 모든 비 `HoldOn` 타이머를 언로드합니다. next를 차단하는 퍼포먼스가 있으면 next는 반응하지 않습니다.
+
+자동 재생(auto) 모드에서 next가 트리거 될 때(사용자가 마우스를 클릭)
+
+1. next를 차단하는 퍼포먼스가 있는지 확인하고, 그렇지 않으면 응답하지 않습니다
+
+2. 차단되지 않았다면, 모든 퍼포먼스를 중단하고 auto를 중단합니다
diff --git "a/dev-docs/\345\212\250\347\224\273\344\270\216\345\217\230\346\215\242\347\263\273\347\273\237\350\256\276\350\256\241.md" "b/dev-docs/\345\212\250\347\224\273\344\270\216\345\217\230\346\215\242\347\263\273\347\273\237\350\256\276\350\256\241.md"
new file mode 100644
index 000000000..7f7dda914
--- /dev/null
+++ "b/dev-docs/\345\212\250\347\224\273\344\270\216\345\217\230\346\215\242\347\263\273\347\273\237\350\256\276\350\256\241.md"
@@ -0,0 +1,21 @@
+## 动画系统设计
+
+在 WebGAL 新版本中,`动画`将会是一个 `PIXI.Ticker` 回调函数。而`变换`则是一个特殊的动画,只不过执行时间是0ms(在动画的第一帧就将舞台对象设置为终态状态)。
+
+除此以外,因为并不是每一个舞台状态都包含动画相关的演出,所以为了在动画结束后也要保持舞台对象处于动画结束后被设置的位置(或应用的效果),还会有一个“内部变换”。
+
+“内部变换”是一连串对指定 key 的舞台对象要应用的效果的记录。“内部变换”调用的时机一般是任何舞台对象动画结束后或舞台对象发生改变时。
+
+为了防止“内部变换”影响舞台对象上动画的正常运行,WebGAL 舞台控制器有一个记录,可以记录下正在进行动画的舞台对象的 key,这些key会被锁定,无法应用变换。直到动画结束后,才会被解锁。
+
+在任何动画结束后,动画执行程序都会将其作用的舞台对象的“终态”更新到对应的 key 的舞台对象的“内部变换”记录上。
+
+## 不同类型的动画
+
+在 WebGAL 中,可以作用在舞台对象上的动画,可以分为即时动画和退出动画。
+
+即时动画会在脚本被调用的时候被立即触发。由于在没有任何动画被设置的情况下,舞台对象会预设一个渐入,所以如果想要自定义渐入动画,只需要在切换立绘/背景后立刻执行一个自定义的即时动画就可以了。
+
+而退出动画将会在立绘/背景被移除时作用。默认的退出动画是一个渐出。如果想要应用一个退出动画,一个比较好的方式是在立绘退出时(在切换或关闭立绘时)为带有后缀 `-off` 的退出舞台对象设置一个即时动画。
+
+所以,无论是进入还是退出动画,都应在 setFigure 或 setBg 后应用,无论这个命令是为了添加还是删除一个立绘。
diff --git "a/dev-docs/\346\274\224\345\207\272\350\260\203\347\224\250\350\257\264\346\230\216.md" "b/dev-docs/\346\274\224\345\207\272\350\260\203\347\224\250\350\257\264\346\230\216.md"
new file mode 100644
index 000000000..9c9f8f0d9
--- /dev/null
+++ "b/dev-docs/\346\274\224\345\207\272\350\260\203\347\224\250\350\257\264\346\230\216.md"
@@ -0,0 +1,31 @@
+## 演出调用说明
+
+第一步,语句调用器 `runScript.ts` 根据语句类型执行一个语句,语句会返回一个IPerform对象,并将当前语句的演出写入到场景状态。
+
+第二步,语句调用器 `runScript.ts` 会设定一个自动的计时器,用于结束并卸载对象。
+
+```typescript
+export interface IPerform {
+ performName: string,// 演出名称,用于在后面手动清除演出,如果没有标识,则代表不是保持演出,给予一个随机字符串
+ duration: number, // 持续时间,单位为ms,持续时间到后强制设置该演出为“已经结束”状态
+ isOver: boolean, //演出是否已经结束
+ isHoldOn: boolean, //演出是不是一个保持类型的演出
+ stopFunction: Function, // 卸载演出的函数,这个函数不一定要真的卸载演出,只是在演出结束时会默认调用。
+ blockingNext: Function // 演出是否阻塞游戏流程继续(一个函数,返回 boolean类型的结果,判断要不要阻塞)
+ blockingAuto: Function //演出是否阻塞自动模式(一个函数,返回 boolean类型的结果,判断要不要阻塞)
+}
+```
+
+第三步,在演出时间抵达时,卸载演出(语句调用器会在指定时间自动卸载),直到演出列表里没有演出(保持演出则不会卸载)
+
+**注意:`isOver` 为 `true` 时,不会阻塞auto和next,这时候不检查 `blockingNext` 与 `blockingAuto`**
+
+当在演出时发生auto调用: auto函数会检查演出列表,如果只存在不阻塞auto的演出,就调用auto。
+
+当在演出时发生next调用: next函数会结束所有可打断演出,卸载演出。 卸载所有非 `HoldOn` 计时器 如果存在阻塞next的演出,next不会响应。
+
+当在自动播放(auto)模式下触发next(用户点击鼠标)
+
+1.检查是否存在阻塞next的演出,否则不响应
+
+2.如果没有被阻塞,那么就停止所有演出,并停止auto
diff --git "a/dev-docs/\347\225\214\351\235\242\345\261\202\347\272\247\345\210\222\345\210\206.md" "b/dev-docs/\347\225\214\351\235\242\345\261\202\347\272\247\345\210\222\345\210\206.md"
new file mode 100644
index 000000000..8fd7d8cf6
--- /dev/null
+++ "b/dev-docs/\347\225\214\351\235\242\345\261\202\347\272\247\345\210\222\345\210\206.md"
@@ -0,0 +1,26 @@
+# 界面层级划分(草案)
+
+```
+z-index:组件
+ 0:备用
+ 1:舞台背景
+ 2:备用
+ 3:pixi 演出
+ 4:人物立绘
+ 5:pixi 演出的另一个可选层
+ 6:文本框
+ 7:备用
+ 8:选项与触发的舞台交互
+ 9:文本框下面的control Panel
+10:control Panel 触发的UI(比如快速存读档)
+11:视频与全屏演出
+12:全屏点击
+13:Title 层,覆盖全屏点击的演出层
+14:Title 层触发的UI
+15:备用
+16:Menu层
+17:Menu层触发的UI
+18:备用
+19:游戏引擎进入动画
+20:全局对话框
+```
diff --git "a/dev-docs/\350\257\255\345\217\245\350\247\243\346\236\220\347\244\272\344\276\213.md" "b/dev-docs/\350\257\255\345\217\245\350\247\243\346\236\220\347\244\272\344\276\213.md"
new file mode 100644
index 000000000..20be5689c
--- /dev/null
+++ "b/dev-docs/\350\257\255\345\217\245\350\247\243\346\236\220\347\244\272\344\276\213.md"
@@ -0,0 +1,26 @@
+```
+WebGAL:这是一条测试语句。 -v1.ogg ;
+解析为=> {
+ command: commandType.say, //语句类型
+ content: '这是一条测试语句。', //语句内容
+ args: [
+ {
+ key: 'vocal', //参数键是语音
+ value: './vocal/v1.ogg' //参数值是语音文件路径(由assetSetter 解析)
+ },
+ {
+ key: 'speaker', //参数键是说话人
+ value: 'WebGAL' //参数值是说话人名称
+ }
+ ], //参数列表
+ sentenceAssets: [
+ {
+ name: 'v1.ogg', //资源名称
+ type: assetType.audio, //资源类型
+ url: './vocal/v1.ogg', //资源url
+ lineNumber: 1, //触发资源语句的行号
+ }
+ ], // 语句携带的资源列表
+ subScene: '' // 语句包含子场景
+}
+```
\ No newline at end of file
diff --git a/game/scene/start.sce b/game/scene/start.sce
deleted file mode 100644
index 8164c2a96..000000000
--- a/game/scene/start.sce
+++ /dev/null
@@ -1,2 +0,0 @@
-A:真是安静啊
-A:街上什么人也没有
\ No newline at end of file
diff --git a/index.html b/index.html
deleted file mode 100644
index 917800619..000000000
--- a/index.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
- INDEX
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..9afd3456d
--- /dev/null
+++ b/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "@webgal/base",
+ "version": "4.5",
+ "description": "A brand new web Visual Novel engine.",
+ "repository": "https://github.com/MakinoharaShoko/WebGAL.git",
+ "author": "Mahiru ",
+ "scripts": {
+ "build": "yarn parser:build && yarn webgal:build",
+ "build-ci": "yarn parser:build-ci && yarn webgal:build",
+ "dev": "yarn parser:build && yarn webgal:dev",
+ "webgal:dev": "cd packages/webgal && yarn dev",
+ "webgal:build": "cd packages/webgal && yarn build",
+ "parser:test": "cd packages/parser && yarn test",
+ "parser:test-coverage": "cd packages/parser && yarn coverage",
+ "parser:build": "cd packages/parser && yarn build",
+ "parser:build-ci": "cd packages/parser && yarn build-ci"
+ },
+ "license": "MPL-2.0",
+ "workspaces": {
+ "packages": [
+ "packages/*"
+ ]
+ },
+ "private": true
+}
diff --git a/packages/parser/.babelrc b/packages/parser/.babelrc
new file mode 100644
index 000000000..cc4476abb
--- /dev/null
+++ b/packages/parser/.babelrc
@@ -0,0 +1,14 @@
+{
+ "presets": [
+ [
+ "@babel/preset-env",
+ {
+ "useBuiltIns": "entry",
+ "corejs": "3.6.4",
+ "modules": false
+ },
+ "@babel/preset-typescript"
+ ]
+ ],
+ "exclude": "node_modules/**"
+}
diff --git a/packages/parser/.gitignore b/packages/parser/.gitignore
new file mode 100644
index 000000000..549164b2f
--- /dev/null
+++ b/packages/parser/.gitignore
@@ -0,0 +1,4 @@
+coverage
+node_modules
+.idea
+.vscode
diff --git a/packages/parser/.npmignore b/packages/parser/.npmignore
new file mode 100644
index 000000000..7bcd9b489
--- /dev/null
+++ b/packages/parser/.npmignore
@@ -0,0 +1,6 @@
+coverage
+test
+.babelrc
+rollup.config.js
+tsconfig.json
+tsconfig.node.json
diff --git a/packages/parser/.prettierrc b/packages/parser/.prettierrc
new file mode 100644
index 000000000..33f6d8eaf
--- /dev/null
+++ b/packages/parser/.prettierrc
@@ -0,0 +1,19 @@
+{
+ "printWidth": 80,
+ "tabWidth": 2,
+ "useTabs": false,
+ "semi": true,
+ "singleQuote": true,
+ "quoteProps": "as-needed",
+ "jsxSingleQuote": false,
+ "trailingComma": "all",
+ "bracketSpacing": true,
+ "bracketSameLine": false,
+ "arrowParens": "always",
+ "requirePragma": false,
+ "insertPragma": false,
+ "proseWrap": "preserve",
+ "htmlWhitespaceSensitivity": "css",
+ "endOfLine": "lf",
+ "embeddedLanguageFormatting": "auto"
+}
diff --git a/packages/parser/LICENSE b/packages/parser/LICENSE
new file mode 100644
index 000000000..a612ad981
--- /dev/null
+++ b/packages/parser/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/packages/parser/README.md b/packages/parser/README.md
new file mode 100644
index 000000000..f6348d365
--- /dev/null
+++ b/packages/parser/README.md
@@ -0,0 +1,29 @@
+# WebGAL Parser: The Next Generation
+
+## Development
+
+### Run all tests
+
+```sh
+yarn test
+```
+
+### Run a specific test
+
+```sh
+yarn test -t TEST_NAME
+```
+
+### Trace the parsing process
+
+Since this print a verbose test tree, we typically use for a specific test:
+
+```sh
+yarn test-trace -t TEST_NAME
+```
+
+Or, if all tests are needed:
+
+```sh
+yarn test-trace
+```
diff --git a/packages/parser/package.json b/packages/parser/package.json
new file mode 100644
index 000000000..a2f098eb1
--- /dev/null
+++ b/packages/parser/package.json
@@ -0,0 +1,51 @@
+{
+ "name": "webgal-parser",
+ "version": "4.5.10-beta.2",
+ "description": "WebGAL script parser",
+ "scripts": {
+ "test": "yarn parser && vitest",
+ "test-trace": "yarn parser --trace && vitest",
+ "coverage": "vitest run --coverage",
+ "build": "rimraf -rf ./build && rollup --config",
+ "build-ci": "yarn parser && rollup --config",
+ "debug": "tsx test/debug.ts",
+ "dev": "tsx src/index.ts",
+ "parser": "npx peggy -o src/parser.js --format es src/parser.pegjs -m"
+ },
+ "types": "./build/types/index.d.ts",
+ "module": "./build/es/index.js",
+ "main": "./build/cjs/index.js",
+ "author": "tinyAdapter ",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "lodash": "^4.17.21",
+ "tsx": "^3.12.7"
+ },
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^23.0.2",
+ "@rollup/plugin-json": "^5.0.1",
+ "@rollup/plugin-node-resolve": "^15.0.1",
+ "@rollup/plugin-typescript": "^9.0.2",
+ "@types/lodash": "^4.14.189",
+ "@types/node": "^18.14.0",
+ "@typescript-eslint/eslint-plugin": "^5.18.0",
+ "@typescript-eslint/parser": "^5.18.0",
+ "@vitest/coverage-c8": "^0.28.5",
+ "eslint": "^8.13.0",
+ "eslint-config-alloy": "^4.5.1",
+ "eslint-config-prettier": "^8.5.0",
+ "eslint-plugin-prettier": "^4.2.1",
+ "eslint-plugin-react": "^7.29.4",
+ "lint-staged": "^12.3.7",
+ "prettier": "^2.6.2",
+ "rollup": "^3.3.0",
+ "rollup-plugin-babel": "^4.4.0",
+ "rollup-plugin-terser": "^7.0.2",
+ "rollup-plugin-typescript2": "^0.34.1",
+ "ts-node": "^10.9.1",
+ "tslib": "^2.4.1",
+ "typescript": "^4.9.3",
+ "vitest": "^0.28.5"
+ },
+ "type": "module"
+}
diff --git a/packages/parser/rollup.config.js b/packages/parser/rollup.config.js
new file mode 100644
index 000000000..787b18bc7
--- /dev/null
+++ b/packages/parser/rollup.config.js
@@ -0,0 +1,69 @@
+import typescript from "rollup-plugin-typescript2";
+import resolve from "@rollup/plugin-node-resolve";
+import commonjs from "@rollup/plugin-commonjs";
+
+const mode = process.env.MODE ?? 'prod';
+const isProd = mode === "prod";
+
+export default [
+ {
+ input: `./src/index.ts`,
+ output:
+ {
+ file: "./build/es/index.js",
+ format: "es",
+ sourcemap: !isProd
+ },
+ plugins: [
+ resolve(), commonjs(), typescript({
+ useTsconfigDeclarationDir: true,
+ tsconfigOverride: {
+ compilerOptions: {
+ sourceMap: !isProd,
+ declarationDir: "build/es"
+ }, include: ["src"]
+ }
+ })]
+ }, {
+ input: `./src/index.ts`,
+ output: [
+ {
+ file: "./build/cjs/index.cjs",
+ exports: "named",
+ format: "cjs",
+ sourcemap: !isProd
+ },
+ ],
+ plugins: [
+ resolve(), commonjs(), typescript({
+ useTsconfigDeclarationDir: true,
+ tsconfigOverride: {
+ compilerOptions: {
+ sourceMap: !isProd,
+ declarationDir: "build/cjs"
+ }, include: ["src"]
+ }
+ })],
+ },
+ {
+ input: `./src/index.ts`,
+ output: [
+ {
+ file: "./build/umd/index.global.js",
+ name: 'webgalParser',
+ format: 'iife',
+ sourcemap: !isProd
+ },
+ ],
+ plugins: [
+ resolve(), commonjs(), typescript({
+ useTsconfigDeclarationDir: true,
+ tsconfigOverride: {
+ compilerOptions: {
+ sourceMap: !isProd,
+ declarationDir: "build/types"
+ }, include: ["src"]
+ }
+ })],
+ }
+];
diff --git a/packages/parser/src/config/scriptConfig.ts b/packages/parser/src/config/scriptConfig.ts
new file mode 100644
index 000000000..c8463fd7b
--- /dev/null
+++ b/packages/parser/src/config/scriptConfig.ts
@@ -0,0 +1,50 @@
+import {commandType} from '../interface/sceneInterface';
+
+export const SCRIPT_CONFIG = [
+ { scriptString: 'intro', scriptType: commandType.intro },
+ { scriptString: 'changeBg', scriptType: commandType.changeBg },
+ { scriptString: 'changeFigure', scriptType: commandType.changeFigure },
+ { scriptString: 'miniAvatar', scriptType: commandType.miniAvatar },
+ { scriptString: 'changeScene', scriptType: commandType.changeScene },
+ { scriptString: 'choose', scriptType: commandType.choose },
+ { scriptString: 'end', scriptType: commandType.end },
+ { scriptString: 'bgm', scriptType: commandType.bgm },
+ { scriptString: 'playVideo', scriptType: commandType.video },
+ {
+ scriptString: 'setComplexAnimation',
+ scriptType: commandType.setComplexAnimation,
+ },
+ { scriptString: 'setFilter', scriptType: commandType.setFilter },
+ { scriptString: 'pixiInit', scriptType: commandType.pixiInit },
+ { scriptString: 'pixiPerform', scriptType: commandType.pixi },
+ { scriptString: 'label', scriptType: commandType.label },
+ { scriptString: 'jumpLabel', scriptType: commandType.jumpLabel },
+ { scriptString: 'setVar', scriptType: commandType.setVar },
+ { scriptString: 'callScene', scriptType: commandType.callScene },
+ { scriptString: 'showVars', scriptType: commandType.showVars },
+ { scriptString: 'unlockCg', scriptType: commandType.unlockCg },
+ { scriptString: 'unlockBgm', scriptType: commandType.unlockBgm },
+ { scriptString: 'say', scriptType: commandType.say },
+ { scriptString: 'filmMode', scriptType: commandType.filmMode },
+ { scriptString: 'callScene', scriptType: commandType.callScene },
+ { scriptString: 'setTextbox', scriptType: commandType.setTextbox },
+ { scriptString: 'setAnimation', scriptType: commandType.setAnimation },
+ { scriptString: 'playEffect', scriptType: commandType.playEffect },
+ { scriptString: 'applyStyle', scriptType: commandType.applyStyle },
+];
+export const ADD_NEXT_ARG_LIST = [
+ commandType.bgm,
+ commandType.pixi,
+ commandType.pixiInit,
+ commandType.label,
+ commandType.if,
+ commandType.miniAvatar,
+ commandType.setVar,
+ commandType.unlockBgm,
+ commandType.unlockCg,
+ commandType.filmMode,
+ commandType.playEffect,
+];
+
+export type ConfigMap = Map;
+export type ConfigItem = { scriptString: string; scriptType: commandType };
diff --git a/packages/parser/src/configParser/configParser.ts b/packages/parser/src/configParser/configParser.ts
new file mode 100644
index 000000000..cca9e66ec
--- /dev/null
+++ b/packages/parser/src/configParser/configParser.ts
@@ -0,0 +1,72 @@
+import { argsParser } from '../scriptParser/argsParser';
+
+interface IOptionItem {
+ key: string;
+ value: string | number | boolean;
+}
+interface IConfigItem {
+ command: string;
+ args: string[];
+ options: IOptionItem[];
+}
+
+export type WebgalConfig = IConfigItem[];
+
+function configLineParser(inputLine: string): IConfigItem {
+ const options: Array = [];
+ let command: string;
+
+ let newSentenceRaw = inputLine.split(';')[0];
+ if (newSentenceRaw === '') {
+ // 注释提前返回
+ return {
+ command: '',
+ args: [],
+ options: [],
+ };
+ }
+ // 截取命令
+ const getCommandResult = /\s*:\s*/.exec(newSentenceRaw);
+
+ // 没有command
+ if (getCommandResult === null) {
+ command = '';
+ } else {
+ command = newSentenceRaw.substring(0, getCommandResult.index);
+ // 划分命令区域和content区域
+ newSentenceRaw = newSentenceRaw.substring(
+ getCommandResult.index + 1,
+ newSentenceRaw.length,
+ );
+ }
+ // 截取 Options 区域
+ const getOptionsResult = / -/.exec(newSentenceRaw);
+ // 获取到参数
+ if (getOptionsResult) {
+ const optionsRaw = newSentenceRaw.substring(
+ getOptionsResult.index,
+ newSentenceRaw.length,
+ );
+ newSentenceRaw = newSentenceRaw.substring(0, getOptionsResult.index);
+ for (const e of argsParser(optionsRaw, (name, _) => {
+ return name;
+ })) {
+ options.push(e);
+ }
+ }
+ return {
+ command,
+ args: newSentenceRaw
+ .split('|')
+ .map((e) => e.trim())
+ .filter((e) => e !== ''),
+ options,
+ };
+}
+
+export function configParser(configText: string): WebgalConfig {
+ const configLines = configText.replaceAll(`\r`, '').split('\n');
+ return configLines
+ .map((e) => configLineParser(e))
+ .filter((e) => e.command !== '');
+}
diff --git a/packages/parser/src/index.ts b/packages/parser/src/index.ts
new file mode 100644
index 000000000..449286448
--- /dev/null
+++ b/packages/parser/src/index.ts
@@ -0,0 +1,163 @@
+import * as p from './parser';
+import { configParser, WebgalConfig } from './configParser/configParser';
+import { commandType, IAsset } from "./interface/sceneInterface";
+import { fileType } from "./interface/assets";
+import { SyntaxError as parserSyntaxError } from './parser';
+import { contentParser } from './scriptParser/contentParser';
+import { assetsScanner } from './scriptParser/assetsScanner';
+import { subSceneScanner } from './scriptParser/subSceneScanner';
+import { uniqWith } from 'lodash';
+import { IWebGALStyleObj, scss2cssinjsParser } from './styleParser';
+export { SyntaxError as parserSyntaxError } from './parser';
+import {
+ ADD_NEXT_ARG_LIST,
+ SCRIPT_CONFIG,
+ ConfigMap,
+ ConfigItem,
+} from './config/scriptConfig';
+import { sceneParser } from './sceneParser';
+
+export class NewSceneParser {
+
+ private readonly assetsPrefetcher;
+ private readonly assetSetter;
+ private readonly ADD_NEXT_ARG_LIST;
+ private readonly SCRIPT_CONFIG;
+
+ public constructor(assetsPrefetcher: ((assetList: Array) => void),
+ assetSetter: (fileName: string, assetType: fileType) => string,
+ ADD_NEXT_ARG_LIST: Array, SCRIPT_CONFIG: Array) {
+ this.assetsPrefetcher = assetsPrefetcher;
+ this.assetSetter = assetSetter;
+ this.ADD_NEXT_ARG_LIST = ADD_NEXT_ARG_LIST;
+ this.SCRIPT_CONFIG = SCRIPT_CONFIG;
+ }
+
+ public parse(rawScene: string, sceneName: string, sceneUrl: string) {
+ let result;
+ try {
+ result = p.parse(rawScene);
+ } catch (e) {
+ throw parserSyntaxError(`ERROR: parsing scene "${rawScene}" error with ${e}`);
+ }
+
+ let assetsList: Array = []; // 场景资源列表
+ let subSceneList: Array = []; // 子场景列表
+
+ // 开始资源的预加载
+ assetsList = uniqWith(assetsList); // 去重
+ this.assetsPrefetcher(assetsList);
+
+ result.sentenceList.forEach((sentence) => {
+ // 为了向后兼容性,我们单独抽取choose命令的原始语句
+ if (sentence.command === commandType.choose) {
+ const r = sentence.args.find(obj => obj.key === 'contentRawRange');
+ sentence.content = rawScene.substring(r.value[0], r.value[1]);
+ }
+
+ // 将语句内容里的文件名转为相对或绝对路径
+ const content = contentParser(sentence.content, sentence.command, this.assetSetter);
+
+ // 扫描语句携带资源
+ const sentenceAssets = assetsScanner(sentence.command, content, sentence.args);
+
+ // 扫描语句携带子场景
+ const subScene = subSceneScanner(sentence.command, content);
+
+ // 添加至语句解析结果
+ sentence.sentenceAssets = sentenceAssets;
+ sentence.subScene = subScene;
+
+ // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList
+ assetsList = [...assetsList, ...sentenceAssets];
+ subSceneList = [...subSceneList, ...subScene];
+ });
+
+ return result;
+ }
+
+ public parseConfig(configText: string) {
+ return configParser(configText);
+ }
+
+ public stringifyConfig(config: WebgalConfig) {
+ return config
+ .reduce(
+ (previousValue, curr) =>
+ (previousValue + `${curr.command}:${curr.args.join('|')}${curr.options.length <= 0 ? '' : curr.options.reduce((p, c) => (p + ' -' + c.key + '=' + c.value), '')};\n`),
+ ''
+ );
+ }
+
+ public parseScssToWebgalStyleObj(scssString: string): IWebGALStyleObj {
+ return scss2cssinjsParser(scssString);
+ }
+
+}
+
+export default class SceneParser {
+ private readonly SCRIPT_CONFIG_MAP: ConfigMap;
+ constructor(
+ private readonly assetsPrefetcher: (assetList: IAsset[]) => void,
+ private readonly assetSetter: (
+ fileName: string,
+ assetType: fileType,
+ ) => string,
+ private readonly ADD_NEXT_ARG_LIST: number[],
+ SCRIPT_CONFIG_INPUT: ConfigItem[] | ConfigMap,
+ ) {
+ if (Array.isArray(SCRIPT_CONFIG_INPUT)) {
+ this.SCRIPT_CONFIG_MAP = new Map();
+ SCRIPT_CONFIG_INPUT.forEach((config) => {
+ this.SCRIPT_CONFIG_MAP.set(config.scriptString, config);
+ });
+ } else {
+ this.SCRIPT_CONFIG_MAP = SCRIPT_CONFIG_INPUT;
+ }
+ }
+ /**
+ * 解析场景
+ * @param rawScene 原始场景
+ * @param sceneName 场景名称
+ * @param sceneUrl 场景url
+ * @return 解析后的场景
+ */
+ parse(rawScene: string, sceneName: string, sceneUrl: string) {
+ return sceneParser(
+ rawScene,
+ sceneName,
+ sceneUrl,
+ this.assetsPrefetcher,
+ this.assetSetter,
+ this.ADD_NEXT_ARG_LIST,
+ this.SCRIPT_CONFIG_MAP,
+ );
+ }
+
+ parseConfig(configText: string) {
+ return configParser(configText);
+ }
+
+ stringifyConfig(config: WebgalConfig) {
+ return config.reduce(
+ (previousValue, curr) =>
+ previousValue +
+ `${curr.command}:${curr.args.join('|')}${
+ curr.options.length <= 0
+ ? ''
+ : curr.options.reduce(
+ (p, c) => p + ' -' + c.key + '=' + c.value,
+ '',
+ )
+ };\n`,
+ '',
+ );
+ }
+
+ parseScssToWebgalStyleObj(scssString: string): IWebGALStyleObj{
+ return scss2cssinjsParser(scssString);
+ }
+
+}
+
+export { ADD_NEXT_ARG_LIST, SCRIPT_CONFIG };
diff --git a/packages/parser/src/interface/assets.ts b/packages/parser/src/interface/assets.ts
new file mode 100644
index 000000000..0fb0e2cfa
--- /dev/null
+++ b/packages/parser/src/interface/assets.ts
@@ -0,0 +1,12 @@
+/**
+ * 内置资源类型的枚举
+ */
+export enum fileType {
+ background,
+ bgm,
+ figure,
+ scene,
+ tex,
+ vocal,
+ video,
+}
diff --git a/packages/parser/src/interface/runtimeInterface.ts b/packages/parser/src/interface/runtimeInterface.ts
new file mode 100644
index 000000000..d10f30fbd
--- /dev/null
+++ b/packages/parser/src/interface/runtimeInterface.ts
@@ -0,0 +1,9 @@
+/**
+ * 子场景结束后回到父场景的入口
+ * @interface sceneEntry
+ */
+export interface sceneEntry {
+ sceneName: string; // 场景名称
+ sceneUrl: string; // 场景url
+ continueLine: number; // 继续原场景的行号
+}
diff --git a/packages/parser/src/interface/sceneInterface.ts b/packages/parser/src/interface/sceneInterface.ts
new file mode 100644
index 000000000..e5419af3b
--- /dev/null
+++ b/packages/parser/src/interface/sceneInterface.ts
@@ -0,0 +1,105 @@
+/**
+ * 语句类型
+ */
+import { sceneEntry } from './runtimeInterface';
+import { fileType } from './assets';
+
+export enum commandType {
+ say, // 对话
+ changeBg, // 更改背景
+ changeFigure, // 更改立绘
+ bgm, // 更改背景音乐
+ video, // 播放视频
+ pixi, // pixi演出
+ pixiInit, // pixi初始化
+ intro, // 黑屏文字演示
+ miniAvatar, // 小头像
+ changeScene, // 切换场景
+ choose, // 分支选择
+ end, // 结束游戏
+ setComplexAnimation, // 动画演出
+ setFilter, // 设置效果
+ label, // 标签
+ jumpLabel, // 跳转标签
+ chooseLabel, // 选择标签
+ setVar, // 设置变量
+ if, // 条件跳转
+ callScene, // 调用场景
+ showVars,
+ unlockCg,
+ unlockBgm,
+ filmMode,
+ setTextbox,
+ setAnimation,
+ playEffect,
+ setTempAnimation,
+ comment,
+ setTransform,
+ setTransition,
+ getUserInput,
+ applyStyle
+}
+
+/**
+ * 单个参数接口
+ * @interface arg
+ */
+export interface arg {
+ key: string; // 参数键
+ value: string | boolean | number; // 参数值
+}
+
+/**
+ * 资源接口
+ * @interface IAsset
+ */
+export interface IAsset {
+ name: string; // 资源名称
+ type: fileType; // 资源类型
+ url: string; // 资源url
+ lineNumber: number; // 触发资源语句的行号
+}
+
+/**
+ * 单条语句接口
+ * @interface ISentence
+ */
+export interface ISentence {
+ command: commandType; // 语句类型
+ commandRaw: string; // 命令的原始内容,方便调试
+ content: string; // 语句内容
+ args: Array; // 参数列表
+ sentenceAssets: Array; // 语句携带的资源列表
+ subScene: Array; // 语句包含子场景列表
+}
+
+/**
+ * 场景接口
+ * @interface IScene
+ */
+export interface IScene {
+ sceneName: string; // 场景名称
+ sceneUrl: string; // 场景url
+ sentenceList: Array; // 语句列表
+ assetsList: Array; // 资源列表
+ subSceneList: Array; // 子场景的url列表
+}
+
+/**
+ * 当前的场景数据
+ * @interface ISceneData
+ */
+export interface ISceneData {
+ currentSentenceId: number; // 当前语句ID
+ sceneStack: Array; // 场景栈
+ currentScene: IScene; // 当前场景数据
+}
+
+/**
+ * 处理后的命令接口
+ * @interface parsedCommand
+ */
+export interface parsedCommand {
+ type: commandType;
+ additionalArgs: Array;
+}
diff --git a/packages/parser/src/parser.js b/packages/parser/src/parser.js
new file mode 100644
index 000000000..f7923a71b
--- /dev/null
+++ b/packages/parser/src/parser.js
@@ -0,0 +1,6412 @@
+// @generated by Peggy 4.0.3.
+//
+// https://peggyjs.org/
+
+
+
+ const commandType = {
+ say: 0, // 对话
+ changeBg: 1, // 更改背景
+ changeFigure: 2, // 更改立绘
+ bgm: 3, // 更改背景音乐
+ video: 4, // 播放视频
+ pixi: 5, // pixi演出
+ pixiInit: 6, // pixi初始化
+ intro: 7, // 黑屏文字演示
+ miniAvatar: 8, // 小头像
+ changeScene: 9, // 切换场景
+ choose: 10, // 分支选择
+ end: 11, // 结束游戏
+ setComplexAnimation: 12, // 动画演出
+ setFilter: 13, // 设置效果
+ label: 14, // 标签
+ jumpLabel: 15, // 跳转标签
+ chooseLabel: 16, // 选择标签
+ setVar: 17, // 设置变量
+ if: 18, // 条件跳转
+ callScene: 19, // 调用场景
+ showVars: 20,
+ unlockCg: 21,
+ unlockBgm: 22,
+ filmMode: 23,
+ setTextbox: 24,
+ setAnimation: 25,
+ playEffect: 26,
+ setTempAnimation: 27,
+ comment: 28,
+ setTransform: 29,
+ setTransition: 30,
+ getUserInput: 31
+ };
+
+
+ function buildList(head, tail, index) {
+ return [head].concat(extractList(tail, index));
+ }
+
+ function extractList(list, index) {
+ return list.map(function(element) { return element[index]; });
+ }
+
+ function optionalList(value) {
+ return value !== null ? value : [];
+ }
+
+ function optionalString(value) {
+ return value != null ? value : "";
+ }
+
+ function filterNulls(value) {
+ return value.filter(function(element) { return element != null; });
+ }
+
+ function processVocalFileName(args) {
+ return args.map((arg) => {
+ if (arg.key.toLowerCase().match(/.ogg|.mp3|.wav/)) {
+ return {
+ key: "vocal",
+ value: arg.key
+ };
+ }
+ return arg;
+ });
+ }
+
+ function processNone(content) {
+ if (content === "" || content.toLowerCase() === "none") {
+ return "";
+ }
+ return content;
+ }
+
+function peg$subclass(child, parent) {
+ function C() { this.constructor = child; }
+ C.prototype = parent.prototype;
+ child.prototype = new C();
+}
+
+function peg$SyntaxError(message, expected, found, location) {
+ var self = Error.call(this, message);
+ // istanbul ignore next Check is a necessary evil to support older environments
+ if (Object.setPrototypeOf) {
+ Object.setPrototypeOf(self, peg$SyntaxError.prototype);
+ }
+ self.expected = expected;
+ self.found = found;
+ self.location = location;
+ self.name = "SyntaxError";
+ return self;
+}
+
+peg$subclass(peg$SyntaxError, Error);
+
+function peg$padEnd(str, targetLength, padString) {
+ padString = padString || " ";
+ if (str.length > targetLength) { return str; }
+ targetLength -= str.length;
+ padString += padString.repeat(targetLength);
+ return str + padString.slice(0, targetLength);
+}
+
+peg$SyntaxError.prototype.format = function(sources) {
+ var str = "Error: " + this.message;
+ if (this.location) {
+ var src = null;
+ var k;
+ for (k = 0; k < sources.length; k++) {
+ if (sources[k].source === this.location.source) {
+ src = sources[k].text.split(/\r\n|\n|\r/g);
+ break;
+ }
+ }
+ var s = this.location.start;
+ var offset_s = (this.location.source && (typeof this.location.source.offset === "function"))
+ ? this.location.source.offset(s)
+ : s;
+ var loc = this.location.source + ":" + offset_s.line + ":" + offset_s.column;
+ if (src) {
+ var e = this.location.end;
+ var filler = peg$padEnd("", offset_s.line.toString().length, ' ');
+ var line = src[s.line - 1];
+ var last = s.line === e.line ? e.column : line.length + 1;
+ var hatLen = (last - s.column) || 1;
+ str += "\n --> " + loc + "\n"
+ + filler + " |\n"
+ + offset_s.line + " | " + line + "\n"
+ + filler + " | " + peg$padEnd("", s.column - 1, ' ')
+ + peg$padEnd("", hatLen, "^");
+ } else {
+ str += "\n at " + loc;
+ }
+ }
+ return str;
+};
+
+peg$SyntaxError.buildMessage = function(expected, found) {
+ var DESCRIBE_EXPECTATION_FNS = {
+ literal: function(expectation) {
+ return "\"" + literalEscape(expectation.text) + "\"";
+ },
+
+ class: function(expectation) {
+ var escapedParts = expectation.parts.map(function(part) {
+ return Array.isArray(part)
+ ? classEscape(part[0]) + "-" + classEscape(part[1])
+ : classEscape(part);
+ });
+
+ return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]";
+ },
+
+ any: function() {
+ return "any character";
+ },
+
+ end: function() {
+ return "end of input";
+ },
+
+ other: function(expectation) {
+ return expectation.description;
+ }
+ };
+
+ function hex(ch) {
+ return ch.charCodeAt(0).toString(16).toUpperCase();
+ }
+
+ function literalEscape(s) {
+ return s
+ .replace(/\\/g, "\\\\")
+ .replace(/"/g, "\\\"")
+ .replace(/\0/g, "\\0")
+ .replace(/\t/g, "\\t")
+ .replace(/\n/g, "\\n")
+ .replace(/\r/g, "\\r")
+ .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); })
+ .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); });
+ }
+
+ function classEscape(s) {
+ return s
+ .replace(/\\/g, "\\\\")
+ .replace(/\]/g, "\\]")
+ .replace(/\^/g, "\\^")
+ .replace(/-/g, "\\-")
+ .replace(/\0/g, "\\0")
+ .replace(/\t/g, "\\t")
+ .replace(/\n/g, "\\n")
+ .replace(/\r/g, "\\r")
+ .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); })
+ .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); });
+ }
+
+ function describeExpectation(expectation) {
+ return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);
+ }
+
+ function describeExpected(expected) {
+ var descriptions = expected.map(describeExpectation);
+ var i, j;
+
+ descriptions.sort();
+
+ if (descriptions.length > 0) {
+ for (i = 1, j = 1; i < descriptions.length; i++) {
+ if (descriptions[i - 1] !== descriptions[i]) {
+ descriptions[j] = descriptions[i];
+ j++;
+ }
+ }
+ descriptions.length = j;
+ }
+
+ switch (descriptions.length) {
+ case 1:
+ return descriptions[0];
+
+ case 2:
+ return descriptions[0] + " or " + descriptions[1];
+
+ default:
+ return descriptions.slice(0, -1).join(", ")
+ + ", or "
+ + descriptions[descriptions.length - 1];
+ }
+ }
+
+ function describeFound(found) {
+ return found ? "\"" + literalEscape(found) + "\"" : "end of input";
+ }
+
+ return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found.";
+};
+
+function peg$parse(input, options) {
+ options = options !== undefined ? options : {};
+
+ var peg$FAILED = {};
+ var peg$source = options.grammarSource;
+
+ var peg$startRuleFunctions = { Start: peg$parseStart };
+ var peg$startRuleFunction = peg$parseStart;
+
+ var peg$c0 = "program";
+ var peg$c1 = "body";
+ var peg$c2 = "head";
+ var peg$c3 = "tail";
+ var peg$c4 = ";";
+ var peg$c5 = " -";
+ var peg$c6 = "key";
+ var peg$c7 = "=";
+ var peg$c8 = "value";
+ var peg$c9 = "changeBg";
+ var peg$c10 = "changeFigure";
+ var peg$c11 = "bgm";
+ var peg$c12 = "playVideo";
+ var peg$c13 = "pixiPerform";
+ var peg$c14 = "pixiInit";
+ var peg$c15 = "intro";
+ var peg$c16 = "miniAvatar";
+ var peg$c17 = "changeScene";
+ var peg$c18 = "choose";
+ var peg$c19 = "end";
+ var peg$c20 = "setComplexAnimation";
+ var peg$c21 = "setFilter";
+ var peg$c22 = "label";
+ var peg$c23 = "jumpLabel";
+ var peg$c24 = "chooseLabel";
+ var peg$c25 = "setVar";
+ var peg$c26 = "if";
+ var peg$c27 = "callScene";
+ var peg$c28 = "showVars";
+ var peg$c29 = "unlockCg";
+ var peg$c30 = "unlockBgm";
+ var peg$c31 = "filmMode";
+ var peg$c32 = "setTextbox";
+ var peg$c33 = "setAnimation";
+ var peg$c34 = "playEffect";
+ var peg$c35 = "setTempAnimation";
+ var peg$c36 = "comment";
+ var peg$c37 = "setTransform";
+ var peg$c38 = "setTransition";
+ var peg$c39 = "getUserInput";
+ var peg$c40 = "'";
+ var peg$c41 = "sequence";
+ var peg$c42 = "\"";
+ var peg$c43 = "\\";
+ var peg$c44 = "0";
+ var peg$c45 = "b";
+ var peg$c46 = "f";
+ var peg$c47 = "n";
+ var peg$c48 = "r";
+ var peg$c49 = "t";
+ var peg$c50 = "v";
+ var peg$c51 = "x";
+ var peg$c52 = "digits";
+ var peg$c53 = "u";
+ var peg$c54 = "\n";
+ var peg$c55 = "\r\n";
+ var peg$c56 = ":";
+ var peg$c57 = "fileName";
+ var peg$c58 = "args";
+ var peg$c59 = "performName";
+ var peg$c60 = "lines";
+ var peg$c61 = "choices";
+ var peg$c62 = "|";
+ var peg$c63 = "(";
+ var peg$c64 = "sexp";
+ var peg$c65 = ")";
+ var peg$c66 = "[";
+ var peg$c67 = "cexp";
+ var peg$c68 = "]";
+ var peg$c69 = "->";
+ var peg$c70 = "text";
+ var peg$c71 = "dest";
+ var peg$c72 = "animationName";
+ var peg$c73 = "labelName";
+ var peg$c74 = "kv";
+ var peg$c75 = "sceneName";
+ var peg$c76 = "name";
+ var peg$c77 = "content";
+ var peg$c78 = "json";
+ var peg$c79 = "into";
+ var peg$c80 = "speaker";
+ var peg$c81 = "err";
+
+ var peg$r0 = /^[(-);={}]/;
+ var peg$r1 = /^['\\]/;
+ var peg$r2 = /^["\\]/;
+ var peg$r3 = /^["'\\]/;
+ var peg$r4 = /^[(-){}]/;
+ var peg$r5 = /^[0-9ux]/;
+ var peg$r6 = /^[0-9]/;
+ var peg$r7 = /^[$A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376-\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4-\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5-\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/;
+ var peg$r8 = /^[0-9a-f]/i;
+ var peg$r9 = /^[0-9_\u0300-\u036F\u0483-\u0487\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u0669\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\u06F0-\u06F9\u0711\u0730-\u074A\u07A6-\u07B0\u07C0-\u07C9\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962-\u0963\u0966-\u096F\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7-\u09C8\u09CB-\u09CD\u09D7\u09E2-\u09E3\u09E6-\u09EF\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A51\u0A66-\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47-\u0B48\u0B4B-\u0B4D\u0B56-\u0B57\u0B62-\u0B63\u0B66-\u0B6F\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56\u0C62-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5-\u0CD6\u0CE2-\u0CE3\u0CE6-\u0CEF\u0D01-\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62-\u0D63\u0D66-\u0D6F\u0D82-\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2-\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0E50-\u0E59\u0EB1\u0EB4-\u0EB9\u0EBB-\u0EBC\u0EC8-\u0ECD\u0ED0-\u0ED9\u0F18-\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F3F\u0F71-\u0F84\u0F86-\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1040-\u1049\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F-\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752-\u1753\u1772-\u1773\u17B4-\u17D3\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u18A9\u1920-\u192B\u1930-\u193B\u1946-\u194F\u19D0-\u19D9\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AB0-\u1ABD\u1B00-\u1B04\u1B34-\u1B44\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BB0-\u1BB9\u1BE6-\u1BF3\u1C24-\u1C37\u1C40-\u1C49\u1C50-\u1C59\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF2-\u1CF4\u1CF8-\u1CF9\u1DC0-\u1DF5\u1DFC-\u1DFF\u200C-\u200D\u203F-\u2040\u2054\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099-\u309A\uA620-\uA629\uA66F\uA674-\uA67D\uA69E-\uA69F\uA6F0-\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA880-\uA881\uA8B4-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F1\uA900-\uA909\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9D0-\uA9D9\uA9E5\uA9F0-\uA9F9\uAA29-\uAA36\uAA43\uAA4C-\uAA4D\uAA50-\uAA59\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7-\uAAB8\uAABE-\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5-\uAAF6\uABE3-\uABEA\uABEC-\uABED\uABF0-\uABF9\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\uFE33-\uFE34\uFE4D-\uFE4F\uFF10-\uFF19\uFF3F]/;
+ var peg$r10 = /^[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376-\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4-\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5-\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/;
+ var peg$r11 = /^[\u0300-\u036F\u0483-\u0487\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962-\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7-\u09C8\u09CB-\u09CD\u09D7\u09E2-\u09E3\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A51\u0A70-\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2-\u0AE3\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47-\u0B48\u0B4B-\u0B4D\u0B56-\u0B57\u0B62-\u0B63\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C00-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56\u0C62-\u0C63\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5-\u0CD6\u0CE2-\u0CE3\u0D01-\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62-\u0D63\u0D82-\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2-\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB-\u0EBC\u0EC8-\u0ECD\u0F18-\u0F19\u0F35\u0F37\u0F39\u0F3E-\u0F3F\u0F71-\u0F84\u0F86-\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F\u109A-\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752-\u1753\u1772-\u1773\u17B4-\u17D3\u17DD\u180B-\u180D\u18A9\u1920-\u192B\u1930-\u193B\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F\u1AB0-\u1ABD\u1B00-\u1B04\u1B34-\u1B44\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BE6-\u1BF3\u1C24-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF2-\u1CF4\u1CF8-\u1CF9\u1DC0-\u1DF5\u1DFC-\u1DFF\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099-\u309A\uA66F\uA674-\uA67D\uA69E-\uA69F\uA6F0-\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA880-\uA881\uA8B4-\uA8C4\uA8E0-\uA8F1\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9E5\uAA29-\uAA36\uAA43\uAA4C-\uAA4D\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7-\uAAB8\uAABE-\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5-\uAAF6\uABE3-\uABEA\uABEC-\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F]/;
+ var peg$r12 = /^[\n\r\u2028\u2029]/;
+ var peg$r13 = /^[\r\u2028-\u2029]/;
+ var peg$r14 = /^[\t\v-\f \xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF]/;
+ var peg$r15 = /^[);]/;
+ var peg$r16 = /^[:-;]/;
+ var peg$r17 = /^[a-z\xB5\xDF-\xF6\xF8-\xFF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137-\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148-\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C-\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA-\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9-\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC-\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF-\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F-\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0-\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB-\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE-\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0561-\u0587\u13F8-\u13FD\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6-\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FC7\u1FD0-\u1FD3\u1FD6-\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6-\u1FF7\u210A\u210E-\u210F\u2113\u212F\u2134\u2139\u213C-\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5E\u2C61\u2C65-\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73-\u2C74\u2C76-\u2C7B\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3-\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7B5\uA7B7\uA7FA\uAB30-\uAB5A\uAB60-\uAB65\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A]/;
+ var peg$r18 = /^[\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0374\u037A\u0559\u0640\u06E5-\u06E6\u07F4-\u07F5\u07FA\u081A\u0824\u0828\u0971\u0E46\u0EC6\u10FC\u17D7\u1843\u1AA7\u1C78-\u1C7D\u1D2C-\u1D6A\u1D78\u1D9B-\u1DBF\u2071\u207F\u2090-\u209C\u2C7C-\u2C7D\u2D6F\u2E2F\u3005\u3031-\u3035\u303B\u309D-\u309E\u30FC-\u30FE\uA015\uA4F8-\uA4FD\uA60C\uA67F\uA69C-\uA69D\uA717-\uA71F\uA770\uA788\uA7F8-\uA7F9\uA9CF\uA9E6\uAA70\uAADD\uAAF3-\uAAF4\uAB5C-\uAB5F\uFF70\uFF9E-\uFF9F]/;
+ var peg$r19 = /^[\xAA\xBA\u01BB\u01C0-\u01C3\u0294\u05D0-\u05EA\u05F0-\u05F2\u0620-\u063F\u0641-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u0800-\u0815\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0972-\u0980\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E45\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u10D0-\u10FA\u10FD-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17DC\u1820-\u1842\u1844-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C77\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5-\u1CF6\u2135-\u2138\u2D30-\u2D67\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3006\u303C\u3041-\u3096\u309F\u30A1-\u30FA\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA014\uA016-\uA48C\uA4D0-\uA4F7\uA500-\uA60B\uA610-\uA61F\uA62A-\uA62B\uA66E\uA6A0-\uA6E5\uA78F\uA7F7\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9E0-\uA9E4\uA9E7-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA6F\uAA71-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADC\uAAE0-\uAAEA\uAAF2\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF66-\uFF6F\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/;
+ var peg$r20 = /^[\u01C5\u01C8\u01CB\u01F2\u1F88-\u1F8F\u1F98-\u1F9F\u1FA8-\u1FAF\u1FBC\u1FCC\u1FFC]/;
+ var peg$r21 = /^[A-Z\xC0-\xD6\xD8-\xDE\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178-\u0179\u017B\u017D\u0181-\u0182\u0184\u0186-\u0187\u0189-\u018B\u018E-\u0191\u0193-\u0194\u0196-\u0198\u019C-\u019D\u019F-\u01A0\u01A2\u01A4\u01A6-\u01A7\u01A9\u01AC\u01AE-\u01AF\u01B1-\u01B3\u01B5\u01B7-\u01B8\u01BC\u01C4\u01C7\u01CA\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F1\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A-\u023B\u023D-\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E-\u038F\u0391-\u03A1\u03A3-\u03AB\u03CF\u03D2-\u03D4\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F4\u03F7\u03F9-\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0-\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u10A0-\u10C5\u10C7\u10CD\u13A0-\u13F5\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1FB8-\u1FBB\u1FC8-\u1FCB\u1FD8-\u1FDB\u1FE8-\u1FEC\u1FF8-\u1FFB\u2102\u2107\u210B-\u210D\u2110-\u2112\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u2130-\u2133\u213E-\u213F\u2145\u2183\u2C00-\u2C2E\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D-\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AD\uA7B0-\uA7B4\uA7B6\uFF21-\uFF3A]/;
+ var peg$r22 = /^[\u0903\u093B\u093E-\u0940\u0949-\u094C\u094E-\u094F\u0982-\u0983\u09BE-\u09C0\u09C7-\u09C8\u09CB-\u09CC\u09D7\u0A03\u0A3E-\u0A40\u0A83\u0ABE-\u0AC0\u0AC9\u0ACB-\u0ACC\u0B02-\u0B03\u0B3E\u0B40\u0B47-\u0B48\u0B4B-\u0B4C\u0B57\u0BBE-\u0BBF\u0BC1-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BD7\u0C01-\u0C03\u0C41-\u0C44\u0C82-\u0C83\u0CBE\u0CC0-\u0CC4\u0CC7-\u0CC8\u0CCA-\u0CCB\u0CD5-\u0CD6\u0D02-\u0D03\u0D3E-\u0D40\u0D46-\u0D48\u0D4A-\u0D4C\u0D57\u0D82-\u0D83\u0DCF-\u0DD1\u0DD8-\u0DDF\u0DF2-\u0DF3\u0F3E-\u0F3F\u0F7F\u102B-\u102C\u1031\u1038\u103B-\u103C\u1056-\u1057\u1062-\u1064\u1067-\u106D\u1083-\u1084\u1087-\u108C\u108F\u109A-\u109C\u17B6\u17BE-\u17C5\u17C7-\u17C8\u1923-\u1926\u1929-\u192B\u1930-\u1931\u1933-\u1938\u1A19-\u1A1A\u1A55\u1A57\u1A61\u1A63-\u1A64\u1A6D-\u1A72\u1B04\u1B35\u1B3B\u1B3D-\u1B41\u1B43-\u1B44\u1B82\u1BA1\u1BA6-\u1BA7\u1BAA\u1BE7\u1BEA-\u1BEC\u1BEE\u1BF2-\u1BF3\u1C24-\u1C2B\u1C34-\u1C35\u1CE1\u1CF2-\u1CF3\u302E-\u302F\uA823-\uA824\uA827\uA880-\uA881\uA8B4-\uA8C3\uA952-\uA953\uA983\uA9B4-\uA9B5\uA9BA-\uA9BB\uA9BD-\uA9C0\uAA2F-\uAA30\uAA33-\uAA34\uAA4D\uAA7B\uAA7D\uAAEB\uAAEE-\uAAEF\uAAF5\uABE3-\uABE4\uABE6-\uABE7\uABE9-\uABEA\uABEC]/;
+ var peg$r23 = /^[\u0300-\u036F\u0483-\u0487\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08E3-\u0902\u093A\u093C\u0941-\u0948\u094D\u0951-\u0957\u0962-\u0963\u0981\u09BC\u09C1-\u09C4\u09CD\u09E2-\u09E3\u0A01-\u0A02\u0A3C\u0A41-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A51\u0A70-\u0A71\u0A75\u0A81-\u0A82\u0ABC\u0AC1-\u0AC5\u0AC7-\u0AC8\u0ACD\u0AE2-\u0AE3\u0B01\u0B3C\u0B3F\u0B41-\u0B44\u0B4D\u0B56\u0B62-\u0B63\u0B82\u0BC0\u0BCD\u0C00\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56\u0C62-\u0C63\u0C81\u0CBC\u0CBF\u0CC6\u0CCC-\u0CCD\u0CE2-\u0CE3\u0D01\u0D41-\u0D44\u0D4D\u0D62-\u0D63\u0DCA\u0DD2-\u0DD4\u0DD6\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB-\u0EBC\u0EC8-\u0ECD\u0F18-\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86-\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039-\u103A\u103D-\u103E\u1058-\u1059\u105E-\u1060\u1071-\u1074\u1082\u1085-\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752-\u1753\u1772-\u1773\u17B4-\u17B5\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u18A9\u1920-\u1922\u1927-\u1928\u1932\u1939-\u193B\u1A17-\u1A18\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ABD\u1B00-\u1B03\u1B34\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80-\u1B81\u1BA2-\u1BA5\u1BA8-\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8-\u1BE9\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8-\u1CF9\u1DC0-\u1DF5\u1DFC-\u1DFF\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u3099-\u309A\uA66F\uA674-\uA67D\uA69E-\uA69F\uA6F0-\uA6F1\uA802\uA806\uA80B\uA825-\uA826\uA8C4\uA8E0-\uA8F1\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC\uA9E5\uAA29-\uAA2E\uAA31-\uAA32\uAA35-\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7-\uAAB8\uAABE-\uAABF\uAAC1\uAAEC-\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F]/;
+ var peg$r24 = /^[0-9\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0BE6-\u0BEF\u0C66-\u0C6F\u0CE6-\u0CEF\u0D66-\u0D6F\u0DE6-\u0DEF\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F29\u1040-\u1049\u1090-\u1099\u17E0-\u17E9\u1810-\u1819\u1946-\u194F\u19D0-\u19D9\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\uA620-\uA629\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uA9F0-\uA9F9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19]/;
+ var peg$r25 = /^[\u16EE-\u16F0\u2160-\u2182\u2185-\u2188\u3007\u3021-\u3029\u3038-\u303A\uA6E6-\uA6EF]/;
+ var peg$r26 = /^[_\u203F-\u2040\u2054\uFE33-\uFE34\uFE4D-\uFE4F\uFF3F]/;
+ var peg$r27 = /^[ \xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/;
+
+ var peg$e0 = peg$anyExpectation();
+ var peg$e1 = peg$literalExpectation(";", false);
+ var peg$e2 = peg$otherExpectation("source element");
+ var peg$e3 = peg$otherExpectation("arguments");
+ var peg$e4 = peg$otherExpectation("argument");
+ var peg$e5 = peg$literalExpectation(" -", false);
+ var peg$e6 = peg$otherExpectation("argument with value");
+ var peg$e7 = peg$literalExpectation("=", false);
+ var peg$e8 = peg$otherExpectation("argument without value");
+ var peg$e9 = peg$otherExpectation("reserve word");
+ var peg$e10 = peg$otherExpectation("'changeBg'");
+ var peg$e11 = peg$literalExpectation("changeBg", false);
+ var peg$e12 = peg$otherExpectation("'changeFigure'");
+ var peg$e13 = peg$literalExpectation("changeFigure", false);
+ var peg$e14 = peg$otherExpectation("'bgm'");
+ var peg$e15 = peg$literalExpectation("bgm", false);
+ var peg$e16 = peg$otherExpectation("'playVideo'");
+ var peg$e17 = peg$literalExpectation("playVideo", false);
+ var peg$e18 = peg$otherExpectation("'pixiPerform'");
+ var peg$e19 = peg$literalExpectation("pixiPerform", false);
+ var peg$e20 = peg$otherExpectation("'pixiInit'");
+ var peg$e21 = peg$literalExpectation("pixiInit", false);
+ var peg$e22 = peg$otherExpectation("'intro'");
+ var peg$e23 = peg$literalExpectation("intro", false);
+ var peg$e24 = peg$otherExpectation("'miniAvatar'");
+ var peg$e25 = peg$literalExpectation("miniAvatar", false);
+ var peg$e26 = peg$otherExpectation("'changeScene'");
+ var peg$e27 = peg$literalExpectation("changeScene", false);
+ var peg$e28 = peg$otherExpectation("'choose'");
+ var peg$e29 = peg$literalExpectation("choose", false);
+ var peg$e30 = peg$otherExpectation("'end'");
+ var peg$e31 = peg$literalExpectation("end", false);
+ var peg$e32 = peg$otherExpectation("'setComplexAnimation'");
+ var peg$e33 = peg$literalExpectation("setComplexAnimation", false);
+ var peg$e34 = peg$otherExpectation("'setFilter'");
+ var peg$e35 = peg$literalExpectation("setFilter", false);
+ var peg$e36 = peg$otherExpectation("'label'");
+ var peg$e37 = peg$literalExpectation("label", false);
+ var peg$e38 = peg$otherExpectation("'jumpLabel'");
+ var peg$e39 = peg$literalExpectation("jumpLabel", false);
+ var peg$e40 = peg$otherExpectation("'chooseLabel'");
+ var peg$e41 = peg$literalExpectation("chooseLabel", false);
+ var peg$e42 = peg$otherExpectation("'setVar'");
+ var peg$e43 = peg$literalExpectation("setVar", false);
+ var peg$e44 = peg$otherExpectation("'if'");
+ var peg$e45 = peg$literalExpectation("if", false);
+ var peg$e46 = peg$otherExpectation("'callScene'");
+ var peg$e47 = peg$literalExpectation("callScene", false);
+ var peg$e48 = peg$otherExpectation("'showVars'");
+ var peg$e49 = peg$literalExpectation("showVars", false);
+ var peg$e50 = peg$otherExpectation("'unlockCg'");
+ var peg$e51 = peg$literalExpectation("unlockCg", false);
+ var peg$e52 = peg$otherExpectation("'unlockBgm'");
+ var peg$e53 = peg$literalExpectation("unlockBgm", false);
+ var peg$e54 = peg$otherExpectation("'filmMode'");
+ var peg$e55 = peg$literalExpectation("filmMode", false);
+ var peg$e56 = peg$otherExpectation("'setTextbox'");
+ var peg$e57 = peg$literalExpectation("setTextbox", false);
+ var peg$e58 = peg$otherExpectation("'setAnimation'");
+ var peg$e59 = peg$literalExpectation("setAnimation", false);
+ var peg$e60 = peg$otherExpectation("'playEffect'");
+ var peg$e61 = peg$literalExpectation("playEffect", false);
+ var peg$e62 = peg$otherExpectation("'setTempAnimation'");
+ var peg$e63 = peg$literalExpectation("setTempAnimation", false);
+ var peg$e64 = peg$otherExpectation("'comment'");
+ var peg$e65 = peg$literalExpectation("comment", false);
+ var peg$e66 = peg$otherExpectation("'setTransform'");
+ var peg$e67 = peg$literalExpectation("setTransform", false);
+ var peg$e68 = peg$otherExpectation("'setTransition'");
+ var peg$e69 = peg$literalExpectation("setTransition", false);
+ var peg$e70 = peg$otherExpectation("'getUserInput'");
+ var peg$e71 = peg$literalExpectation("getUserInput", false);
+ var peg$e72 = peg$classExpectation([["(", ")"], ";", "=", "{", "}"], false, false);
+ var peg$e73 = peg$otherExpectation("string");
+ var peg$e74 = peg$literalExpectation("'", false);
+ var peg$e75 = peg$literalExpectation("\"", false);
+ var peg$e76 = peg$otherExpectation("string allow white space");
+ var peg$e77 = peg$classExpectation(["'", "\\"], false, false);
+ var peg$e78 = peg$literalExpectation("\\", false);
+ var peg$e79 = peg$classExpectation(["\"", "\\"], false, false);
+ var peg$e80 = peg$literalExpectation("0", false);
+ var peg$e81 = peg$classExpectation(["\"", "'", "\\"], false, false);
+ var peg$e82 = peg$literalExpectation("b", false);
+ var peg$e83 = peg$literalExpectation("f", false);
+ var peg$e84 = peg$literalExpectation("n", false);
+ var peg$e85 = peg$literalExpectation("r", false);
+ var peg$e86 = peg$literalExpectation("t", false);
+ var peg$e87 = peg$literalExpectation("v", false);
+ var peg$e88 = peg$classExpectation([["(", ")"], "{", "}"], false, false);
+ var peg$e89 = peg$classExpectation([["0", "9"], "u", "x"], false, false);
+ var peg$e90 = peg$classExpectation([["0", "9"]], false, false);
+ var peg$e91 = peg$literalExpectation("x", false);
+ var peg$e92 = peg$literalExpectation("u", false);
+ var peg$e93 = peg$classExpectation(["$", ["A", "Z"], "_", ["a", "z"], "\xAA", "\xB5", "\xBA", ["\xC0", "\xD6"], ["\xD8", "\xF6"], ["\xF8", "\u02C1"], ["\u02C6", "\u02D1"], ["\u02E0", "\u02E4"], "\u02EC", "\u02EE", ["\u0370", "\u0374"], ["\u0376", "\u0377"], ["\u037A", "\u037D"], "\u037F", "\u0386", ["\u0388", "\u038A"], "\u038C", ["\u038E", "\u03A1"], ["\u03A3", "\u03F5"], ["\u03F7", "\u0481"], ["\u048A", "\u052F"], ["\u0531", "\u0556"], "\u0559", ["\u0561", "\u0587"], ["\u05D0", "\u05EA"], ["\u05F0", "\u05F2"], ["\u0620", "\u064A"], ["\u066E", "\u066F"], ["\u0671", "\u06D3"], "\u06D5", ["\u06E5", "\u06E6"], ["\u06EE", "\u06EF"], ["\u06FA", "\u06FC"], "\u06FF", "\u0710", ["\u0712", "\u072F"], ["\u074D", "\u07A5"], "\u07B1", ["\u07CA", "\u07EA"], ["\u07F4", "\u07F5"], "\u07FA", ["\u0800", "\u0815"], "\u081A", "\u0824", "\u0828", ["\u0840", "\u0858"], ["\u08A0", "\u08B4"], ["\u0904", "\u0939"], "\u093D", "\u0950", ["\u0958", "\u0961"], ["\u0971", "\u0980"], ["\u0985", "\u098C"], ["\u098F", "\u0990"], ["\u0993", "\u09A8"], ["\u09AA", "\u09B0"], "\u09B2", ["\u09B6", "\u09B9"], "\u09BD", "\u09CE", ["\u09DC", "\u09DD"], ["\u09DF", "\u09E1"], ["\u09F0", "\u09F1"], ["\u0A05", "\u0A0A"], ["\u0A0F", "\u0A10"], ["\u0A13", "\u0A28"], ["\u0A2A", "\u0A30"], ["\u0A32", "\u0A33"], ["\u0A35", "\u0A36"], ["\u0A38", "\u0A39"], ["\u0A59", "\u0A5C"], "\u0A5E", ["\u0A72", "\u0A74"], ["\u0A85", "\u0A8D"], ["\u0A8F", "\u0A91"], ["\u0A93", "\u0AA8"], ["\u0AAA", "\u0AB0"], ["\u0AB2", "\u0AB3"], ["\u0AB5", "\u0AB9"], "\u0ABD", "\u0AD0", ["\u0AE0", "\u0AE1"], "\u0AF9", ["\u0B05", "\u0B0C"], ["\u0B0F", "\u0B10"], ["\u0B13", "\u0B28"], ["\u0B2A", "\u0B30"], ["\u0B32", "\u0B33"], ["\u0B35", "\u0B39"], "\u0B3D", ["\u0B5C", "\u0B5D"], ["\u0B5F", "\u0B61"], "\u0B71", "\u0B83", ["\u0B85", "\u0B8A"], ["\u0B8E", "\u0B90"], ["\u0B92", "\u0B95"], ["\u0B99", "\u0B9A"], "\u0B9C", ["\u0B9E", "\u0B9F"], ["\u0BA3", "\u0BA4"], ["\u0BA8", "\u0BAA"], ["\u0BAE", "\u0BB9"], "\u0BD0", ["\u0C05", "\u0C0C"], ["\u0C0E", "\u0C10"], ["\u0C12", "\u0C28"], ["\u0C2A", "\u0C39"], "\u0C3D", ["\u0C58", "\u0C5A"], ["\u0C60", "\u0C61"], ["\u0C85", "\u0C8C"], ["\u0C8E", "\u0C90"], ["\u0C92", "\u0CA8"], ["\u0CAA", "\u0CB3"], ["\u0CB5", "\u0CB9"], "\u0CBD", "\u0CDE", ["\u0CE0", "\u0CE1"], ["\u0CF1", "\u0CF2"], ["\u0D05", "\u0D0C"], ["\u0D0E", "\u0D10"], ["\u0D12", "\u0D3A"], "\u0D3D", "\u0D4E", ["\u0D5F", "\u0D61"], ["\u0D7A", "\u0D7F"], ["\u0D85", "\u0D96"], ["\u0D9A", "\u0DB1"], ["\u0DB3", "\u0DBB"], "\u0DBD", ["\u0DC0", "\u0DC6"], ["\u0E01", "\u0E30"], ["\u0E32", "\u0E33"], ["\u0E40", "\u0E46"], ["\u0E81", "\u0E82"], "\u0E84", ["\u0E87", "\u0E88"], "\u0E8A", "\u0E8D", ["\u0E94", "\u0E97"], ["\u0E99", "\u0E9F"], ["\u0EA1", "\u0EA3"], "\u0EA5", "\u0EA7", ["\u0EAA", "\u0EAB"], ["\u0EAD", "\u0EB0"], ["\u0EB2", "\u0EB3"], "\u0EBD", ["\u0EC0", "\u0EC4"], "\u0EC6", ["\u0EDC", "\u0EDF"], "\u0F00", ["\u0F40", "\u0F47"], ["\u0F49", "\u0F6C"], ["\u0F88", "\u0F8C"], ["\u1000", "\u102A"], "\u103F", ["\u1050", "\u1055"], ["\u105A", "\u105D"], "\u1061", ["\u1065", "\u1066"], ["\u106E", "\u1070"], ["\u1075", "\u1081"], "\u108E", ["\u10A0", "\u10C5"], "\u10C7", "\u10CD", ["\u10D0", "\u10FA"], ["\u10FC", "\u1248"], ["\u124A", "\u124D"], ["\u1250", "\u1256"], "\u1258", ["\u125A", "\u125D"], ["\u1260", "\u1288"], ["\u128A", "\u128D"], ["\u1290", "\u12B0"], ["\u12B2", "\u12B5"], ["\u12B8", "\u12BE"], "\u12C0", ["\u12C2", "\u12C5"], ["\u12C8", "\u12D6"], ["\u12D8", "\u1310"], ["\u1312", "\u1315"], ["\u1318", "\u135A"], ["\u1380", "\u138F"], ["\u13A0", "\u13F5"], ["\u13F8", "\u13FD"], ["\u1401", "\u166C"], ["\u166F", "\u167F"], ["\u1681", "\u169A"], ["\u16A0", "\u16EA"], ["\u16EE", "\u16F8"], ["\u1700", "\u170C"], ["\u170E", "\u1711"], ["\u1720", "\u1731"], ["\u1740", "\u1751"], ["\u1760", "\u176C"], ["\u176E", "\u1770"], ["\u1780", "\u17B3"], "\u17D7", "\u17DC", ["\u1820", "\u1877"], ["\u1880", "\u18A8"], "\u18AA", ["\u18B0", "\u18F5"], ["\u1900", "\u191E"], ["\u1950", "\u196D"], ["\u1970", "\u1974"], ["\u1980", "\u19AB"], ["\u19B0", "\u19C9"], ["\u1A00", "\u1A16"], ["\u1A20", "\u1A54"], "\u1AA7", ["\u1B05", "\u1B33"], ["\u1B45", "\u1B4B"], ["\u1B83", "\u1BA0"], ["\u1BAE", "\u1BAF"], ["\u1BBA", "\u1BE5"], ["\u1C00", "\u1C23"], ["\u1C4D", "\u1C4F"], ["\u1C5A", "\u1C7D"], ["\u1CE9", "\u1CEC"], ["\u1CEE", "\u1CF1"], ["\u1CF5", "\u1CF6"], ["\u1D00", "\u1DBF"], ["\u1E00", "\u1F15"], ["\u1F18", "\u1F1D"], ["\u1F20", "\u1F45"], ["\u1F48", "\u1F4D"], ["\u1F50", "\u1F57"], "\u1F59", "\u1F5B", "\u1F5D", ["\u1F5F", "\u1F7D"], ["\u1F80", "\u1FB4"], ["\u1FB6", "\u1FBC"], "\u1FBE", ["\u1FC2", "\u1FC4"], ["\u1FC6", "\u1FCC"], ["\u1FD0", "\u1FD3"], ["\u1FD6", "\u1FDB"], ["\u1FE0", "\u1FEC"], ["\u1FF2", "\u1FF4"], ["\u1FF6", "\u1FFC"], "\u2071", "\u207F", ["\u2090", "\u209C"], "\u2102", "\u2107", ["\u210A", "\u2113"], "\u2115", ["\u2119", "\u211D"], "\u2124", "\u2126", "\u2128", ["\u212A", "\u212D"], ["\u212F", "\u2139"], ["\u213C", "\u213F"], ["\u2145", "\u2149"], "\u214E", ["\u2160", "\u2188"], ["\u2C00", "\u2C2E"], ["\u2C30", "\u2C5E"], ["\u2C60", "\u2CE4"], ["\u2CEB", "\u2CEE"], ["\u2CF2", "\u2CF3"], ["\u2D00", "\u2D25"], "\u2D27", "\u2D2D", ["\u2D30", "\u2D67"], "\u2D6F", ["\u2D80", "\u2D96"], ["\u2DA0", "\u2DA6"], ["\u2DA8", "\u2DAE"], ["\u2DB0", "\u2DB6"], ["\u2DB8", "\u2DBE"], ["\u2DC0", "\u2DC6"], ["\u2DC8", "\u2DCE"], ["\u2DD0", "\u2DD6"], ["\u2DD8", "\u2DDE"], "\u2E2F", ["\u3005", "\u3007"], ["\u3021", "\u3029"], ["\u3031", "\u3035"], ["\u3038", "\u303C"], ["\u3041", "\u3096"], ["\u309D", "\u309F"], ["\u30A1", "\u30FA"], ["\u30FC", "\u30FF"], ["\u3105", "\u312D"], ["\u3131", "\u318E"], ["\u31A0", "\u31BA"], ["\u31F0", "\u31FF"], ["\u3400", "\u4DB5"], ["\u4E00", "\u9FD5"], ["\uA000", "\uA48C"], ["\uA4D0", "\uA4FD"], ["\uA500", "\uA60C"], ["\uA610", "\uA61F"], ["\uA62A", "\uA62B"], ["\uA640", "\uA66E"], ["\uA67F", "\uA69D"], ["\uA6A0", "\uA6EF"], ["\uA717", "\uA71F"], ["\uA722", "\uA788"], ["\uA78B", "\uA7AD"], ["\uA7B0", "\uA7B7"], ["\uA7F7", "\uA801"], ["\uA803", "\uA805"], ["\uA807", "\uA80A"], ["\uA80C", "\uA822"], ["\uA840", "\uA873"], ["\uA882", "\uA8B3"], ["\uA8F2", "\uA8F7"], "\uA8FB", "\uA8FD", ["\uA90A", "\uA925"], ["\uA930", "\uA946"], ["\uA960", "\uA97C"], ["\uA984", "\uA9B2"], "\uA9CF", ["\uA9E0", "\uA9E4"], ["\uA9E6", "\uA9EF"], ["\uA9FA", "\uA9FE"], ["\uAA00", "\uAA28"], ["\uAA40", "\uAA42"], ["\uAA44", "\uAA4B"], ["\uAA60", "\uAA76"], "\uAA7A", ["\uAA7E", "\uAAAF"], "\uAAB1", ["\uAAB5", "\uAAB6"], ["\uAAB9", "\uAABD"], "\uAAC0", "\uAAC2", ["\uAADB", "\uAADD"], ["\uAAE0", "\uAAEA"], ["\uAAF2", "\uAAF4"], ["\uAB01", "\uAB06"], ["\uAB09", "\uAB0E"], ["\uAB11", "\uAB16"], ["\uAB20", "\uAB26"], ["\uAB28", "\uAB2E"], ["\uAB30", "\uAB5A"], ["\uAB5C", "\uAB65"], ["\uAB70", "\uABE2"], ["\uAC00", "\uD7A3"], ["\uD7B0", "\uD7C6"], ["\uD7CB", "\uD7FB"], ["\uF900", "\uFA6D"], ["\uFA70", "\uFAD9"], ["\uFB00", "\uFB06"], ["\uFB13", "\uFB17"], "\uFB1D", ["\uFB1F", "\uFB28"], ["\uFB2A", "\uFB36"], ["\uFB38", "\uFB3C"], "\uFB3E", ["\uFB40", "\uFB41"], ["\uFB43", "\uFB44"], ["\uFB46", "\uFBB1"], ["\uFBD3", "\uFD3D"], ["\uFD50", "\uFD8F"], ["\uFD92", "\uFDC7"], ["\uFDF0", "\uFDFB"], ["\uFE70", "\uFE74"], ["\uFE76", "\uFEFC"], ["\uFF21", "\uFF3A"], ["\uFF41", "\uFF5A"], ["\uFF66", "\uFFBE"], ["\uFFC2", "\uFFC7"], ["\uFFCA", "\uFFCF"], ["\uFFD2", "\uFFD7"], ["\uFFDA", "\uFFDC"]], false, false);
+ var peg$e94 = peg$classExpectation([["0", "9"], ["a", "f"]], false, true);
+ var peg$e95 = peg$classExpectation([["0", "9"], "_", ["\u0300", "\u036F"], ["\u0483", "\u0487"], ["\u0591", "\u05BD"], "\u05BF", ["\u05C1", "\u05C2"], ["\u05C4", "\u05C5"], "\u05C7", ["\u0610", "\u061A"], ["\u064B", "\u0669"], "\u0670", ["\u06D6", "\u06DC"], ["\u06DF", "\u06E4"], ["\u06E7", "\u06E8"], ["\u06EA", "\u06ED"], ["\u06F0", "\u06F9"], "\u0711", ["\u0730", "\u074A"], ["\u07A6", "\u07B0"], ["\u07C0", "\u07C9"], ["\u07EB", "\u07F3"], ["\u0816", "\u0819"], ["\u081B", "\u0823"], ["\u0825", "\u0827"], ["\u0829", "\u082D"], ["\u0859", "\u085B"], ["\u08E3", "\u0903"], ["\u093A", "\u093C"], ["\u093E", "\u094F"], ["\u0951", "\u0957"], ["\u0962", "\u0963"], ["\u0966", "\u096F"], ["\u0981", "\u0983"], "\u09BC", ["\u09BE", "\u09C4"], ["\u09C7", "\u09C8"], ["\u09CB", "\u09CD"], "\u09D7", ["\u09E2", "\u09E3"], ["\u09E6", "\u09EF"], ["\u0A01", "\u0A03"], "\u0A3C", ["\u0A3E", "\u0A42"], ["\u0A47", "\u0A48"], ["\u0A4B", "\u0A4D"], "\u0A51", ["\u0A66", "\u0A71"], "\u0A75", ["\u0A81", "\u0A83"], "\u0ABC", ["\u0ABE", "\u0AC5"], ["\u0AC7", "\u0AC9"], ["\u0ACB", "\u0ACD"], ["\u0AE2", "\u0AE3"], ["\u0AE6", "\u0AEF"], ["\u0B01", "\u0B03"], "\u0B3C", ["\u0B3E", "\u0B44"], ["\u0B47", "\u0B48"], ["\u0B4B", "\u0B4D"], ["\u0B56", "\u0B57"], ["\u0B62", "\u0B63"], ["\u0B66", "\u0B6F"], "\u0B82", ["\u0BBE", "\u0BC2"], ["\u0BC6", "\u0BC8"], ["\u0BCA", "\u0BCD"], "\u0BD7", ["\u0BE6", "\u0BEF"], ["\u0C00", "\u0C03"], ["\u0C3E", "\u0C44"], ["\u0C46", "\u0C48"], ["\u0C4A", "\u0C4D"], ["\u0C55", "\u0C56"], ["\u0C62", "\u0C63"], ["\u0C66", "\u0C6F"], ["\u0C81", "\u0C83"], "\u0CBC", ["\u0CBE", "\u0CC4"], ["\u0CC6", "\u0CC8"], ["\u0CCA", "\u0CCD"], ["\u0CD5", "\u0CD6"], ["\u0CE2", "\u0CE3"], ["\u0CE6", "\u0CEF"], ["\u0D01", "\u0D03"], ["\u0D3E", "\u0D44"], ["\u0D46", "\u0D48"], ["\u0D4A", "\u0D4D"], "\u0D57", ["\u0D62", "\u0D63"], ["\u0D66", "\u0D6F"], ["\u0D82", "\u0D83"], "\u0DCA", ["\u0DCF", "\u0DD4"], "\u0DD6", ["\u0DD8", "\u0DDF"], ["\u0DE6", "\u0DEF"], ["\u0DF2", "\u0DF3"], "\u0E31", ["\u0E34", "\u0E3A"], ["\u0E47", "\u0E4E"], ["\u0E50", "\u0E59"], "\u0EB1", ["\u0EB4", "\u0EB9"], ["\u0EBB", "\u0EBC"], ["\u0EC8", "\u0ECD"], ["\u0ED0", "\u0ED9"], ["\u0F18", "\u0F19"], ["\u0F20", "\u0F29"], "\u0F35", "\u0F37", "\u0F39", ["\u0F3E", "\u0F3F"], ["\u0F71", "\u0F84"], ["\u0F86", "\u0F87"], ["\u0F8D", "\u0F97"], ["\u0F99", "\u0FBC"], "\u0FC6", ["\u102B", "\u103E"], ["\u1040", "\u1049"], ["\u1056", "\u1059"], ["\u105E", "\u1060"], ["\u1062", "\u1064"], ["\u1067", "\u106D"], ["\u1071", "\u1074"], ["\u1082", "\u108D"], ["\u108F", "\u109D"], ["\u135D", "\u135F"], ["\u1712", "\u1714"], ["\u1732", "\u1734"], ["\u1752", "\u1753"], ["\u1772", "\u1773"], ["\u17B4", "\u17D3"], "\u17DD", ["\u17E0", "\u17E9"], ["\u180B", "\u180D"], ["\u1810", "\u1819"], "\u18A9", ["\u1920", "\u192B"], ["\u1930", "\u193B"], ["\u1946", "\u194F"], ["\u19D0", "\u19D9"], ["\u1A17", "\u1A1B"], ["\u1A55", "\u1A5E"], ["\u1A60", "\u1A7C"], ["\u1A7F", "\u1A89"], ["\u1A90", "\u1A99"], ["\u1AB0", "\u1ABD"], ["\u1B00", "\u1B04"], ["\u1B34", "\u1B44"], ["\u1B50", "\u1B59"], ["\u1B6B", "\u1B73"], ["\u1B80", "\u1B82"], ["\u1BA1", "\u1BAD"], ["\u1BB0", "\u1BB9"], ["\u1BE6", "\u1BF3"], ["\u1C24", "\u1C37"], ["\u1C40", "\u1C49"], ["\u1C50", "\u1C59"], ["\u1CD0", "\u1CD2"], ["\u1CD4", "\u1CE8"], "\u1CED", ["\u1CF2", "\u1CF4"], ["\u1CF8", "\u1CF9"], ["\u1DC0", "\u1DF5"], ["\u1DFC", "\u1DFF"], ["\u200C", "\u200D"], ["\u203F", "\u2040"], "\u2054", ["\u20D0", "\u20DC"], "\u20E1", ["\u20E5", "\u20F0"], ["\u2CEF", "\u2CF1"], "\u2D7F", ["\u2DE0", "\u2DFF"], ["\u302A", "\u302F"], ["\u3099", "\u309A"], ["\uA620", "\uA629"], "\uA66F", ["\uA674", "\uA67D"], ["\uA69E", "\uA69F"], ["\uA6F0", "\uA6F1"], "\uA802", "\uA806", "\uA80B", ["\uA823", "\uA827"], ["\uA880", "\uA881"], ["\uA8B4", "\uA8C4"], ["\uA8D0", "\uA8D9"], ["\uA8E0", "\uA8F1"], ["\uA900", "\uA909"], ["\uA926", "\uA92D"], ["\uA947", "\uA953"], ["\uA980", "\uA983"], ["\uA9B3", "\uA9C0"], ["\uA9D0", "\uA9D9"], "\uA9E5", ["\uA9F0", "\uA9F9"], ["\uAA29", "\uAA36"], "\uAA43", ["\uAA4C", "\uAA4D"], ["\uAA50", "\uAA59"], ["\uAA7B", "\uAA7D"], "\uAAB0", ["\uAAB2", "\uAAB4"], ["\uAAB7", "\uAAB8"], ["\uAABE", "\uAABF"], "\uAAC1", ["\uAAEB", "\uAAEF"], ["\uAAF5", "\uAAF6"], ["\uABE3", "\uABEA"], ["\uABEC", "\uABED"], ["\uABF0", "\uABF9"], "\uFB1E", ["\uFE00", "\uFE0F"], ["\uFE20", "\uFE2F"], ["\uFE33", "\uFE34"], ["\uFE4D", "\uFE4F"], ["\uFF10", "\uFF19"], "\uFF3F"], false, false);
+ var peg$e96 = peg$classExpectation([["A", "Z"], ["a", "z"], "\xAA", "\xB5", "\xBA", ["\xC0", "\xD6"], ["\xD8", "\xF6"], ["\xF8", "\u02C1"], ["\u02C6", "\u02D1"], ["\u02E0", "\u02E4"], "\u02EC", "\u02EE", ["\u0370", "\u0374"], ["\u0376", "\u0377"], ["\u037A", "\u037D"], "\u037F", "\u0386", ["\u0388", "\u038A"], "\u038C", ["\u038E", "\u03A1"], ["\u03A3", "\u03F5"], ["\u03F7", "\u0481"], ["\u048A", "\u052F"], ["\u0531", "\u0556"], "\u0559", ["\u0561", "\u0587"], ["\u05D0", "\u05EA"], ["\u05F0", "\u05F2"], ["\u0620", "\u064A"], ["\u066E", "\u066F"], ["\u0671", "\u06D3"], "\u06D5", ["\u06E5", "\u06E6"], ["\u06EE", "\u06EF"], ["\u06FA", "\u06FC"], "\u06FF", "\u0710", ["\u0712", "\u072F"], ["\u074D", "\u07A5"], "\u07B1", ["\u07CA", "\u07EA"], ["\u07F4", "\u07F5"], "\u07FA", ["\u0800", "\u0815"], "\u081A", "\u0824", "\u0828", ["\u0840", "\u0858"], ["\u08A0", "\u08B4"], ["\u0904", "\u0939"], "\u093D", "\u0950", ["\u0958", "\u0961"], ["\u0971", "\u0980"], ["\u0985", "\u098C"], ["\u098F", "\u0990"], ["\u0993", "\u09A8"], ["\u09AA", "\u09B0"], "\u09B2", ["\u09B6", "\u09B9"], "\u09BD", "\u09CE", ["\u09DC", "\u09DD"], ["\u09DF", "\u09E1"], ["\u09F0", "\u09F1"], ["\u0A05", "\u0A0A"], ["\u0A0F", "\u0A10"], ["\u0A13", "\u0A28"], ["\u0A2A", "\u0A30"], ["\u0A32", "\u0A33"], ["\u0A35", "\u0A36"], ["\u0A38", "\u0A39"], ["\u0A59", "\u0A5C"], "\u0A5E", ["\u0A72", "\u0A74"], ["\u0A85", "\u0A8D"], ["\u0A8F", "\u0A91"], ["\u0A93", "\u0AA8"], ["\u0AAA", "\u0AB0"], ["\u0AB2", "\u0AB3"], ["\u0AB5", "\u0AB9"], "\u0ABD", "\u0AD0", ["\u0AE0", "\u0AE1"], "\u0AF9", ["\u0B05", "\u0B0C"], ["\u0B0F", "\u0B10"], ["\u0B13", "\u0B28"], ["\u0B2A", "\u0B30"], ["\u0B32", "\u0B33"], ["\u0B35", "\u0B39"], "\u0B3D", ["\u0B5C", "\u0B5D"], ["\u0B5F", "\u0B61"], "\u0B71", "\u0B83", ["\u0B85", "\u0B8A"], ["\u0B8E", "\u0B90"], ["\u0B92", "\u0B95"], ["\u0B99", "\u0B9A"], "\u0B9C", ["\u0B9E", "\u0B9F"], ["\u0BA3", "\u0BA4"], ["\u0BA8", "\u0BAA"], ["\u0BAE", "\u0BB9"], "\u0BD0", ["\u0C05", "\u0C0C"], ["\u0C0E", "\u0C10"], ["\u0C12", "\u0C28"], ["\u0C2A", "\u0C39"], "\u0C3D", ["\u0C58", "\u0C5A"], ["\u0C60", "\u0C61"], ["\u0C85", "\u0C8C"], ["\u0C8E", "\u0C90"], ["\u0C92", "\u0CA8"], ["\u0CAA", "\u0CB3"], ["\u0CB5", "\u0CB9"], "\u0CBD", "\u0CDE", ["\u0CE0", "\u0CE1"], ["\u0CF1", "\u0CF2"], ["\u0D05", "\u0D0C"], ["\u0D0E", "\u0D10"], ["\u0D12", "\u0D3A"], "\u0D3D", "\u0D4E", ["\u0D5F", "\u0D61"], ["\u0D7A", "\u0D7F"], ["\u0D85", "\u0D96"], ["\u0D9A", "\u0DB1"], ["\u0DB3", "\u0DBB"], "\u0DBD", ["\u0DC0", "\u0DC6"], ["\u0E01", "\u0E30"], ["\u0E32", "\u0E33"], ["\u0E40", "\u0E46"], ["\u0E81", "\u0E82"], "\u0E84", ["\u0E87", "\u0E88"], "\u0E8A", "\u0E8D", ["\u0E94", "\u0E97"], ["\u0E99", "\u0E9F"], ["\u0EA1", "\u0EA3"], "\u0EA5", "\u0EA7", ["\u0EAA", "\u0EAB"], ["\u0EAD", "\u0EB0"], ["\u0EB2", "\u0EB3"], "\u0EBD", ["\u0EC0", "\u0EC4"], "\u0EC6", ["\u0EDC", "\u0EDF"], "\u0F00", ["\u0F40", "\u0F47"], ["\u0F49", "\u0F6C"], ["\u0F88", "\u0F8C"], ["\u1000", "\u102A"], "\u103F", ["\u1050", "\u1055"], ["\u105A", "\u105D"], "\u1061", ["\u1065", "\u1066"], ["\u106E", "\u1070"], ["\u1075", "\u1081"], "\u108E", ["\u10A0", "\u10C5"], "\u10C7", "\u10CD", ["\u10D0", "\u10FA"], ["\u10FC", "\u1248"], ["\u124A", "\u124D"], ["\u1250", "\u1256"], "\u1258", ["\u125A", "\u125D"], ["\u1260", "\u1288"], ["\u128A", "\u128D"], ["\u1290", "\u12B0"], ["\u12B2", "\u12B5"], ["\u12B8", "\u12BE"], "\u12C0", ["\u12C2", "\u12C5"], ["\u12C8", "\u12D6"], ["\u12D8", "\u1310"], ["\u1312", "\u1315"], ["\u1318", "\u135A"], ["\u1380", "\u138F"], ["\u13A0", "\u13F5"], ["\u13F8", "\u13FD"], ["\u1401", "\u166C"], ["\u166F", "\u167F"], ["\u1681", "\u169A"], ["\u16A0", "\u16EA"], ["\u16EE", "\u16F8"], ["\u1700", "\u170C"], ["\u170E", "\u1711"], ["\u1720", "\u1731"], ["\u1740", "\u1751"], ["\u1760", "\u176C"], ["\u176E", "\u1770"], ["\u1780", "\u17B3"], "\u17D7", "\u17DC", ["\u1820", "\u1877"], ["\u1880", "\u18A8"], "\u18AA", ["\u18B0", "\u18F5"], ["\u1900", "\u191E"], ["\u1950", "\u196D"], ["\u1970", "\u1974"], ["\u1980", "\u19AB"], ["\u19B0", "\u19C9"], ["\u1A00", "\u1A16"], ["\u1A20", "\u1A54"], "\u1AA7", ["\u1B05", "\u1B33"], ["\u1B45", "\u1B4B"], ["\u1B83", "\u1BA0"], ["\u1BAE", "\u1BAF"], ["\u1BBA", "\u1BE5"], ["\u1C00", "\u1C23"], ["\u1C4D", "\u1C4F"], ["\u1C5A", "\u1C7D"], ["\u1CE9", "\u1CEC"], ["\u1CEE", "\u1CF1"], ["\u1CF5", "\u1CF6"], ["\u1D00", "\u1DBF"], ["\u1E00", "\u1F15"], ["\u1F18", "\u1F1D"], ["\u1F20", "\u1F45"], ["\u1F48", "\u1F4D"], ["\u1F50", "\u1F57"], "\u1F59", "\u1F5B", "\u1F5D", ["\u1F5F", "\u1F7D"], ["\u1F80", "\u1FB4"], ["\u1FB6", "\u1FBC"], "\u1FBE", ["\u1FC2", "\u1FC4"], ["\u1FC6", "\u1FCC"], ["\u1FD0", "\u1FD3"], ["\u1FD6", "\u1FDB"], ["\u1FE0", "\u1FEC"], ["\u1FF2", "\u1FF4"], ["\u1FF6", "\u1FFC"], "\u2071", "\u207F", ["\u2090", "\u209C"], "\u2102", "\u2107", ["\u210A", "\u2113"], "\u2115", ["\u2119", "\u211D"], "\u2124", "\u2126", "\u2128", ["\u212A", "\u212D"], ["\u212F", "\u2139"], ["\u213C", "\u213F"], ["\u2145", "\u2149"], "\u214E", ["\u2160", "\u2188"], ["\u2C00", "\u2C2E"], ["\u2C30", "\u2C5E"], ["\u2C60", "\u2CE4"], ["\u2CEB", "\u2CEE"], ["\u2CF2", "\u2CF3"], ["\u2D00", "\u2D25"], "\u2D27", "\u2D2D", ["\u2D30", "\u2D67"], "\u2D6F", ["\u2D80", "\u2D96"], ["\u2DA0", "\u2DA6"], ["\u2DA8", "\u2DAE"], ["\u2DB0", "\u2DB6"], ["\u2DB8", "\u2DBE"], ["\u2DC0", "\u2DC6"], ["\u2DC8", "\u2DCE"], ["\u2DD0", "\u2DD6"], ["\u2DD8", "\u2DDE"], "\u2E2F", ["\u3005", "\u3007"], ["\u3021", "\u3029"], ["\u3031", "\u3035"], ["\u3038", "\u303C"], ["\u3041", "\u3096"], ["\u309D", "\u309F"], ["\u30A1", "\u30FA"], ["\u30FC", "\u30FF"], ["\u3105", "\u312D"], ["\u3131", "\u318E"], ["\u31A0", "\u31BA"], ["\u31F0", "\u31FF"], ["\u3400", "\u4DB5"], ["\u4E00", "\u9FD5"], ["\uA000", "\uA48C"], ["\uA4D0", "\uA4FD"], ["\uA500", "\uA60C"], ["\uA610", "\uA61F"], ["\uA62A", "\uA62B"], ["\uA640", "\uA66E"], ["\uA67F", "\uA69D"], ["\uA6A0", "\uA6EF"], ["\uA717", "\uA71F"], ["\uA722", "\uA788"], ["\uA78B", "\uA7AD"], ["\uA7B0", "\uA7B7"], ["\uA7F7", "\uA801"], ["\uA803", "\uA805"], ["\uA807", "\uA80A"], ["\uA80C", "\uA822"], ["\uA840", "\uA873"], ["\uA882", "\uA8B3"], ["\uA8F2", "\uA8F7"], "\uA8FB", "\uA8FD", ["\uA90A", "\uA925"], ["\uA930", "\uA946"], ["\uA960", "\uA97C"], ["\uA984", "\uA9B2"], "\uA9CF", ["\uA9E0", "\uA9E4"], ["\uA9E6", "\uA9EF"], ["\uA9FA", "\uA9FE"], ["\uAA00", "\uAA28"], ["\uAA40", "\uAA42"], ["\uAA44", "\uAA4B"], ["\uAA60", "\uAA76"], "\uAA7A", ["\uAA7E", "\uAAAF"], "\uAAB1", ["\uAAB5", "\uAAB6"], ["\uAAB9", "\uAABD"], "\uAAC0", "\uAAC2", ["\uAADB", "\uAADD"], ["\uAAE0", "\uAAEA"], ["\uAAF2", "\uAAF4"], ["\uAB01", "\uAB06"], ["\uAB09", "\uAB0E"], ["\uAB11", "\uAB16"], ["\uAB20", "\uAB26"], ["\uAB28", "\uAB2E"], ["\uAB30", "\uAB5A"], ["\uAB5C", "\uAB65"], ["\uAB70", "\uABE2"], ["\uAC00", "\uD7A3"], ["\uD7B0", "\uD7C6"], ["\uD7CB", "\uD7FB"], ["\uF900", "\uFA6D"], ["\uFA70", "\uFAD9"], ["\uFB00", "\uFB06"], ["\uFB13", "\uFB17"], "\uFB1D", ["\uFB1F", "\uFB28"], ["\uFB2A", "\uFB36"], ["\uFB38", "\uFB3C"], "\uFB3E", ["\uFB40", "\uFB41"], ["\uFB43", "\uFB44"], ["\uFB46", "\uFBB1"], ["\uFBD3", "\uFD3D"], ["\uFD50", "\uFD8F"], ["\uFD92", "\uFDC7"], ["\uFDF0", "\uFDFB"], ["\uFE70", "\uFE74"], ["\uFE76", "\uFEFC"], ["\uFF21", "\uFF3A"], ["\uFF41", "\uFF5A"], ["\uFF66", "\uFFBE"], ["\uFFC2", "\uFFC7"], ["\uFFCA", "\uFFCF"], ["\uFFD2", "\uFFD7"], ["\uFFDA", "\uFFDC"]], false, false);
+ var peg$e97 = peg$classExpectation([["\u0300", "\u036F"], ["\u0483", "\u0487"], ["\u0591", "\u05BD"], "\u05BF", ["\u05C1", "\u05C2"], ["\u05C4", "\u05C5"], "\u05C7", ["\u0610", "\u061A"], ["\u064B", "\u065F"], "\u0670", ["\u06D6", "\u06DC"], ["\u06DF", "\u06E4"], ["\u06E7", "\u06E8"], ["\u06EA", "\u06ED"], "\u0711", ["\u0730", "\u074A"], ["\u07A6", "\u07B0"], ["\u07EB", "\u07F3"], ["\u0816", "\u0819"], ["\u081B", "\u0823"], ["\u0825", "\u0827"], ["\u0829", "\u082D"], ["\u0859", "\u085B"], ["\u08E3", "\u0903"], ["\u093A", "\u093C"], ["\u093E", "\u094F"], ["\u0951", "\u0957"], ["\u0962", "\u0963"], ["\u0981", "\u0983"], "\u09BC", ["\u09BE", "\u09C4"], ["\u09C7", "\u09C8"], ["\u09CB", "\u09CD"], "\u09D7", ["\u09E2", "\u09E3"], ["\u0A01", "\u0A03"], "\u0A3C", ["\u0A3E", "\u0A42"], ["\u0A47", "\u0A48"], ["\u0A4B", "\u0A4D"], "\u0A51", ["\u0A70", "\u0A71"], "\u0A75", ["\u0A81", "\u0A83"], "\u0ABC", ["\u0ABE", "\u0AC5"], ["\u0AC7", "\u0AC9"], ["\u0ACB", "\u0ACD"], ["\u0AE2", "\u0AE3"], ["\u0B01", "\u0B03"], "\u0B3C", ["\u0B3E", "\u0B44"], ["\u0B47", "\u0B48"], ["\u0B4B", "\u0B4D"], ["\u0B56", "\u0B57"], ["\u0B62", "\u0B63"], "\u0B82", ["\u0BBE", "\u0BC2"], ["\u0BC6", "\u0BC8"], ["\u0BCA", "\u0BCD"], "\u0BD7", ["\u0C00", "\u0C03"], ["\u0C3E", "\u0C44"], ["\u0C46", "\u0C48"], ["\u0C4A", "\u0C4D"], ["\u0C55", "\u0C56"], ["\u0C62", "\u0C63"], ["\u0C81", "\u0C83"], "\u0CBC", ["\u0CBE", "\u0CC4"], ["\u0CC6", "\u0CC8"], ["\u0CCA", "\u0CCD"], ["\u0CD5", "\u0CD6"], ["\u0CE2", "\u0CE3"], ["\u0D01", "\u0D03"], ["\u0D3E", "\u0D44"], ["\u0D46", "\u0D48"], ["\u0D4A", "\u0D4D"], "\u0D57", ["\u0D62", "\u0D63"], ["\u0D82", "\u0D83"], "\u0DCA", ["\u0DCF", "\u0DD4"], "\u0DD6", ["\u0DD8", "\u0DDF"], ["\u0DF2", "\u0DF3"], "\u0E31", ["\u0E34", "\u0E3A"], ["\u0E47", "\u0E4E"], "\u0EB1", ["\u0EB4", "\u0EB9"], ["\u0EBB", "\u0EBC"], ["\u0EC8", "\u0ECD"], ["\u0F18", "\u0F19"], "\u0F35", "\u0F37", "\u0F39", ["\u0F3E", "\u0F3F"], ["\u0F71", "\u0F84"], ["\u0F86", "\u0F87"], ["\u0F8D", "\u0F97"], ["\u0F99", "\u0FBC"], "\u0FC6", ["\u102B", "\u103E"], ["\u1056", "\u1059"], ["\u105E", "\u1060"], ["\u1062", "\u1064"], ["\u1067", "\u106D"], ["\u1071", "\u1074"], ["\u1082", "\u108D"], "\u108F", ["\u109A", "\u109D"], ["\u135D", "\u135F"], ["\u1712", "\u1714"], ["\u1732", "\u1734"], ["\u1752", "\u1753"], ["\u1772", "\u1773"], ["\u17B4", "\u17D3"], "\u17DD", ["\u180B", "\u180D"], "\u18A9", ["\u1920", "\u192B"], ["\u1930", "\u193B"], ["\u1A17", "\u1A1B"], ["\u1A55", "\u1A5E"], ["\u1A60", "\u1A7C"], "\u1A7F", ["\u1AB0", "\u1ABD"], ["\u1B00", "\u1B04"], ["\u1B34", "\u1B44"], ["\u1B6B", "\u1B73"], ["\u1B80", "\u1B82"], ["\u1BA1", "\u1BAD"], ["\u1BE6", "\u1BF3"], ["\u1C24", "\u1C37"], ["\u1CD0", "\u1CD2"], ["\u1CD4", "\u1CE8"], "\u1CED", ["\u1CF2", "\u1CF4"], ["\u1CF8", "\u1CF9"], ["\u1DC0", "\u1DF5"], ["\u1DFC", "\u1DFF"], ["\u20D0", "\u20DC"], "\u20E1", ["\u20E5", "\u20F0"], ["\u2CEF", "\u2CF1"], "\u2D7F", ["\u2DE0", "\u2DFF"], ["\u302A", "\u302F"], ["\u3099", "\u309A"], "\uA66F", ["\uA674", "\uA67D"], ["\uA69E", "\uA69F"], ["\uA6F0", "\uA6F1"], "\uA802", "\uA806", "\uA80B", ["\uA823", "\uA827"], ["\uA880", "\uA881"], ["\uA8B4", "\uA8C4"], ["\uA8E0", "\uA8F1"], ["\uA926", "\uA92D"], ["\uA947", "\uA953"], ["\uA980", "\uA983"], ["\uA9B3", "\uA9C0"], "\uA9E5", ["\uAA29", "\uAA36"], "\uAA43", ["\uAA4C", "\uAA4D"], ["\uAA7B", "\uAA7D"], "\uAAB0", ["\uAAB2", "\uAAB4"], ["\uAAB7", "\uAAB8"], ["\uAABE", "\uAABF"], "\uAAC1", ["\uAAEB", "\uAAEF"], ["\uAAF5", "\uAAF6"], ["\uABE3", "\uABEA"], ["\uABEC", "\uABED"], "\uFB1E", ["\uFE00", "\uFE0F"], ["\uFE20", "\uFE2F"]], false, false);
+ var peg$e98 = peg$otherExpectation("line terminator");
+ var peg$e99 = peg$classExpectation(["\n", "\r", "\u2028", "\u2029"], false, false);
+ var peg$e100 = peg$otherExpectation("end of line");
+ var peg$e101 = peg$literalExpectation("\n", false);
+ var peg$e102 = peg$literalExpectation("\r\n", false);
+ var peg$e103 = peg$classExpectation(["\r", ["\u2028", "\u2029"]], false, false);
+ var peg$e104 = peg$otherExpectation("whitespace");
+ var peg$e105 = peg$classExpectation(["\t", ["\v", "\f"], " ", "\xA0", "\u1680", ["\u2000", "\u200A"], "\u202F", "\u205F", "\u3000", "\uFEFF"], false, false);
+ var peg$e106 = peg$otherExpectation("changeBg statement");
+ var peg$e107 = peg$literalExpectation(":", false);
+ var peg$e108 = peg$otherExpectation("changeFigure statement");
+ var peg$e109 = peg$otherExpectation("bgm statement");
+ var peg$e110 = peg$otherExpectation("video statement");
+ var peg$e111 = peg$otherExpectation("pixi statement");
+ var peg$e112 = peg$otherExpectation("pixiInit statement");
+ var peg$e113 = peg$otherExpectation("intro statement");
+ var peg$e114 = peg$otherExpectation("miniAvatar statement");
+ var peg$e115 = peg$otherExpectation("changeScene statement");
+ var peg$e116 = peg$otherExpectation("choose statement");
+ var peg$e117 = peg$otherExpectation("choices");
+ var peg$e118 = peg$literalExpectation("|", false);
+ var peg$e119 = peg$otherExpectation("choice");
+ var peg$e120 = peg$literalExpectation("(", false);
+ var peg$e121 = peg$literalExpectation(")", false);
+ var peg$e122 = peg$literalExpectation("[", false);
+ var peg$e123 = peg$literalExpectation("]", false);
+ var peg$e124 = peg$literalExpectation("->", false);
+ var peg$e125 = peg$otherExpectation("end statement");
+ var peg$e126 = peg$otherExpectation("setComplexAnimation statement");
+ var peg$e127 = peg$otherExpectation("setFilter statement");
+ var peg$e128 = peg$otherExpectation("label statement");
+ var peg$e129 = peg$otherExpectation("jumpLabel statement");
+ var peg$e130 = peg$otherExpectation("setVar statement");
+ var peg$e131 = peg$otherExpectation("internal argument");
+ var peg$e132 = peg$classExpectation([")", ";"], false, false);
+ var peg$e133 = peg$otherExpectation("callScene statement");
+ var peg$e134 = peg$otherExpectation("showVars statement");
+ var peg$e135 = peg$otherExpectation("unlockCg statement");
+ var peg$e136 = peg$otherExpectation("unlockBgm statement");
+ var peg$e137 = peg$otherExpectation("filmMode statement");
+ var peg$e138 = peg$otherExpectation("setTextbox statement");
+ var peg$e139 = peg$otherExpectation("setAnimation statement");
+ var peg$e140 = peg$otherExpectation("setTransition statement");
+ var peg$e141 = peg$otherExpectation("playEffect statement");
+ var peg$e142 = peg$otherExpectation("setTempAnimation statement");
+ var peg$e143 = peg$otherExpectation("setTransform statement");
+ var peg$e144 = peg$otherExpectation("getUserInput statement");
+ var peg$e145 = peg$otherExpectation("say statement");
+ var peg$e146 = peg$classExpectation([[":", ";"]], false, false);
+ var peg$e147 = peg$otherExpectation("statement");
+ var peg$e148 = peg$classExpectation([["a", "z"], "\xB5", ["\xDF", "\xF6"], ["\xF8", "\xFF"], "\u0101", "\u0103", "\u0105", "\u0107", "\u0109", "\u010B", "\u010D", "\u010F", "\u0111", "\u0113", "\u0115", "\u0117", "\u0119", "\u011B", "\u011D", "\u011F", "\u0121", "\u0123", "\u0125", "\u0127", "\u0129", "\u012B", "\u012D", "\u012F", "\u0131", "\u0133", "\u0135", ["\u0137", "\u0138"], "\u013A", "\u013C", "\u013E", "\u0140", "\u0142", "\u0144", "\u0146", ["\u0148", "\u0149"], "\u014B", "\u014D", "\u014F", "\u0151", "\u0153", "\u0155", "\u0157", "\u0159", "\u015B", "\u015D", "\u015F", "\u0161", "\u0163", "\u0165", "\u0167", "\u0169", "\u016B", "\u016D", "\u016F", "\u0171", "\u0173", "\u0175", "\u0177", "\u017A", "\u017C", ["\u017E", "\u0180"], "\u0183", "\u0185", "\u0188", ["\u018C", "\u018D"], "\u0192", "\u0195", ["\u0199", "\u019B"], "\u019E", "\u01A1", "\u01A3", "\u01A5", "\u01A8", ["\u01AA", "\u01AB"], "\u01AD", "\u01B0", "\u01B4", "\u01B6", ["\u01B9", "\u01BA"], ["\u01BD", "\u01BF"], "\u01C6", "\u01C9", "\u01CC", "\u01CE", "\u01D0", "\u01D2", "\u01D4", "\u01D6", "\u01D8", "\u01DA", ["\u01DC", "\u01DD"], "\u01DF", "\u01E1", "\u01E3", "\u01E5", "\u01E7", "\u01E9", "\u01EB", "\u01ED", ["\u01EF", "\u01F0"], "\u01F3", "\u01F5", "\u01F9", "\u01FB", "\u01FD", "\u01FF", "\u0201", "\u0203", "\u0205", "\u0207", "\u0209", "\u020B", "\u020D", "\u020F", "\u0211", "\u0213", "\u0215", "\u0217", "\u0219", "\u021B", "\u021D", "\u021F", "\u0221", "\u0223", "\u0225", "\u0227", "\u0229", "\u022B", "\u022D", "\u022F", "\u0231", ["\u0233", "\u0239"], "\u023C", ["\u023F", "\u0240"], "\u0242", "\u0247", "\u0249", "\u024B", "\u024D", ["\u024F", "\u0293"], ["\u0295", "\u02AF"], "\u0371", "\u0373", "\u0377", ["\u037B", "\u037D"], "\u0390", ["\u03AC", "\u03CE"], ["\u03D0", "\u03D1"], ["\u03D5", "\u03D7"], "\u03D9", "\u03DB", "\u03DD", "\u03DF", "\u03E1", "\u03E3", "\u03E5", "\u03E7", "\u03E9", "\u03EB", "\u03ED", ["\u03EF", "\u03F3"], "\u03F5", "\u03F8", ["\u03FB", "\u03FC"], ["\u0430", "\u045F"], "\u0461", "\u0463", "\u0465", "\u0467", "\u0469", "\u046B", "\u046D", "\u046F", "\u0471", "\u0473", "\u0475", "\u0477", "\u0479", "\u047B", "\u047D", "\u047F", "\u0481", "\u048B", "\u048D", "\u048F", "\u0491", "\u0493", "\u0495", "\u0497", "\u0499", "\u049B", "\u049D", "\u049F", "\u04A1", "\u04A3", "\u04A5", "\u04A7", "\u04A9", "\u04AB", "\u04AD", "\u04AF", "\u04B1", "\u04B3", "\u04B5", "\u04B7", "\u04B9", "\u04BB", "\u04BD", "\u04BF", "\u04C2", "\u04C4", "\u04C6", "\u04C8", "\u04CA", "\u04CC", ["\u04CE", "\u04CF"], "\u04D1", "\u04D3", "\u04D5", "\u04D7", "\u04D9", "\u04DB", "\u04DD", "\u04DF", "\u04E1", "\u04E3", "\u04E5", "\u04E7", "\u04E9", "\u04EB", "\u04ED", "\u04EF", "\u04F1", "\u04F3", "\u04F5", "\u04F7", "\u04F9", "\u04FB", "\u04FD", "\u04FF", "\u0501", "\u0503", "\u0505", "\u0507", "\u0509", "\u050B", "\u050D", "\u050F", "\u0511", "\u0513", "\u0515", "\u0517", "\u0519", "\u051B", "\u051D", "\u051F", "\u0521", "\u0523", "\u0525", "\u0527", "\u0529", "\u052B", "\u052D", "\u052F", ["\u0561", "\u0587"], ["\u13F8", "\u13FD"], ["\u1D00", "\u1D2B"], ["\u1D6B", "\u1D77"], ["\u1D79", "\u1D9A"], "\u1E01", "\u1E03", "\u1E05", "\u1E07", "\u1E09", "\u1E0B", "\u1E0D", "\u1E0F", "\u1E11", "\u1E13", "\u1E15", "\u1E17", "\u1E19", "\u1E1B", "\u1E1D", "\u1E1F", "\u1E21", "\u1E23", "\u1E25", "\u1E27", "\u1E29", "\u1E2B", "\u1E2D", "\u1E2F", "\u1E31", "\u1E33", "\u1E35", "\u1E37", "\u1E39", "\u1E3B", "\u1E3D", "\u1E3F", "\u1E41", "\u1E43", "\u1E45", "\u1E47", "\u1E49", "\u1E4B", "\u1E4D", "\u1E4F", "\u1E51", "\u1E53", "\u1E55", "\u1E57", "\u1E59", "\u1E5B", "\u1E5D", "\u1E5F", "\u1E61", "\u1E63", "\u1E65", "\u1E67", "\u1E69", "\u1E6B", "\u1E6D", "\u1E6F", "\u1E71", "\u1E73", "\u1E75", "\u1E77", "\u1E79", "\u1E7B", "\u1E7D", "\u1E7F", "\u1E81", "\u1E83", "\u1E85", "\u1E87", "\u1E89", "\u1E8B", "\u1E8D", "\u1E8F", "\u1E91", "\u1E93", ["\u1E95", "\u1E9D"], "\u1E9F", "\u1EA1", "\u1EA3", "\u1EA5", "\u1EA7", "\u1EA9", "\u1EAB", "\u1EAD", "\u1EAF", "\u1EB1", "\u1EB3", "\u1EB5", "\u1EB7", "\u1EB9", "\u1EBB", "\u1EBD", "\u1EBF", "\u1EC1", "\u1EC3", "\u1EC5", "\u1EC7", "\u1EC9", "\u1ECB", "\u1ECD", "\u1ECF", "\u1ED1", "\u1ED3", "\u1ED5", "\u1ED7", "\u1ED9", "\u1EDB", "\u1EDD", "\u1EDF", "\u1EE1", "\u1EE3", "\u1EE5", "\u1EE7", "\u1EE9", "\u1EEB", "\u1EED", "\u1EEF", "\u1EF1", "\u1EF3", "\u1EF5", "\u1EF7", "\u1EF9", "\u1EFB", "\u1EFD", ["\u1EFF", "\u1F07"], ["\u1F10", "\u1F15"], ["\u1F20", "\u1F27"], ["\u1F30", "\u1F37"], ["\u1F40", "\u1F45"], ["\u1F50", "\u1F57"], ["\u1F60", "\u1F67"], ["\u1F70", "\u1F7D"], ["\u1F80", "\u1F87"], ["\u1F90", "\u1F97"], ["\u1FA0", "\u1FA7"], ["\u1FB0", "\u1FB4"], ["\u1FB6", "\u1FB7"], "\u1FBE", ["\u1FC2", "\u1FC4"], ["\u1FC6", "\u1FC7"], ["\u1FD0", "\u1FD3"], ["\u1FD6", "\u1FD7"], ["\u1FE0", "\u1FE7"], ["\u1FF2", "\u1FF4"], ["\u1FF6", "\u1FF7"], "\u210A", ["\u210E", "\u210F"], "\u2113", "\u212F", "\u2134", "\u2139", ["\u213C", "\u213D"], ["\u2146", "\u2149"], "\u214E", "\u2184", ["\u2C30", "\u2C5E"], "\u2C61", ["\u2C65", "\u2C66"], "\u2C68", "\u2C6A", "\u2C6C", "\u2C71", ["\u2C73", "\u2C74"], ["\u2C76", "\u2C7B"], "\u2C81", "\u2C83", "\u2C85", "\u2C87", "\u2C89", "\u2C8B", "\u2C8D", "\u2C8F", "\u2C91", "\u2C93", "\u2C95", "\u2C97", "\u2C99", "\u2C9B", "\u2C9D", "\u2C9F", "\u2CA1", "\u2CA3", "\u2CA5", "\u2CA7", "\u2CA9", "\u2CAB", "\u2CAD", "\u2CAF", "\u2CB1", "\u2CB3", "\u2CB5", "\u2CB7", "\u2CB9", "\u2CBB", "\u2CBD", "\u2CBF", "\u2CC1", "\u2CC3", "\u2CC5", "\u2CC7", "\u2CC9", "\u2CCB", "\u2CCD", "\u2CCF", "\u2CD1", "\u2CD3", "\u2CD5", "\u2CD7", "\u2CD9", "\u2CDB", "\u2CDD", "\u2CDF", "\u2CE1", ["\u2CE3", "\u2CE4"], "\u2CEC", "\u2CEE", "\u2CF3", ["\u2D00", "\u2D25"], "\u2D27", "\u2D2D", "\uA641", "\uA643", "\uA645", "\uA647", "\uA649", "\uA64B", "\uA64D", "\uA64F", "\uA651", "\uA653", "\uA655", "\uA657", "\uA659", "\uA65B", "\uA65D", "\uA65F", "\uA661", "\uA663", "\uA665", "\uA667", "\uA669", "\uA66B", "\uA66D", "\uA681", "\uA683", "\uA685", "\uA687", "\uA689", "\uA68B", "\uA68D", "\uA68F", "\uA691", "\uA693", "\uA695", "\uA697", "\uA699", "\uA69B", "\uA723", "\uA725", "\uA727", "\uA729", "\uA72B", "\uA72D", ["\uA72F", "\uA731"], "\uA733", "\uA735", "\uA737", "\uA739", "\uA73B", "\uA73D", "\uA73F", "\uA741", "\uA743", "\uA745", "\uA747", "\uA749", "\uA74B", "\uA74D", "\uA74F", "\uA751", "\uA753", "\uA755", "\uA757", "\uA759", "\uA75B", "\uA75D", "\uA75F", "\uA761", "\uA763", "\uA765", "\uA767", "\uA769", "\uA76B", "\uA76D", "\uA76F", ["\uA771", "\uA778"], "\uA77A", "\uA77C", "\uA77F", "\uA781", "\uA783", "\uA785", "\uA787", "\uA78C", "\uA78E", "\uA791", ["\uA793", "\uA795"], "\uA797", "\uA799", "\uA79B", "\uA79D", "\uA79F", "\uA7A1", "\uA7A3", "\uA7A5", "\uA7A7", "\uA7A9", "\uA7B5", "\uA7B7", "\uA7FA", ["\uAB30", "\uAB5A"], ["\uAB60", "\uAB65"], ["\uAB70", "\uABBF"], ["\uFB00", "\uFB06"], ["\uFB13", "\uFB17"], ["\uFF41", "\uFF5A"]], false, false);
+ var peg$e149 = peg$classExpectation([["\u02B0", "\u02C1"], ["\u02C6", "\u02D1"], ["\u02E0", "\u02E4"], "\u02EC", "\u02EE", "\u0374", "\u037A", "\u0559", "\u0640", ["\u06E5", "\u06E6"], ["\u07F4", "\u07F5"], "\u07FA", "\u081A", "\u0824", "\u0828", "\u0971", "\u0E46", "\u0EC6", "\u10FC", "\u17D7", "\u1843", "\u1AA7", ["\u1C78", "\u1C7D"], ["\u1D2C", "\u1D6A"], "\u1D78", ["\u1D9B", "\u1DBF"], "\u2071", "\u207F", ["\u2090", "\u209C"], ["\u2C7C", "\u2C7D"], "\u2D6F", "\u2E2F", "\u3005", ["\u3031", "\u3035"], "\u303B", ["\u309D", "\u309E"], ["\u30FC", "\u30FE"], "\uA015", ["\uA4F8", "\uA4FD"], "\uA60C", "\uA67F", ["\uA69C", "\uA69D"], ["\uA717", "\uA71F"], "\uA770", "\uA788", ["\uA7F8", "\uA7F9"], "\uA9CF", "\uA9E6", "\uAA70", "\uAADD", ["\uAAF3", "\uAAF4"], ["\uAB5C", "\uAB5F"], "\uFF70", ["\uFF9E", "\uFF9F"]], false, false);
+ var peg$e150 = peg$classExpectation(["\xAA", "\xBA", "\u01BB", ["\u01C0", "\u01C3"], "\u0294", ["\u05D0", "\u05EA"], ["\u05F0", "\u05F2"], ["\u0620", "\u063F"], ["\u0641", "\u064A"], ["\u066E", "\u066F"], ["\u0671", "\u06D3"], "\u06D5", ["\u06EE", "\u06EF"], ["\u06FA", "\u06FC"], "\u06FF", "\u0710", ["\u0712", "\u072F"], ["\u074D", "\u07A5"], "\u07B1", ["\u07CA", "\u07EA"], ["\u0800", "\u0815"], ["\u0840", "\u0858"], ["\u08A0", "\u08B4"], ["\u0904", "\u0939"], "\u093D", "\u0950", ["\u0958", "\u0961"], ["\u0972", "\u0980"], ["\u0985", "\u098C"], ["\u098F", "\u0990"], ["\u0993", "\u09A8"], ["\u09AA", "\u09B0"], "\u09B2", ["\u09B6", "\u09B9"], "\u09BD", "\u09CE", ["\u09DC", "\u09DD"], ["\u09DF", "\u09E1"], ["\u09F0", "\u09F1"], ["\u0A05", "\u0A0A"], ["\u0A0F", "\u0A10"], ["\u0A13", "\u0A28"], ["\u0A2A", "\u0A30"], ["\u0A32", "\u0A33"], ["\u0A35", "\u0A36"], ["\u0A38", "\u0A39"], ["\u0A59", "\u0A5C"], "\u0A5E", ["\u0A72", "\u0A74"], ["\u0A85", "\u0A8D"], ["\u0A8F", "\u0A91"], ["\u0A93", "\u0AA8"], ["\u0AAA", "\u0AB0"], ["\u0AB2", "\u0AB3"], ["\u0AB5", "\u0AB9"], "\u0ABD", "\u0AD0", ["\u0AE0", "\u0AE1"], "\u0AF9", ["\u0B05", "\u0B0C"], ["\u0B0F", "\u0B10"], ["\u0B13", "\u0B28"], ["\u0B2A", "\u0B30"], ["\u0B32", "\u0B33"], ["\u0B35", "\u0B39"], "\u0B3D", ["\u0B5C", "\u0B5D"], ["\u0B5F", "\u0B61"], "\u0B71", "\u0B83", ["\u0B85", "\u0B8A"], ["\u0B8E", "\u0B90"], ["\u0B92", "\u0B95"], ["\u0B99", "\u0B9A"], "\u0B9C", ["\u0B9E", "\u0B9F"], ["\u0BA3", "\u0BA4"], ["\u0BA8", "\u0BAA"], ["\u0BAE", "\u0BB9"], "\u0BD0", ["\u0C05", "\u0C0C"], ["\u0C0E", "\u0C10"], ["\u0C12", "\u0C28"], ["\u0C2A", "\u0C39"], "\u0C3D", ["\u0C58", "\u0C5A"], ["\u0C60", "\u0C61"], ["\u0C85", "\u0C8C"], ["\u0C8E", "\u0C90"], ["\u0C92", "\u0CA8"], ["\u0CAA", "\u0CB3"], ["\u0CB5", "\u0CB9"], "\u0CBD", "\u0CDE", ["\u0CE0", "\u0CE1"], ["\u0CF1", "\u0CF2"], ["\u0D05", "\u0D0C"], ["\u0D0E", "\u0D10"], ["\u0D12", "\u0D3A"], "\u0D3D", "\u0D4E", ["\u0D5F", "\u0D61"], ["\u0D7A", "\u0D7F"], ["\u0D85", "\u0D96"], ["\u0D9A", "\u0DB1"], ["\u0DB3", "\u0DBB"], "\u0DBD", ["\u0DC0", "\u0DC6"], ["\u0E01", "\u0E30"], ["\u0E32", "\u0E33"], ["\u0E40", "\u0E45"], ["\u0E81", "\u0E82"], "\u0E84", ["\u0E87", "\u0E88"], "\u0E8A", "\u0E8D", ["\u0E94", "\u0E97"], ["\u0E99", "\u0E9F"], ["\u0EA1", "\u0EA3"], "\u0EA5", "\u0EA7", ["\u0EAA", "\u0EAB"], ["\u0EAD", "\u0EB0"], ["\u0EB2", "\u0EB3"], "\u0EBD", ["\u0EC0", "\u0EC4"], ["\u0EDC", "\u0EDF"], "\u0F00", ["\u0F40", "\u0F47"], ["\u0F49", "\u0F6C"], ["\u0F88", "\u0F8C"], ["\u1000", "\u102A"], "\u103F", ["\u1050", "\u1055"], ["\u105A", "\u105D"], "\u1061", ["\u1065", "\u1066"], ["\u106E", "\u1070"], ["\u1075", "\u1081"], "\u108E", ["\u10D0", "\u10FA"], ["\u10FD", "\u1248"], ["\u124A", "\u124D"], ["\u1250", "\u1256"], "\u1258", ["\u125A", "\u125D"], ["\u1260", "\u1288"], ["\u128A", "\u128D"], ["\u1290", "\u12B0"], ["\u12B2", "\u12B5"], ["\u12B8", "\u12BE"], "\u12C0", ["\u12C2", "\u12C5"], ["\u12C8", "\u12D6"], ["\u12D8", "\u1310"], ["\u1312", "\u1315"], ["\u1318", "\u135A"], ["\u1380", "\u138F"], ["\u1401", "\u166C"], ["\u166F", "\u167F"], ["\u1681", "\u169A"], ["\u16A0", "\u16EA"], ["\u16F1", "\u16F8"], ["\u1700", "\u170C"], ["\u170E", "\u1711"], ["\u1720", "\u1731"], ["\u1740", "\u1751"], ["\u1760", "\u176C"], ["\u176E", "\u1770"], ["\u1780", "\u17B3"], "\u17DC", ["\u1820", "\u1842"], ["\u1844", "\u1877"], ["\u1880", "\u18A8"], "\u18AA", ["\u18B0", "\u18F5"], ["\u1900", "\u191E"], ["\u1950", "\u196D"], ["\u1970", "\u1974"], ["\u1980", "\u19AB"], ["\u19B0", "\u19C9"], ["\u1A00", "\u1A16"], ["\u1A20", "\u1A54"], ["\u1B05", "\u1B33"], ["\u1B45", "\u1B4B"], ["\u1B83", "\u1BA0"], ["\u1BAE", "\u1BAF"], ["\u1BBA", "\u1BE5"], ["\u1C00", "\u1C23"], ["\u1C4D", "\u1C4F"], ["\u1C5A", "\u1C77"], ["\u1CE9", "\u1CEC"], ["\u1CEE", "\u1CF1"], ["\u1CF5", "\u1CF6"], ["\u2135", "\u2138"], ["\u2D30", "\u2D67"], ["\u2D80", "\u2D96"], ["\u2DA0", "\u2DA6"], ["\u2DA8", "\u2DAE"], ["\u2DB0", "\u2DB6"], ["\u2DB8", "\u2DBE"], ["\u2DC0", "\u2DC6"], ["\u2DC8", "\u2DCE"], ["\u2DD0", "\u2DD6"], ["\u2DD8", "\u2DDE"], "\u3006", "\u303C", ["\u3041", "\u3096"], "\u309F", ["\u30A1", "\u30FA"], "\u30FF", ["\u3105", "\u312D"], ["\u3131", "\u318E"], ["\u31A0", "\u31BA"], ["\u31F0", "\u31FF"], ["\u3400", "\u4DB5"], ["\u4E00", "\u9FD5"], ["\uA000", "\uA014"], ["\uA016", "\uA48C"], ["\uA4D0", "\uA4F7"], ["\uA500", "\uA60B"], ["\uA610", "\uA61F"], ["\uA62A", "\uA62B"], "\uA66E", ["\uA6A0", "\uA6E5"], "\uA78F", "\uA7F7", ["\uA7FB", "\uA801"], ["\uA803", "\uA805"], ["\uA807", "\uA80A"], ["\uA80C", "\uA822"], ["\uA840", "\uA873"], ["\uA882", "\uA8B3"], ["\uA8F2", "\uA8F7"], "\uA8FB", "\uA8FD", ["\uA90A", "\uA925"], ["\uA930", "\uA946"], ["\uA960", "\uA97C"], ["\uA984", "\uA9B2"], ["\uA9E0", "\uA9E4"], ["\uA9E7", "\uA9EF"], ["\uA9FA", "\uA9FE"], ["\uAA00", "\uAA28"], ["\uAA40", "\uAA42"], ["\uAA44", "\uAA4B"], ["\uAA60", "\uAA6F"], ["\uAA71", "\uAA76"], "\uAA7A", ["\uAA7E", "\uAAAF"], "\uAAB1", ["\uAAB5", "\uAAB6"], ["\uAAB9", "\uAABD"], "\uAAC0", "\uAAC2", ["\uAADB", "\uAADC"], ["\uAAE0", "\uAAEA"], "\uAAF2", ["\uAB01", "\uAB06"], ["\uAB09", "\uAB0E"], ["\uAB11", "\uAB16"], ["\uAB20", "\uAB26"], ["\uAB28", "\uAB2E"], ["\uABC0", "\uABE2"], ["\uAC00", "\uD7A3"], ["\uD7B0", "\uD7C6"], ["\uD7CB", "\uD7FB"], ["\uF900", "\uFA6D"], ["\uFA70", "\uFAD9"], "\uFB1D", ["\uFB1F", "\uFB28"], ["\uFB2A", "\uFB36"], ["\uFB38", "\uFB3C"], "\uFB3E", ["\uFB40", "\uFB41"], ["\uFB43", "\uFB44"], ["\uFB46", "\uFBB1"], ["\uFBD3", "\uFD3D"], ["\uFD50", "\uFD8F"], ["\uFD92", "\uFDC7"], ["\uFDF0", "\uFDFB"], ["\uFE70", "\uFE74"], ["\uFE76", "\uFEFC"], ["\uFF66", "\uFF6F"], ["\uFF71", "\uFF9D"], ["\uFFA0", "\uFFBE"], ["\uFFC2", "\uFFC7"], ["\uFFCA", "\uFFCF"], ["\uFFD2", "\uFFD7"], ["\uFFDA", "\uFFDC"]], false, false);
+ var peg$e151 = peg$classExpectation(["\u01C5", "\u01C8", "\u01CB", "\u01F2", ["\u1F88", "\u1F8F"], ["\u1F98", "\u1F9F"], ["\u1FA8", "\u1FAF"], "\u1FBC", "\u1FCC", "\u1FFC"], false, false);
+ var peg$e152 = peg$classExpectation([["A", "Z"], ["\xC0", "\xD6"], ["\xD8", "\xDE"], "\u0100", "\u0102", "\u0104", "\u0106", "\u0108", "\u010A", "\u010C", "\u010E", "\u0110", "\u0112", "\u0114", "\u0116", "\u0118", "\u011A", "\u011C", "\u011E", "\u0120", "\u0122", "\u0124", "\u0126", "\u0128", "\u012A", "\u012C", "\u012E", "\u0130", "\u0132", "\u0134", "\u0136", "\u0139", "\u013B", "\u013D", "\u013F", "\u0141", "\u0143", "\u0145", "\u0147", "\u014A", "\u014C", "\u014E", "\u0150", "\u0152", "\u0154", "\u0156", "\u0158", "\u015A", "\u015C", "\u015E", "\u0160", "\u0162", "\u0164", "\u0166", "\u0168", "\u016A", "\u016C", "\u016E", "\u0170", "\u0172", "\u0174", "\u0176", ["\u0178", "\u0179"], "\u017B", "\u017D", ["\u0181", "\u0182"], "\u0184", ["\u0186", "\u0187"], ["\u0189", "\u018B"], ["\u018E", "\u0191"], ["\u0193", "\u0194"], ["\u0196", "\u0198"], ["\u019C", "\u019D"], ["\u019F", "\u01A0"], "\u01A2", "\u01A4", ["\u01A6", "\u01A7"], "\u01A9", "\u01AC", ["\u01AE", "\u01AF"], ["\u01B1", "\u01B3"], "\u01B5", ["\u01B7", "\u01B8"], "\u01BC", "\u01C4", "\u01C7", "\u01CA", "\u01CD", "\u01CF", "\u01D1", "\u01D3", "\u01D5", "\u01D7", "\u01D9", "\u01DB", "\u01DE", "\u01E0", "\u01E2", "\u01E4", "\u01E6", "\u01E8", "\u01EA", "\u01EC", "\u01EE", "\u01F1", "\u01F4", ["\u01F6", "\u01F8"], "\u01FA", "\u01FC", "\u01FE", "\u0200", "\u0202", "\u0204", "\u0206", "\u0208", "\u020A", "\u020C", "\u020E", "\u0210", "\u0212", "\u0214", "\u0216", "\u0218", "\u021A", "\u021C", "\u021E", "\u0220", "\u0222", "\u0224", "\u0226", "\u0228", "\u022A", "\u022C", "\u022E", "\u0230", "\u0232", ["\u023A", "\u023B"], ["\u023D", "\u023E"], "\u0241", ["\u0243", "\u0246"], "\u0248", "\u024A", "\u024C", "\u024E", "\u0370", "\u0372", "\u0376", "\u037F", "\u0386", ["\u0388", "\u038A"], "\u038C", ["\u038E", "\u038F"], ["\u0391", "\u03A1"], ["\u03A3", "\u03AB"], "\u03CF", ["\u03D2", "\u03D4"], "\u03D8", "\u03DA", "\u03DC", "\u03DE", "\u03E0", "\u03E2", "\u03E4", "\u03E6", "\u03E8", "\u03EA", "\u03EC", "\u03EE", "\u03F4", "\u03F7", ["\u03F9", "\u03FA"], ["\u03FD", "\u042F"], "\u0460", "\u0462", "\u0464", "\u0466", "\u0468", "\u046A", "\u046C", "\u046E", "\u0470", "\u0472", "\u0474", "\u0476", "\u0478", "\u047A", "\u047C", "\u047E", "\u0480", "\u048A", "\u048C", "\u048E", "\u0490", "\u0492", "\u0494", "\u0496", "\u0498", "\u049A", "\u049C", "\u049E", "\u04A0", "\u04A2", "\u04A4", "\u04A6", "\u04A8", "\u04AA", "\u04AC", "\u04AE", "\u04B0", "\u04B2", "\u04B4", "\u04B6", "\u04B8", "\u04BA", "\u04BC", "\u04BE", ["\u04C0", "\u04C1"], "\u04C3", "\u04C5", "\u04C7", "\u04C9", "\u04CB", "\u04CD", "\u04D0", "\u04D2", "\u04D4", "\u04D6", "\u04D8", "\u04DA", "\u04DC", "\u04DE", "\u04E0", "\u04E2", "\u04E4", "\u04E6", "\u04E8", "\u04EA", "\u04EC", "\u04EE", "\u04F0", "\u04F2", "\u04F4", "\u04F6", "\u04F8", "\u04FA", "\u04FC", "\u04FE", "\u0500", "\u0502", "\u0504", "\u0506", "\u0508", "\u050A", "\u050C", "\u050E", "\u0510", "\u0512", "\u0514", "\u0516", "\u0518", "\u051A", "\u051C", "\u051E", "\u0520", "\u0522", "\u0524", "\u0526", "\u0528", "\u052A", "\u052C", "\u052E", ["\u0531", "\u0556"], ["\u10A0", "\u10C5"], "\u10C7", "\u10CD", ["\u13A0", "\u13F5"], "\u1E00", "\u1E02", "\u1E04", "\u1E06", "\u1E08", "\u1E0A", "\u1E0C", "\u1E0E", "\u1E10", "\u1E12", "\u1E14", "\u1E16", "\u1E18", "\u1E1A", "\u1E1C", "\u1E1E", "\u1E20", "\u1E22", "\u1E24", "\u1E26", "\u1E28", "\u1E2A", "\u1E2C", "\u1E2E", "\u1E30", "\u1E32", "\u1E34", "\u1E36", "\u1E38", "\u1E3A", "\u1E3C", "\u1E3E", "\u1E40", "\u1E42", "\u1E44", "\u1E46", "\u1E48", "\u1E4A", "\u1E4C", "\u1E4E", "\u1E50", "\u1E52", "\u1E54", "\u1E56", "\u1E58", "\u1E5A", "\u1E5C", "\u1E5E", "\u1E60", "\u1E62", "\u1E64", "\u1E66", "\u1E68", "\u1E6A", "\u1E6C", "\u1E6E", "\u1E70", "\u1E72", "\u1E74", "\u1E76", "\u1E78", "\u1E7A", "\u1E7C", "\u1E7E", "\u1E80", "\u1E82", "\u1E84", "\u1E86", "\u1E88", "\u1E8A", "\u1E8C", "\u1E8E", "\u1E90", "\u1E92", "\u1E94", "\u1E9E", "\u1EA0", "\u1EA2", "\u1EA4", "\u1EA6", "\u1EA8", "\u1EAA", "\u1EAC", "\u1EAE", "\u1EB0", "\u1EB2", "\u1EB4", "\u1EB6", "\u1EB8", "\u1EBA", "\u1EBC", "\u1EBE", "\u1EC0", "\u1EC2", "\u1EC4", "\u1EC6", "\u1EC8", "\u1ECA", "\u1ECC", "\u1ECE", "\u1ED0", "\u1ED2", "\u1ED4", "\u1ED6", "\u1ED8", "\u1EDA", "\u1EDC", "\u1EDE", "\u1EE0", "\u1EE2", "\u1EE4", "\u1EE6", "\u1EE8", "\u1EEA", "\u1EEC", "\u1EEE", "\u1EF0", "\u1EF2", "\u1EF4", "\u1EF6", "\u1EF8", "\u1EFA", "\u1EFC", "\u1EFE", ["\u1F08", "\u1F0F"], ["\u1F18", "\u1F1D"], ["\u1F28", "\u1F2F"], ["\u1F38", "\u1F3F"], ["\u1F48", "\u1F4D"], "\u1F59", "\u1F5B", "\u1F5D", "\u1F5F", ["\u1F68", "\u1F6F"], ["\u1FB8", "\u1FBB"], ["\u1FC8", "\u1FCB"], ["\u1FD8", "\u1FDB"], ["\u1FE8", "\u1FEC"], ["\u1FF8", "\u1FFB"], "\u2102", "\u2107", ["\u210B", "\u210D"], ["\u2110", "\u2112"], "\u2115", ["\u2119", "\u211D"], "\u2124", "\u2126", "\u2128", ["\u212A", "\u212D"], ["\u2130", "\u2133"], ["\u213E", "\u213F"], "\u2145", "\u2183", ["\u2C00", "\u2C2E"], "\u2C60", ["\u2C62", "\u2C64"], "\u2C67", "\u2C69", "\u2C6B", ["\u2C6D", "\u2C70"], "\u2C72", "\u2C75", ["\u2C7E", "\u2C80"], "\u2C82", "\u2C84", "\u2C86", "\u2C88", "\u2C8A", "\u2C8C", "\u2C8E", "\u2C90", "\u2C92", "\u2C94", "\u2C96", "\u2C98", "\u2C9A", "\u2C9C", "\u2C9E", "\u2CA0", "\u2CA2", "\u2CA4", "\u2CA6", "\u2CA8", "\u2CAA", "\u2CAC", "\u2CAE", "\u2CB0", "\u2CB2", "\u2CB4", "\u2CB6", "\u2CB8", "\u2CBA", "\u2CBC", "\u2CBE", "\u2CC0", "\u2CC2", "\u2CC4", "\u2CC6", "\u2CC8", "\u2CCA", "\u2CCC", "\u2CCE", "\u2CD0", "\u2CD2", "\u2CD4", "\u2CD6", "\u2CD8", "\u2CDA", "\u2CDC", "\u2CDE", "\u2CE0", "\u2CE2", "\u2CEB", "\u2CED", "\u2CF2", "\uA640", "\uA642", "\uA644", "\uA646", "\uA648", "\uA64A", "\uA64C", "\uA64E", "\uA650", "\uA652", "\uA654", "\uA656", "\uA658", "\uA65A", "\uA65C", "\uA65E", "\uA660", "\uA662", "\uA664", "\uA666", "\uA668", "\uA66A", "\uA66C", "\uA680", "\uA682", "\uA684", "\uA686", "\uA688", "\uA68A", "\uA68C", "\uA68E", "\uA690", "\uA692", "\uA694", "\uA696", "\uA698", "\uA69A", "\uA722", "\uA724", "\uA726", "\uA728", "\uA72A", "\uA72C", "\uA72E", "\uA732", "\uA734", "\uA736", "\uA738", "\uA73A", "\uA73C", "\uA73E", "\uA740", "\uA742", "\uA744", "\uA746", "\uA748", "\uA74A", "\uA74C", "\uA74E", "\uA750", "\uA752", "\uA754", "\uA756", "\uA758", "\uA75A", "\uA75C", "\uA75E", "\uA760", "\uA762", "\uA764", "\uA766", "\uA768", "\uA76A", "\uA76C", "\uA76E", "\uA779", "\uA77B", ["\uA77D", "\uA77E"], "\uA780", "\uA782", "\uA784", "\uA786", "\uA78B", "\uA78D", "\uA790", "\uA792", "\uA796", "\uA798", "\uA79A", "\uA79C", "\uA79E", "\uA7A0", "\uA7A2", "\uA7A4", "\uA7A6", "\uA7A8", ["\uA7AA", "\uA7AD"], ["\uA7B0", "\uA7B4"], "\uA7B6", ["\uFF21", "\uFF3A"]], false, false);
+ var peg$e153 = peg$classExpectation(["\u0903", "\u093B", ["\u093E", "\u0940"], ["\u0949", "\u094C"], ["\u094E", "\u094F"], ["\u0982", "\u0983"], ["\u09BE", "\u09C0"], ["\u09C7", "\u09C8"], ["\u09CB", "\u09CC"], "\u09D7", "\u0A03", ["\u0A3E", "\u0A40"], "\u0A83", ["\u0ABE", "\u0AC0"], "\u0AC9", ["\u0ACB", "\u0ACC"], ["\u0B02", "\u0B03"], "\u0B3E", "\u0B40", ["\u0B47", "\u0B48"], ["\u0B4B", "\u0B4C"], "\u0B57", ["\u0BBE", "\u0BBF"], ["\u0BC1", "\u0BC2"], ["\u0BC6", "\u0BC8"], ["\u0BCA", "\u0BCC"], "\u0BD7", ["\u0C01", "\u0C03"], ["\u0C41", "\u0C44"], ["\u0C82", "\u0C83"], "\u0CBE", ["\u0CC0", "\u0CC4"], ["\u0CC7", "\u0CC8"], ["\u0CCA", "\u0CCB"], ["\u0CD5", "\u0CD6"], ["\u0D02", "\u0D03"], ["\u0D3E", "\u0D40"], ["\u0D46", "\u0D48"], ["\u0D4A", "\u0D4C"], "\u0D57", ["\u0D82", "\u0D83"], ["\u0DCF", "\u0DD1"], ["\u0DD8", "\u0DDF"], ["\u0DF2", "\u0DF3"], ["\u0F3E", "\u0F3F"], "\u0F7F", ["\u102B", "\u102C"], "\u1031", "\u1038", ["\u103B", "\u103C"], ["\u1056", "\u1057"], ["\u1062", "\u1064"], ["\u1067", "\u106D"], ["\u1083", "\u1084"], ["\u1087", "\u108C"], "\u108F", ["\u109A", "\u109C"], "\u17B6", ["\u17BE", "\u17C5"], ["\u17C7", "\u17C8"], ["\u1923", "\u1926"], ["\u1929", "\u192B"], ["\u1930", "\u1931"], ["\u1933", "\u1938"], ["\u1A19", "\u1A1A"], "\u1A55", "\u1A57", "\u1A61", ["\u1A63", "\u1A64"], ["\u1A6D", "\u1A72"], "\u1B04", "\u1B35", "\u1B3B", ["\u1B3D", "\u1B41"], ["\u1B43", "\u1B44"], "\u1B82", "\u1BA1", ["\u1BA6", "\u1BA7"], "\u1BAA", "\u1BE7", ["\u1BEA", "\u1BEC"], "\u1BEE", ["\u1BF2", "\u1BF3"], ["\u1C24", "\u1C2B"], ["\u1C34", "\u1C35"], "\u1CE1", ["\u1CF2", "\u1CF3"], ["\u302E", "\u302F"], ["\uA823", "\uA824"], "\uA827", ["\uA880", "\uA881"], ["\uA8B4", "\uA8C3"], ["\uA952", "\uA953"], "\uA983", ["\uA9B4", "\uA9B5"], ["\uA9BA", "\uA9BB"], ["\uA9BD", "\uA9C0"], ["\uAA2F", "\uAA30"], ["\uAA33", "\uAA34"], "\uAA4D", "\uAA7B", "\uAA7D", "\uAAEB", ["\uAAEE", "\uAAEF"], "\uAAF5", ["\uABE3", "\uABE4"], ["\uABE6", "\uABE7"], ["\uABE9", "\uABEA"], "\uABEC"], false, false);
+ var peg$e154 = peg$classExpectation([["\u0300", "\u036F"], ["\u0483", "\u0487"], ["\u0591", "\u05BD"], "\u05BF", ["\u05C1", "\u05C2"], ["\u05C4", "\u05C5"], "\u05C7", ["\u0610", "\u061A"], ["\u064B", "\u065F"], "\u0670", ["\u06D6", "\u06DC"], ["\u06DF", "\u06E4"], ["\u06E7", "\u06E8"], ["\u06EA", "\u06ED"], "\u0711", ["\u0730", "\u074A"], ["\u07A6", "\u07B0"], ["\u07EB", "\u07F3"], ["\u0816", "\u0819"], ["\u081B", "\u0823"], ["\u0825", "\u0827"], ["\u0829", "\u082D"], ["\u0859", "\u085B"], ["\u08E3", "\u0902"], "\u093A", "\u093C", ["\u0941", "\u0948"], "\u094D", ["\u0951", "\u0957"], ["\u0962", "\u0963"], "\u0981", "\u09BC", ["\u09C1", "\u09C4"], "\u09CD", ["\u09E2", "\u09E3"], ["\u0A01", "\u0A02"], "\u0A3C", ["\u0A41", "\u0A42"], ["\u0A47", "\u0A48"], ["\u0A4B", "\u0A4D"], "\u0A51", ["\u0A70", "\u0A71"], "\u0A75", ["\u0A81", "\u0A82"], "\u0ABC", ["\u0AC1", "\u0AC5"], ["\u0AC7", "\u0AC8"], "\u0ACD", ["\u0AE2", "\u0AE3"], "\u0B01", "\u0B3C", "\u0B3F", ["\u0B41", "\u0B44"], "\u0B4D", "\u0B56", ["\u0B62", "\u0B63"], "\u0B82", "\u0BC0", "\u0BCD", "\u0C00", ["\u0C3E", "\u0C40"], ["\u0C46", "\u0C48"], ["\u0C4A", "\u0C4D"], ["\u0C55", "\u0C56"], ["\u0C62", "\u0C63"], "\u0C81", "\u0CBC", "\u0CBF", "\u0CC6", ["\u0CCC", "\u0CCD"], ["\u0CE2", "\u0CE3"], "\u0D01", ["\u0D41", "\u0D44"], "\u0D4D", ["\u0D62", "\u0D63"], "\u0DCA", ["\u0DD2", "\u0DD4"], "\u0DD6", "\u0E31", ["\u0E34", "\u0E3A"], ["\u0E47", "\u0E4E"], "\u0EB1", ["\u0EB4", "\u0EB9"], ["\u0EBB", "\u0EBC"], ["\u0EC8", "\u0ECD"], ["\u0F18", "\u0F19"], "\u0F35", "\u0F37", "\u0F39", ["\u0F71", "\u0F7E"], ["\u0F80", "\u0F84"], ["\u0F86", "\u0F87"], ["\u0F8D", "\u0F97"], ["\u0F99", "\u0FBC"], "\u0FC6", ["\u102D", "\u1030"], ["\u1032", "\u1037"], ["\u1039", "\u103A"], ["\u103D", "\u103E"], ["\u1058", "\u1059"], ["\u105E", "\u1060"], ["\u1071", "\u1074"], "\u1082", ["\u1085", "\u1086"], "\u108D", "\u109D", ["\u135D", "\u135F"], ["\u1712", "\u1714"], ["\u1732", "\u1734"], ["\u1752", "\u1753"], ["\u1772", "\u1773"], ["\u17B4", "\u17B5"], ["\u17B7", "\u17BD"], "\u17C6", ["\u17C9", "\u17D3"], "\u17DD", ["\u180B", "\u180D"], "\u18A9", ["\u1920", "\u1922"], ["\u1927", "\u1928"], "\u1932", ["\u1939", "\u193B"], ["\u1A17", "\u1A18"], "\u1A1B", "\u1A56", ["\u1A58", "\u1A5E"], "\u1A60", "\u1A62", ["\u1A65", "\u1A6C"], ["\u1A73", "\u1A7C"], "\u1A7F", ["\u1AB0", "\u1ABD"], ["\u1B00", "\u1B03"], "\u1B34", ["\u1B36", "\u1B3A"], "\u1B3C", "\u1B42", ["\u1B6B", "\u1B73"], ["\u1B80", "\u1B81"], ["\u1BA2", "\u1BA5"], ["\u1BA8", "\u1BA9"], ["\u1BAB", "\u1BAD"], "\u1BE6", ["\u1BE8", "\u1BE9"], "\u1BED", ["\u1BEF", "\u1BF1"], ["\u1C2C", "\u1C33"], ["\u1C36", "\u1C37"], ["\u1CD0", "\u1CD2"], ["\u1CD4", "\u1CE0"], ["\u1CE2", "\u1CE8"], "\u1CED", "\u1CF4", ["\u1CF8", "\u1CF9"], ["\u1DC0", "\u1DF5"], ["\u1DFC", "\u1DFF"], ["\u20D0", "\u20DC"], "\u20E1", ["\u20E5", "\u20F0"], ["\u2CEF", "\u2CF1"], "\u2D7F", ["\u2DE0", "\u2DFF"], ["\u302A", "\u302D"], ["\u3099", "\u309A"], "\uA66F", ["\uA674", "\uA67D"], ["\uA69E", "\uA69F"], ["\uA6F0", "\uA6F1"], "\uA802", "\uA806", "\uA80B", ["\uA825", "\uA826"], "\uA8C4", ["\uA8E0", "\uA8F1"], ["\uA926", "\uA92D"], ["\uA947", "\uA951"], ["\uA980", "\uA982"], "\uA9B3", ["\uA9B6", "\uA9B9"], "\uA9BC", "\uA9E5", ["\uAA29", "\uAA2E"], ["\uAA31", "\uAA32"], ["\uAA35", "\uAA36"], "\uAA43", "\uAA4C", "\uAA7C", "\uAAB0", ["\uAAB2", "\uAAB4"], ["\uAAB7", "\uAAB8"], ["\uAABE", "\uAABF"], "\uAAC1", ["\uAAEC", "\uAAED"], "\uAAF6", "\uABE5", "\uABE8", "\uABED", "\uFB1E", ["\uFE00", "\uFE0F"], ["\uFE20", "\uFE2F"]], false, false);
+ var peg$e155 = peg$classExpectation([["0", "9"], ["\u0660", "\u0669"], ["\u06F0", "\u06F9"], ["\u07C0", "\u07C9"], ["\u0966", "\u096F"], ["\u09E6", "\u09EF"], ["\u0A66", "\u0A6F"], ["\u0AE6", "\u0AEF"], ["\u0B66", "\u0B6F"], ["\u0BE6", "\u0BEF"], ["\u0C66", "\u0C6F"], ["\u0CE6", "\u0CEF"], ["\u0D66", "\u0D6F"], ["\u0DE6", "\u0DEF"], ["\u0E50", "\u0E59"], ["\u0ED0", "\u0ED9"], ["\u0F20", "\u0F29"], ["\u1040", "\u1049"], ["\u1090", "\u1099"], ["\u17E0", "\u17E9"], ["\u1810", "\u1819"], ["\u1946", "\u194F"], ["\u19D0", "\u19D9"], ["\u1A80", "\u1A89"], ["\u1A90", "\u1A99"], ["\u1B50", "\u1B59"], ["\u1BB0", "\u1BB9"], ["\u1C40", "\u1C49"], ["\u1C50", "\u1C59"], ["\uA620", "\uA629"], ["\uA8D0", "\uA8D9"], ["\uA900", "\uA909"], ["\uA9D0", "\uA9D9"], ["\uA9F0", "\uA9F9"], ["\uAA50", "\uAA59"], ["\uABF0", "\uABF9"], ["\uFF10", "\uFF19"]], false, false);
+ var peg$e156 = peg$classExpectation([["\u16EE", "\u16F0"], ["\u2160", "\u2182"], ["\u2185", "\u2188"], "\u3007", ["\u3021", "\u3029"], ["\u3038", "\u303A"], ["\uA6E6", "\uA6EF"]], false, false);
+ var peg$e157 = peg$classExpectation(["_", ["\u203F", "\u2040"], "\u2054", ["\uFE33", "\uFE34"], ["\uFE4D", "\uFE4F"], "\uFF3F"], false, false);
+ var peg$e158 = peg$classExpectation([" ", "\xA0", "\u1680", ["\u2000", "\u200A"], "\u202F", "\u205F", "\u3000"], false, false);
+
+ var peg$f0 = function(program) { return { sentenceList: program, errors }; };
+ var peg$f1 = function(program) { return { sentenceList: program, errors }; };
+ var peg$f2 = function() { report("parsing cannot preceed"); };
+ var peg$f3 = function(body) {
+ return optionalList(body);
+ };
+ var peg$f4 = function(head, tail) {
+ return buildList(head, tail, 1);
+ };
+ var peg$f5 = function() {
+ report("non-completed parsing");
+ };
+ var peg$f6 = function(head, tail) {
+ return buildList(head, tail, 1);
+ };
+ var peg$f7 = function(key, value) {
+ value = value.trim()
+
+ if (value === "none") {
+ value = "";
+ } else if (value === "true" || value === "false") {
+ value = (value === "true");
+ } else {
+ const number = Number(value);
+ if (!isNaN(number)) {
+ value = number;
+ }
+ }
+
+ return { key, value };
+ };
+ var peg$f8 = function(key) {
+ return { key, value: true };
+ };
+ var peg$f9 = function() { return text(); };
+ var peg$f10 = function(sequence) { return sequence.join(""); };
+ var peg$f11 = function(sequence) { return sequence.join(""); };
+ var peg$f12 = function(sequence) { return sequence.join("") };
+ var peg$f13 = function(sequence) { return sequence.join(""); };
+ var peg$f14 = function(sequence) { return sequence.join(""); };
+ var peg$f15 = function(sequence) { return sequence.join(""); };
+ var peg$f16 = function() { return text(); };
+ var peg$f17 = function(sequence) { return sequence; };
+ var peg$f18 = function() { return text(); };
+ var peg$f19 = function(sequence) { return sequence; };
+ var peg$f20 = function() { return text(); };
+ var peg$f21 = function() { return text(); };
+ var peg$f22 = function() { return "\0"; };
+ var peg$f23 = function() { return "\b"; };
+ var peg$f24 = function() { return "\f"; };
+ var peg$f25 = function() { return "\n"; };
+ var peg$f26 = function() { return "\r"; };
+ var peg$f27 = function() { return "\t"; };
+ var peg$f28 = function() { return "\v"; };
+ var peg$f29 = function() { return text(); };
+ var peg$f30 = function(digits) {
+ return String.fromCharCode(parseInt(digits, 16));
+ };
+ var peg$f31 = function(digits) {
+ return String.fromCharCode(parseInt(digits, 16));
+ };
+ var peg$f32 = function(sequence) { return sequence; };
+ var peg$f33 = function(comment) {
+ comment = optionalString(comment);
+
+ return {
+ command: commandType.comment,
+ commandRaw: "comment",
+ content: comment,
+ args: [],
+ }
+ };
+ var peg$f34 = function() {
+ return {
+ command: commandType.comment,
+ commandRaw: "comment",
+ content: "",
+ args: [],
+ }
+ };
+ var peg$f35 = function(fileName, args) {
+ args = optionalList(args);
+ fileName = processNone(fileName.trim());
+
+ return {
+ command: commandType.changeBg,
+ commandRaw: "changeBg",
+ content: fileName,
+ args,
+ };
+ };
+ var peg$f36 = function(fileName, args) {
+ args = optionalList(args);
+ fileName = processNone(fileName.trim());
+
+ return {
+ command: commandType.changeFigure,
+ commandRaw: "changeFigure",
+ content: fileName,
+ args,
+ };
+ };
+ var peg$f37 = function(fileName, args) {
+ args = optionalList(args);
+ fileName = processNone(fileName.trim());
+
+ return {
+ command: commandType.bgm,
+ commandRaw: "bgm",
+ content: fileName,
+ args,
+ };
+ };
+ var peg$f38 = function(fileName, args) {
+ args = optionalList(args);
+ fileName = processNone(fileName.trim());
+
+ return {
+ command: commandType.video,
+ commandRaw: "playVideo",
+ content: fileName,
+ args,
+ };
+ };
+ var peg$f39 = function(performName, args) {
+ args = optionalList(args);
+
+ return {
+ command: commandType.pixi,
+ commandRaw: "pixiPerform",
+ content: performName,
+ args,
+ };
+ };
+ var peg$f40 = function() {
+ return {
+ command: commandType.pixiInit,
+ commandRaw: "pixiInit",
+ content: "",
+ args: [],
+ };
+ };
+ var peg$f41 = function(lines, args) {
+ args = optionalList(args);
+
+ return {
+ command: commandType.intro,
+ commandRaw: "intro",
+ content: processNone(lines.trim()),
+ args,
+ };
+ };
+ var peg$f42 = function(fileName, args) {
+ args = optionalList(args);
+
+ return {
+ command: commandType.miniAvatar,
+ commandRaw: "miniAvatar",
+ content: processNone(fileName.trim()),
+ args,
+ };
+ };
+ var peg$f43 = function(fileName, args) {
+ args = optionalList(args);
+
+ return {
+ command: commandType.changeScene,
+ commandRaw: "changeScene",
+ content: processNone(fileName.trim()),
+ args,
+ };
+ };
+ var peg$f44 = function(choices) {
+ choices.choiceList = optionalList(choices.choiceList);
+
+ return {
+ command: commandType.choose,
+ commandRaw: "choose",
+ content: "",
+ args: [
+ { key: "choices", value: choices.choiceList },
+ { key: "contentRawRange", value: choices.contentRawRange }
+ ],
+ };
+ };
+ var peg$f45 = function(head, tail) {
+ const loc = location();
+
+ return {
+ contentRawRange: [loc.start.offset, loc.end.offset],
+ choiceList: buildList(head, tail, 1)
+ };
+ };
+ var peg$f46 = function(sexp, cexp, text, dest) {
+ return {
+ showExpression: sexp,
+ clickExpression: cexp,
+ text,
+ destination: dest,
+ }
+ };
+ var peg$f47 = function(text, dest) {
+ return {
+ showExpression: "",
+ clickExpression: "",
+ text,
+ destination: dest,
+ }
+ };
+ var peg$f48 = function(sequence) { return sequence.join(""); };
+ var peg$f49 = function() { return text(); };
+ var peg$f50 = function(sequence) { return sequence.join(""); };
+ var peg$f51 = function() { return text(); };
+ var peg$f52 = function() { return text(); };
+ var peg$f53 = function() { return text(); };
+ var peg$f54 = function() {
+ return {
+ command: commandType.end,
+ commandRaw: "end",
+ content: "",
+ args: [],
+ };
+ };
+ var peg$f55 = function(animationName, args) {
+ args = optionalList(args);
+
+ return {
+ command: commandType.setComplexAnimation,
+ commandRaw: "setComplexAnimation",
+ content: animationName,
+ args,
+ };
+ };
+ var peg$f56 = function() {
+ return {
+ command: commandType.setFilter,
+ commandRaw: "setFilter",
+ content: "",
+ args: [],
+ };
+ };
+ var peg$f57 = function(labelName) {
+ return {
+ command: commandType.label,
+ commandRaw: "label",
+ content: labelName,
+ args: [],
+ };
+ };
+ var peg$f58 = function(labelName) {
+ return {
+ command: commandType.jumpLabel,
+ commandRaw: "jumpLabel",
+ content: labelName,
+ args: [],
+ };
+ };
+ var peg$f59 = function(kv, args) {
+ // we prefix variableName and expression with `#` to separate it from
+ // user-defined arguments
+ args = optionalList(args);
+
+ return {
+ command: commandType.setVar,
+ commandRaw: "setVar",
+ content: "",
+ args: [
+ { key: "#variableName", value: kv.key },
+ { key: "#internalExpression", value: kv.value },
+ ].concat(args),
+ };
+ };
+ var peg$f60 = function(kv, args) {
+ args = optionalList(args);
+
+ return {
+ command: commandType.setVar,
+ commandRaw: "setVar",
+ content: "",
+ args: [
+ { key: "#variableName", value: kv.key },
+ { key: "#expression", value: kv.value },
+ ].concat(args),
+ };
+ };
+ var peg$f61 = function(key, value) {
+ if (value === "none") {
+ value = "";
+ } else if (value === "true" || value === "false") {
+ value = (value === "true");
+ } else {
+ const number = Number(value);
+ if (!isNaN(number)) {
+ value = number;
+ }
+ }
+
+ return { key, value };
+ };
+ var peg$f62 = function() { return text(); };
+ var peg$f63 = function(sceneName) {
+ return {
+ command: commandType.callScene,
+ commandRaw: "callScene",
+ content: sceneName,
+ args: [],
+ };
+ };
+ var peg$f64 = function() {
+ return {
+ command: commandType.showVars,
+ commandRaw: "showVars",
+ content: "",
+ args: [],
+ };
+ };
+ var peg$f65 = function(name, args) {
+ args = optionalList(args);
+
+ return {
+ command: commandType.unlockCg,
+ commandRaw: "unlockCg",
+ content: name,
+ args,
+ };
+ };
+ var peg$f66 = function(name, args) {
+ args = optionalList(args);
+
+ return {
+ command: commandType.unlockBgm,
+ commandRaw: "unlockBgm",
+ content: name,
+ args,
+ };
+ };
+ var peg$f67 = function(content) {
+ content = processNone(content.trim());
+
+ return {
+ command: commandType.filmMode,
+ commandRaw: "filmMode",
+ content,
+ args: [],
+ };
+ };
+ var peg$f68 = function(content) {
+ return {
+ command: commandType.setTextbox,
+ commandRaw: "setTextbox",
+ content,
+ args: [],
+ };
+ };
+ var peg$f69 = function(content, args) {
+ args = optionalList(args);
+
+ return {
+ command: commandType.setAnimation,
+ commandRaw: "setAnimation",
+ content,
+ args,
+ };
+ };
+ var peg$f70 = function(args) {
+ args = optionalList(args);
+
+ return {
+ command: commandType.setTransition,
+ commandRaw: "setTransition",
+ content: "",
+ args,
+ };
+ };
+ var peg$f71 = function(name, args) {
+ name = processNone(name.trim());
+ args = optionalList(args);
+
+ return {
+ command: commandType.playEffect,
+ commandRaw: "playEffect",
+ content: name,
+ args,
+ };
+ };
+ var peg$f72 = function(json, args) {
+ // Here, we do a trick by directly check whether a bracket is in the
+ // expression since we don't want to add extra cost on parsing JSON.
+ // The direct check holds because the content will never encounter a
+ // close bracket.
+ args = optionalList(args);
+
+ return {
+ command: commandType.setTempAnimation,
+ commandRaw: "setTempAnimation",
+ content: `[${json}]`,
+ args,
+ };
+ };
+ var peg$f73 = function(json, args) {
+ args = optionalList(args);
+
+ return {
+ command: commandType.setTransform,
+ commandRaw: "setTransform",
+ content: json,
+ args,
+ };
+ };
+ var peg$f74 = function(into, args) {
+ args = optionalList(args);
+
+ return {
+ command: commandType.getUserInput,
+ commandRaw: "getUserInput",
+ content: into,
+ args,
+ };
+ };
+ var peg$f75 = function(speaker, content, args) {
+ args = optionalList(args);
+ args = processVocalFileName(args);
+
+ return {
+ command: commandType.say,
+ commandRaw: speaker,
+ content: content.trim(),
+ args: [{ key: "speaker", value: speaker }].concat(args),
+ }
+ };
+ var peg$f76 = function(content, args) {
+ args = optionalList(args);
+
+ return {
+ command: commandType.say,
+ commandRaw: "say",
+ content: content.trim(),
+ args,
+ }
+ };
+ var peg$f77 = function() { return text(); };
+ var peg$f78 = function(err) { report("unexpected statement `" + err + "`"); };
+ var peg$currPos = options.peg$currPos | 0;
+ var peg$savedPos = peg$currPos;
+ var peg$posDetailsCache = [{ line: 1, column: 1 }];
+ var peg$maxFailPos = peg$currPos;
+ var peg$maxFailExpected = options.peg$maxFailExpected || [];
+ var peg$silentFails = options.peg$silentFails | 0;
+
+ var peg$result;
+
+ if (options.startRule) {
+ if (!(options.startRule in peg$startRuleFunctions)) {
+ throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
+ }
+
+ peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
+ }
+
+ function text() {
+ return input.substring(peg$savedPos, peg$currPos);
+ }
+
+ function offset() {
+ return peg$savedPos;
+ }
+
+ function range() {
+ return {
+ source: peg$source,
+ start: peg$savedPos,
+ end: peg$currPos
+ };
+ }
+
+ function location() {
+ return peg$computeLocation(peg$savedPos, peg$currPos);
+ }
+
+ function expected(description, location) {
+ location = location !== undefined
+ ? location
+ : peg$computeLocation(peg$savedPos, peg$currPos);
+
+ throw peg$buildStructuredError(
+ [peg$otherExpectation(description)],
+ input.substring(peg$savedPos, peg$currPos),
+ location
+ );
+ }
+
+ function error(message, location) {
+ location = location !== undefined
+ ? location
+ : peg$computeLocation(peg$savedPos, peg$currPos);
+
+ throw peg$buildSimpleError(message, location);
+ }
+
+ function peg$literalExpectation(text, ignoreCase) {
+ return { type: "literal", text: text, ignoreCase: ignoreCase };
+ }
+
+ function peg$classExpectation(parts, inverted, ignoreCase) {
+ return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase };
+ }
+
+ function peg$anyExpectation() {
+ return { type: "any" };
+ }
+
+ function peg$endExpectation() {
+ return { type: "end" };
+ }
+
+ function peg$otherExpectation(description) {
+ return { type: "other", description: description };
+ }
+
+ function peg$computePosDetails(pos) {
+ var details = peg$posDetailsCache[pos];
+ var p;
+
+ if (details) {
+ return details;
+ } else {
+ if (pos >= peg$posDetailsCache.length) {
+ p = peg$posDetailsCache.length - 1;
+ } else {
+ p = pos;
+ while (!peg$posDetailsCache[--p]) {}
+ }
+
+ details = peg$posDetailsCache[p];
+ details = {
+ line: details.line,
+ column: details.column
+ };
+
+ while (p < pos) {
+ if (input.charCodeAt(p) === 10) {
+ details.line++;
+ details.column = 1;
+ } else {
+ details.column++;
+ }
+
+ p++;
+ }
+
+ peg$posDetailsCache[pos] = details;
+
+ return details;
+ }
+ }
+
+ function peg$computeLocation(startPos, endPos, offset) {
+ var startPosDetails = peg$computePosDetails(startPos);
+ var endPosDetails = peg$computePosDetails(endPos);
+
+ var res = {
+ source: peg$source,
+ start: {
+ offset: startPos,
+ line: startPosDetails.line,
+ column: startPosDetails.column
+ },
+ end: {
+ offset: endPos,
+ line: endPosDetails.line,
+ column: endPosDetails.column
+ }
+ };
+ if (offset && peg$source && (typeof peg$source.offset === "function")) {
+ res.start = peg$source.offset(res.start);
+ res.end = peg$source.offset(res.end);
+ }
+ return res;
+ }
+
+ function peg$fail(expected) {
+ if (peg$currPos < peg$maxFailPos) { return; }
+
+ if (peg$currPos > peg$maxFailPos) {
+ peg$maxFailPos = peg$currPos;
+ peg$maxFailExpected = [];
+ }
+
+ peg$maxFailExpected.push(expected);
+ }
+
+ function peg$buildSimpleError(message, location) {
+ return new peg$SyntaxError(message, null, null, location);
+ }
+
+ function peg$buildStructuredError(expected, found, location) {
+ return new peg$SyntaxError(
+ peg$SyntaxError.buildMessage(expected, found),
+ expected,
+ found,
+ location
+ );
+ }
+
+ function peg$parseStart() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$parseProgram();
+ s2 = peg$parseEOF();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f0(s1);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = peg$parseProgram();
+ s2 = peg$parseREPORT();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f1(s1);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parseREPORT() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = [];
+ if (input.length > peg$currPos) {
+ s2 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e0); }
+ }
+ if (s2 !== peg$FAILED) {
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ if (input.length > peg$currPos) {
+ s2 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e0); }
+ }
+ }
+ } else {
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f2();
+ }
+ s0 = s1;
+
+ return s0;
+ }
+
+ function peg$parseProgram() {
+ var s0, s1;
+
+ s0 = peg$currPos;
+ s1 = peg$parseSourceElements();
+ if (s1 === peg$FAILED) {
+ s1 = null;
+ }
+ peg$savedPos = s0;
+ s1 = peg$f3(s1);
+ s0 = s1;
+
+ return s0;
+ }
+
+ function peg$parseSourceElements() {
+ var s0, s1, s2, s3, s4;
+
+ s0 = peg$currPos;
+ s1 = peg$parseSourceElement();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseCommentSegment();
+ if (s2 === peg$FAILED) {
+ s2 = null;
+ }
+ s3 = [];
+ s4 = peg$parseSourceElementTail();
+ while (s4 !== peg$FAILED) {
+ s3.push(s4);
+ s4 = peg$parseSourceElementTail();
+ }
+ peg$savedPos = s0;
+ s0 = peg$f4(s1, s3);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseSourceElementTail() {
+ var s0, s1, s2, s3;
+
+ s0 = peg$currPos;
+ s1 = peg$parseEOS();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceElement();
+ if (s2 !== peg$FAILED) {
+ s3 = peg$parseCommentSegment();
+ if (s3 === peg$FAILED) {
+ s3 = null;
+ }
+ s1 = [s1, s2, s3];
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = peg$parseSKIPToNextStatement();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceElement();
+ if (s2 !== peg$FAILED) {
+ s3 = peg$parseCommentSegment();
+ if (s3 === peg$FAILED) {
+ s3 = null;
+ }
+ s1 = [s1, s2, s3];
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSKIPToNextStatement() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$parseREPORTNonCompletedParsing();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseLineTerminator();
+ if (s2 !== peg$FAILED) {
+ s1 = [s1, s2];
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseREPORTNonCompletedParsing() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = [];
+ s2 = peg$parseNonLineTerminator();
+ if (s2 !== peg$FAILED) {
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ s2 = peg$parseNonLineTerminator();
+ }
+ } else {
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f5();
+ }
+ s0 = s1;
+
+ return s0;
+ }
+
+ function peg$parseNonLineTerminator() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = peg$parseLineTerminatorSequence();
+ peg$silentFails--;
+ if (s2 === peg$FAILED) {
+ s1 = undefined;
+ } else {
+ peg$currPos = s1;
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceCharacter();
+ if (s2 !== peg$FAILED) {
+ s1 = [s1, s2];
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseCommentSymbol() {
+ var s0;
+
+ if (input.charCodeAt(peg$currPos) === 59) {
+ s0 = peg$c4;
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e1); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseCommentSegment() {
+ var s0, s1, s2, s3, s4;
+
+ s0 = peg$currPos;
+ s1 = peg$parse_();
+ s2 = peg$parseCommentSymbol();
+ if (s2 !== peg$FAILED) {
+ s3 = peg$currPos;
+ s4 = peg$parseComment();
+ s3 = input.substring(s3, peg$currPos);
+ s0 = s3;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseSourceElement() {
+ var s0, s1;
+
+ peg$silentFails++;
+ s0 = peg$parseStatement();
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e2); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseArgList() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseArg();
+ if (s1 !== peg$FAILED) {
+ s2 = [];
+ s3 = peg$currPos;
+ s4 = peg$currPos;
+ peg$silentFails++;
+ s5 = peg$parse__();
+ peg$silentFails--;
+ peg$currPos = s4;
+ s4 = undefined;
+ s5 = peg$parseArg();
+ if (s5 !== peg$FAILED) {
+ s4 = [s4, s5];
+ s3 = s4;
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ while (s3 !== peg$FAILED) {
+ s2.push(s3);
+ s3 = peg$currPos;
+ s4 = peg$currPos;
+ peg$silentFails++;
+ s5 = peg$parse__();
+ peg$silentFails--;
+ peg$currPos = s4;
+ s4 = undefined;
+ s5 = peg$parseArg();
+ if (s5 !== peg$FAILED) {
+ s4 = [s4, s5];
+ s3 = s4;
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ }
+ peg$savedPos = s0;
+ s0 = peg$f6(s1, s2);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e3); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseArg() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = peg$parse__();
+ peg$silentFails--;
+ peg$currPos = s1;
+ s1 = undefined;
+ s2 = peg$parseArgStart();
+ if (s2 !== peg$FAILED) {
+ s3 = peg$parseArgBody();
+ if (s3 !== peg$FAILED) {
+ s0 = s3;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e4); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseArgStart() {
+ var s0;
+
+ if (input.substr(peg$currPos, 2) === peg$c5) {
+ s0 = peg$c5;
+ peg$currPos += 2;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e5); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseArgBody() {
+ var s0, s1;
+
+ s0 = peg$currPos;
+ s1 = peg$parseArgWithValue();
+ if (s1 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = peg$parseArgWithoutValue();
+ if (s1 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parseArgWithValue() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseArgKey();
+ if (s1 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 61) {
+ s2 = peg$c7;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e7); }
+ }
+ if (s2 !== peg$FAILED) {
+ s3 = peg$parseStringLiteralAllowWhiteSpace();
+ if (s3 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f7(s1, s3);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e6); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseArgWithoutValue() {
+ var s0, s1;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseArgKey();
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f8(s1);
+ }
+ s0 = s1;
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e8); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseReservedWord() {
+ var s0, s1;
+
+ peg$silentFails++;
+ s0 = peg$parseChangeBgToken();
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e9); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseChangeBgToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 8) === peg$c9) {
+ s1 = peg$c9;
+ peg$currPos += 8;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e11); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e10); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseChangeFigureToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 12) === peg$c10) {
+ s1 = peg$c10;
+ peg$currPos += 12;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e13); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e12); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseBgmToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 3) === peg$c11) {
+ s1 = peg$c11;
+ peg$currPos += 3;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e15); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e14); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseVideoToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 9) === peg$c12) {
+ s1 = peg$c12;
+ peg$currPos += 9;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e17); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e16); }
+ }
+
+ return s0;
+ }
+
+ function peg$parsePixiToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 11) === peg$c13) {
+ s1 = peg$c13;
+ peg$currPos += 11;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e19); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e18); }
+ }
+
+ return s0;
+ }
+
+ function peg$parsePixiInitToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 8) === peg$c14) {
+ s1 = peg$c14;
+ peg$currPos += 8;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e21); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e20); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseIntroToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 5) === peg$c15) {
+ s1 = peg$c15;
+ peg$currPos += 5;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e23); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e22); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseMiniAvatarToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 10) === peg$c16) {
+ s1 = peg$c16;
+ peg$currPos += 10;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e25); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e24); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseChangeSceneToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 11) === peg$c17) {
+ s1 = peg$c17;
+ peg$currPos += 11;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e27); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e26); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseChooseToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 6) === peg$c18) {
+ s1 = peg$c18;
+ peg$currPos += 6;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e29); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e28); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseEndToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 3) === peg$c19) {
+ s1 = peg$c19;
+ peg$currPos += 3;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e31); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e30); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetComplexAnimationToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 19) === peg$c20) {
+ s1 = peg$c20;
+ peg$currPos += 19;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e33); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e32); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetFilterToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 9) === peg$c21) {
+ s1 = peg$c21;
+ peg$currPos += 9;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e35); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e34); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseLabelToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 5) === peg$c22) {
+ s1 = peg$c22;
+ peg$currPos += 5;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e37); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e36); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseJumpLabelToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 9) === peg$c23) {
+ s1 = peg$c23;
+ peg$currPos += 9;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e39); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e38); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseChooseLabelToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 11) === peg$c24) {
+ s1 = peg$c24;
+ peg$currPos += 11;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e41); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e40); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetVarToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 6) === peg$c25) {
+ s1 = peg$c25;
+ peg$currPos += 6;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e43); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e42); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseIfToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 2) === peg$c26) {
+ s1 = peg$c26;
+ peg$currPos += 2;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e45); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e44); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseCallSceneToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 9) === peg$c27) {
+ s1 = peg$c27;
+ peg$currPos += 9;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e47); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e46); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseShowVarsToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 8) === peg$c28) {
+ s1 = peg$c28;
+ peg$currPos += 8;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e49); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e48); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseUnlockCgToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 8) === peg$c29) {
+ s1 = peg$c29;
+ peg$currPos += 8;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e51); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e50); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseUnlockBgmToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 9) === peg$c30) {
+ s1 = peg$c30;
+ peg$currPos += 9;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e53); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e52); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseFilmModeToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 8) === peg$c31) {
+ s1 = peg$c31;
+ peg$currPos += 8;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e55); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e54); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetTextboxToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 10) === peg$c32) {
+ s1 = peg$c32;
+ peg$currPos += 10;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e57); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e56); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetAnimationToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 12) === peg$c33) {
+ s1 = peg$c33;
+ peg$currPos += 12;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e59); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e58); }
+ }
+
+ return s0;
+ }
+
+ function peg$parsePlayEffectToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 10) === peg$c34) {
+ s1 = peg$c34;
+ peg$currPos += 10;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e61); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e60); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetTempAnimationToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 16) === peg$c35) {
+ s1 = peg$c35;
+ peg$currPos += 16;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e63); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e62); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseCommentToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 7) === peg$c36) {
+ s1 = peg$c36;
+ peg$currPos += 7;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e65); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e64); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetTransformToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 12) === peg$c37) {
+ s1 = peg$c37;
+ peg$currPos += 12;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e67); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e66); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetTransitionToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 13) === peg$c38) {
+ s1 = peg$c38;
+ peg$currPos += 13;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e69); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e68); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseGetUserInputToken() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 12) === peg$c39) {
+ s1 = peg$c39;
+ peg$currPos += 12;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e71); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseIdentifierPart();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e70); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseArgKey() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = [];
+ s2 = peg$parseKeyCharacter();
+ if (s2 !== peg$FAILED) {
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ s2 = peg$parseKeyCharacter();
+ }
+ } else {
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s0 = input.substring(s0, peg$currPos);
+ } else {
+ s0 = s1;
+ }
+
+ return s0;
+ }
+
+ function peg$parseKeyCharacter() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = input.charAt(peg$currPos);
+ if (peg$r0.test(s2)) {
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e72); }
+ }
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseWhiteSpace();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseLineTerminator();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseEOS();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseArgStart();
+ }
+ }
+ }
+ }
+ peg$silentFails--;
+ if (s2 === peg$FAILED) {
+ s1 = undefined;
+ } else {
+ peg$currPos = s1;
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceCharacter();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f9();
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseStringLiteral() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 39) {
+ s1 = peg$c40;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e74); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = [];
+ s3 = peg$parseSingleStringCharacter();
+ while (s3 !== peg$FAILED) {
+ s2.push(s3);
+ s3 = peg$parseSingleStringCharacter();
+ }
+ if (input.charCodeAt(peg$currPos) === 39) {
+ s3 = peg$c40;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e74); }
+ }
+ if (s3 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f10(s2);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 34) {
+ s1 = peg$c42;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e75); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = [];
+ s3 = peg$parseDoubleStringCharacter();
+ while (s3 !== peg$FAILED) {
+ s2.push(s3);
+ s3 = peg$parseDoubleStringCharacter();
+ }
+ if (input.charCodeAt(peg$currPos) === 34) {
+ s3 = peg$c42;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e75); }
+ }
+ if (s3 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f11(s2);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = [];
+ s2 = peg$parseStringCharacter();
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ s2 = peg$parseStringCharacter();
+ }
+ peg$savedPos = s0;
+ s1 = peg$f12(s1);
+ s0 = s1;
+ }
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e73); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseStringLiteralAllowWhiteSpace() {
+ var s0, s1, s2, s3;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 39) {
+ s1 = peg$c40;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e74); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = [];
+ s3 = peg$parseSingleStringCharacter();
+ while (s3 !== peg$FAILED) {
+ s2.push(s3);
+ s3 = peg$parseSingleStringCharacter();
+ }
+ if (input.charCodeAt(peg$currPos) === 39) {
+ s3 = peg$c40;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e74); }
+ }
+ if (s3 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f13(s2);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 34) {
+ s1 = peg$c42;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e75); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = [];
+ s3 = peg$parseDoubleStringCharacter();
+ while (s3 !== peg$FAILED) {
+ s2.push(s3);
+ s3 = peg$parseDoubleStringCharacter();
+ }
+ if (input.charCodeAt(peg$currPos) === 34) {
+ s3 = peg$c42;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e75); }
+ }
+ if (s3 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f14(s2);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = [];
+ s2 = peg$parseStringCharacterAllowWhiteSpace();
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ s2 = peg$parseStringCharacterAllowWhiteSpace();
+ }
+ peg$savedPos = s0;
+ s1 = peg$f15(s1);
+ s0 = s1;
+ }
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e76); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSingleStringCharacter() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = input.charAt(peg$currPos);
+ if (peg$r1.test(s2)) {
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e77); }
+ }
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseLineTerminator();
+ }
+ peg$silentFails--;
+ if (s2 === peg$FAILED) {
+ s1 = undefined;
+ } else {
+ peg$currPos = s1;
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceCharacter();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f16();
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 92) {
+ s1 = peg$c43;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e78); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseEscapeSequence();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f17(s2);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parseDoubleStringCharacter() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = input.charAt(peg$currPos);
+ if (peg$r2.test(s2)) {
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e79); }
+ }
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseLineTerminator();
+ }
+ peg$silentFails--;
+ if (s2 === peg$FAILED) {
+ s1 = undefined;
+ } else {
+ peg$currPos = s1;
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceCharacter();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f18();
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 92) {
+ s1 = peg$c43;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e78); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseEscapeSequence();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f19(s2);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parseStringCharacter() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = peg$parseCommentSymbol();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseLineTerminator();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseEOS();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseWhiteSpace();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseArgStart();
+ }
+ }
+ }
+ }
+ peg$silentFails--;
+ if (s2 === peg$FAILED) {
+ s1 = undefined;
+ } else {
+ peg$currPos = s1;
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceCharacter();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f20();
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseStringCharacterAllowWhiteSpace() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = peg$parseCommentSymbol();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseLineTerminator();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseEOS();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseArgStart();
+ }
+ }
+ }
+ peg$silentFails--;
+ if (s2 === peg$FAILED) {
+ s1 = undefined;
+ } else {
+ peg$currPos = s1;
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceCharacter();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f21();
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseEscapeSequence() {
+ var s0, s1, s2, s3;
+
+ s0 = peg$parseCharacterEscapeSequence();
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 48) {
+ s1 = peg$c44;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e80); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ peg$silentFails++;
+ s3 = peg$parseDecimalDigit();
+ peg$silentFails--;
+ if (s3 === peg$FAILED) {
+ s2 = undefined;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f22();
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseHexEscapeSequence();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseUnicodeEscapeSequence();
+ }
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parseCharacterEscapeSequence() {
+ var s0;
+
+ s0 = peg$parseSingleEscapeCharacter();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseNonEscapeCharacter();
+ }
+
+ return s0;
+ }
+
+ function peg$parseSingleEscapeCharacter() {
+ var s0, s1;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r3.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e81); }
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 98) {
+ s1 = peg$c45;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e82); }
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f23();
+ }
+ s0 = s1;
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 102) {
+ s1 = peg$c46;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e83); }
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f24();
+ }
+ s0 = s1;
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 110) {
+ s1 = peg$c47;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e84); }
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f25();
+ }
+ s0 = s1;
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 114) {
+ s1 = peg$c48;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e85); }
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f26();
+ }
+ s0 = s1;
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 116) {
+ s1 = peg$c49;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e86); }
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f27();
+ }
+ s0 = s1;
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 118) {
+ s1 = peg$c50;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e87); }
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f28();
+ }
+ s0 = s1;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parseBraceCharacterSequence() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r4.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e88); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseNonEscapeCharacter() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = peg$parseEscapeCharacter();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseLineTerminator();
+ }
+ peg$silentFails--;
+ if (s2 === peg$FAILED) {
+ s1 = undefined;
+ } else {
+ peg$currPos = s1;
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceCharacter();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f29();
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseEscapeCharacter() {
+ var s0;
+
+ s0 = peg$parseSingleEscapeCharacter();
+ if (s0 === peg$FAILED) {
+ s0 = input.charAt(peg$currPos);
+ if (peg$r5.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e89); }
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parseDecimalDigit() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r6.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e90); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseHexEscapeSequence() {
+ var s0, s1, s2, s3, s4, s5;
+
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 120) {
+ s1 = peg$c51;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e91); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ s3 = peg$currPos;
+ s4 = peg$parseHexDigit();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseHexDigit();
+ if (s5 !== peg$FAILED) {
+ s4 = [s4, s5];
+ s3 = s4;
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ if (s3 !== peg$FAILED) {
+ s2 = input.substring(s2, peg$currPos);
+ } else {
+ s2 = s3;
+ }
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f30(s2);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseUnicodeEscapeSequence() {
+ var s0, s1, s2, s3, s4, s5, s6, s7;
+
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 117) {
+ s1 = peg$c53;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e92); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$currPos;
+ s3 = peg$currPos;
+ s4 = peg$parseHexDigit();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseHexDigit();
+ if (s5 !== peg$FAILED) {
+ s6 = peg$parseHexDigit();
+ if (s6 !== peg$FAILED) {
+ s7 = peg$parseHexDigit();
+ if (s7 !== peg$FAILED) {
+ s4 = [s4, s5, s6, s7];
+ s3 = s4;
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ if (s3 !== peg$FAILED) {
+ s2 = input.substring(s2, peg$currPos);
+ } else {
+ s2 = s3;
+ }
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f31(s2);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseIdentifierStart() {
+ var s0, s1, s2;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r7.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e93); }
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 92) {
+ s1 = peg$c43;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e78); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseUnicodeEscapeSequence();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f32(s2);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parseHexDigit() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r8.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e94); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseIdentifierPart() {
+ var s0;
+
+ s0 = peg$parseIdentifierStart();
+ if (s0 === peg$FAILED) {
+ s0 = input.charAt(peg$currPos);
+ if (peg$r9.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e95); }
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parseUnicodeLetter() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r10.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e96); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseUnicodeCombiningMark() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r11.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e97); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSourceCharacter() {
+ var s0;
+
+ if (input.length > peg$currPos) {
+ s0 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e0); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseLineTerminator() {
+ var s0, s1;
+
+ peg$silentFails++;
+ s0 = input.charAt(peg$currPos);
+ if (peg$r12.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e99); }
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e98); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseLineTerminatorSequence() {
+ var s0, s1;
+
+ peg$silentFails++;
+ if (input.charCodeAt(peg$currPos) === 10) {
+ s0 = peg$c54;
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e101); }
+ }
+ if (s0 === peg$FAILED) {
+ if (input.substr(peg$currPos, 2) === peg$c55) {
+ s0 = peg$c55;
+ peg$currPos += 2;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e102); }
+ }
+ if (s0 === peg$FAILED) {
+ s0 = input.charAt(peg$currPos);
+ if (peg$r13.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e103); }
+ }
+ }
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e100); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseWhiteSpace() {
+ var s0, s1;
+
+ peg$silentFails++;
+ s0 = input.charAt(peg$currPos);
+ if (peg$r14.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e105); }
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e104); }
+ }
+
+ return s0;
+ }
+
+ function peg$parse_M_() {
+ var s0, s1;
+
+ s0 = [];
+ s1 = peg$parseWhiteSpace();
+ if (s1 === peg$FAILED) {
+ s1 = peg$parseLineTerminatorSequence();
+ }
+ if (s1 !== peg$FAILED) {
+ while (s1 !== peg$FAILED) {
+ s0.push(s1);
+ s1 = peg$parseWhiteSpace();
+ if (s1 === peg$FAILED) {
+ s1 = peg$parseLineTerminatorSequence();
+ }
+ }
+ } else {
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parse__() {
+ var s0, s1;
+
+ s0 = [];
+ s1 = peg$parseWhiteSpace();
+ if (s1 === peg$FAILED) {
+ s1 = peg$parseLineTerminatorSequence();
+ }
+ while (s1 !== peg$FAILED) {
+ s0.push(s1);
+ s1 = peg$parseWhiteSpace();
+ if (s1 === peg$FAILED) {
+ s1 = peg$parseLineTerminatorSequence();
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parse_() {
+ var s0, s1;
+
+ s0 = [];
+ s1 = peg$parseWhiteSpace();
+ while (s1 !== peg$FAILED) {
+ s0.push(s1);
+ s1 = peg$parseWhiteSpace();
+ }
+
+ return s0;
+ }
+
+ function peg$parseComment() {
+ var s0, s1, s2, s3, s4;
+
+ s0 = peg$currPos;
+ s1 = [];
+ s2 = peg$currPos;
+ s3 = peg$currPos;
+ peg$silentFails++;
+ s4 = peg$parseLineTerminator();
+ peg$silentFails--;
+ if (s4 === peg$FAILED) {
+ s3 = undefined;
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseSourceCharacter();
+ if (s4 !== peg$FAILED) {
+ s3 = [s3, s4];
+ s2 = s3;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ s2 = peg$currPos;
+ s3 = peg$currPos;
+ peg$silentFails++;
+ s4 = peg$parseLineTerminator();
+ peg$silentFails--;
+ if (s4 === peg$FAILED) {
+ s3 = undefined;
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseSourceCharacter();
+ if (s4 !== peg$FAILED) {
+ s3 = [s3, s4];
+ s2 = s3;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ }
+ s0 = input.substring(s0, peg$currPos);
+
+ return s0;
+ }
+
+ function peg$parseEOS() {
+ var s0, s1, s2, s3, s4;
+
+ s0 = peg$currPos;
+ s1 = peg$parse_();
+ if (input.charCodeAt(peg$currPos) === 59) {
+ s2 = peg$c4;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e1); }
+ }
+ if (s2 !== peg$FAILED) {
+ s3 = peg$parseComment();
+ s4 = peg$parseLineTerminatorSequence();
+ if (s4 !== peg$FAILED) {
+ s1 = [s1, s2, s3, s4];
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = peg$parse_();
+ s2 = peg$parseLineTerminatorSequence();
+ if (s2 !== peg$FAILED) {
+ s1 = [s1, s2];
+ s0 = s1;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ }
+
+ return s0;
+ }
+
+ function peg$parseEOF() {
+ var s0, s1;
+
+ s0 = peg$currPos;
+ peg$silentFails++;
+ if (input.length > peg$currPos) {
+ s1 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e0); }
+ }
+ peg$silentFails--;
+ if (s1 === peg$FAILED) {
+ s0 = undefined;
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseEmptyStatement() {
+ var s0, s1, s2, s3, s4;
+
+ s0 = peg$currPos;
+ s1 = peg$parse_();
+ s2 = peg$parseCommentSegment();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f33(s2);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = peg$currPos;
+ s3 = peg$parse_();
+ s4 = peg$parseLineTerminatorSequence();
+ if (s4 !== peg$FAILED) {
+ s3 = [s3, s4];
+ s2 = s3;
+ } else {
+ peg$currPos = s2;
+ s2 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s2 !== peg$FAILED) {
+ peg$currPos = s1;
+ s1 = undefined;
+ } else {
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f34();
+ }
+ s0 = s1;
+ }
+
+ return s0;
+ }
+
+ function peg$parseChangeBgStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseChangeBgToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f35(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e106); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseChangeFigureStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseChangeFigureToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f36(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e108); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseBgmStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseBgmToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f37(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e109); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseVideoStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseVideoToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f38(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e110); }
+ }
+
+ return s0;
+ }
+
+ function peg$parsePixiStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parsePixiToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f39(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e111); }
+ }
+
+ return s0;
+ }
+
+ function peg$parsePixiInitStatement() {
+ var s0, s1;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parsePixiInitToken();
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f40();
+ }
+ s0 = s1;
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e112); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseIntroStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseIntroToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteralAllowWhiteSpace();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f41(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e113); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseMiniAvatarStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseMiniAvatarToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f42(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e114); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseChangeSceneStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseChangeSceneToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f43(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e115); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseChooseStatement() {
+ var s0, s1, s2, s3, s4;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseChooseToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseChoiceList();
+ if (s4 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f44(s4);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e116); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseChoiceList() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseChoice();
+ if (s1 !== peg$FAILED) {
+ s2 = [];
+ s3 = peg$currPos;
+ s4 = peg$parseVerticalBar();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseChoice();
+ if (s5 !== peg$FAILED) {
+ s4 = [s4, s5];
+ s3 = s4;
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ while (s3 !== peg$FAILED) {
+ s2.push(s3);
+ s3 = peg$currPos;
+ s4 = peg$parseVerticalBar();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseChoice();
+ if (s5 !== peg$FAILED) {
+ s4 = [s4, s5];
+ s3 = s4;
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ }
+ peg$savedPos = s0;
+ s0 = peg$f45(s1, s2);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e117); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseVerticalBar() {
+ var s0;
+
+ if (input.charCodeAt(peg$currPos) === 124) {
+ s0 = peg$c62;
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e118); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseChoice() {
+ var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ if (input.charCodeAt(peg$currPos) === 40) {
+ s1 = peg$c63;
+ peg$currPos++;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e120); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseNoParenthesisExpression();
+ if (input.charCodeAt(peg$currPos) === 41) {
+ s3 = peg$c65;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e121); }
+ }
+ if (s3 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 91) {
+ s4 = peg$c66;
+ peg$currPos++;
+ } else {
+ s4 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e122); }
+ }
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseNoBracketExpression();
+ if (input.charCodeAt(peg$currPos) === 93) {
+ s6 = peg$c68;
+ peg$currPos++;
+ } else {
+ s6 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e123); }
+ }
+ if (s6 !== peg$FAILED) {
+ if (input.substr(peg$currPos, 2) === peg$c69) {
+ s7 = peg$c69;
+ peg$currPos += 2;
+ } else {
+ s7 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e124); }
+ }
+ if (s7 !== peg$FAILED) {
+ s8 = peg$parseChoiceTextLiteral();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s9 = peg$c56;
+ peg$currPos++;
+ } else {
+ s9 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s9 !== peg$FAILED) {
+ s10 = peg$parseChoiceDestinationLiteral();
+ peg$savedPos = s0;
+ s0 = peg$f46(s2, s5, s8, s10);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = peg$parseChoiceTextLiteral();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s2 = peg$c56;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s2 !== peg$FAILED) {
+ s3 = peg$parseChoiceDestinationLiteral();
+ peg$savedPos = s0;
+ s0 = peg$f47(s1, s3);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e119); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseNoParenthesisExpression() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = [];
+ s2 = peg$parseNoParenthesisExpressionCharacter();
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ s2 = peg$parseNoParenthesisExpressionCharacter();
+ }
+ peg$savedPos = s0;
+ s1 = peg$f48(s1);
+ s0 = s1;
+
+ return s0;
+ }
+
+ function peg$parseNoParenthesisExpressionCharacter() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = peg$parseLineTerminator();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseEOS();
+ if (s2 === peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 41) {
+ s2 = peg$c65;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e121); }
+ }
+ }
+ }
+ peg$silentFails--;
+ if (s2 === peg$FAILED) {
+ s1 = undefined;
+ } else {
+ peg$currPos = s1;
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceCharacter();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f49();
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseNoBracketExpression() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = [];
+ s2 = peg$parseNoBracketExpressionCharacter();
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ s2 = peg$parseNoBracketExpressionCharacter();
+ }
+ peg$savedPos = s0;
+ s1 = peg$f50(s1);
+ s0 = s1;
+
+ return s0;
+ }
+
+ function peg$parseNoBracketExpressionCharacter() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = peg$parseLineTerminator();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseEOS();
+ if (s2 === peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 93) {
+ s2 = peg$c68;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e123); }
+ }
+ }
+ }
+ peg$silentFails--;
+ if (s2 === peg$FAILED) {
+ s1 = undefined;
+ } else {
+ peg$currPos = s1;
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceCharacter();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f51();
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseChoiceTextLiteral() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = [];
+ s2 = peg$parseChoiceTextCharacter();
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ s2 = peg$parseChoiceTextCharacter();
+ }
+ s0 = input.substring(s0, peg$currPos);
+
+ return s0;
+ }
+
+ function peg$parseChoiceTextCharacter() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = peg$parseCommentSymbol();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseLineTerminator();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseEOS();
+ if (s2 === peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s2 = peg$c56;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ }
+ }
+ }
+ peg$silentFails--;
+ if (s2 === peg$FAILED) {
+ s1 = undefined;
+ } else {
+ peg$currPos = s1;
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceCharacter();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f52();
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseChoiceDestinationLiteral() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = [];
+ s2 = peg$parseChoiceDestinationCharacter();
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ s2 = peg$parseChoiceDestinationCharacter();
+ }
+ s0 = input.substring(s0, peg$currPos);
+
+ return s0;
+ }
+
+ function peg$parseChoiceDestinationCharacter() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = peg$parseCommentSymbol();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseLineTerminator();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseEOS();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseVerticalBar();
+ }
+ }
+ }
+ peg$silentFails--;
+ if (s2 === peg$FAILED) {
+ s1 = undefined;
+ } else {
+ peg$currPos = s1;
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceCharacter();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f53();
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseEndStatement() {
+ var s0, s1;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseEndToken();
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f54();
+ }
+ s0 = s1;
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e125); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetComplexAnimationStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseSetComplexAnimationToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f55(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e126); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetFilterStatement() {
+ var s0, s1;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseSetFilterToken();
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f56();
+ }
+ s0 = s1;
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e127); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseLabelStatement() {
+ var s0, s1, s2, s3, s4;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseLabelToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f57(s4);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e128); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseJumpLabelStatement() {
+ var s0, s1, s2, s3, s4;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseJumpLabelToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f58(s4);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e129); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetVarStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseSetVarToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseInternalArgWithValue();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f59(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = peg$parseSetVarToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseArgWithValue();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f60(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e130); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseInternalArgWithValue() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseArgKey();
+ if (s1 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 61) {
+ s2 = peg$c7;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e7); }
+ }
+ if (s2 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 40) {
+ s3 = peg$c63;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e120); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseInternalArgLiteral();
+ if (s4 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 41) {
+ s5 = peg$c65;
+ peg$currPos++;
+ } else {
+ s5 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e121); }
+ }
+ if (s5 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f61(s1, s4);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e6); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseInternalArgLiteral() {
+ var s0, s1, s2;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = [];
+ s2 = peg$parseInternalArgCharacter();
+ if (s2 !== peg$FAILED) {
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ s2 = peg$parseInternalArgCharacter();
+ }
+ } else {
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s0 = input.substring(s0, peg$currPos);
+ } else {
+ s0 = s1;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e131); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseInternalArgCharacter() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = input.charAt(peg$currPos);
+ if (peg$r15.test(s2)) {
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e132); }
+ }
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseLineTerminator();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseEOS();
+ if (s2 === peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 61) {
+ s2 = peg$c7;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e7); }
+ }
+ }
+ }
+ }
+ peg$silentFails--;
+ if (s2 === peg$FAILED) {
+ s1 = undefined;
+ } else {
+ peg$currPos = s1;
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceCharacter();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f62();
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseCallSceneStatement() {
+ var s0, s1, s2, s3, s4;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseCallSceneToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f63(s4);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e133); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseShowVarsStatement() {
+ var s0, s1;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseShowVarsToken();
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f64();
+ }
+ s0 = s1;
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e134); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseUnlockCgStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseUnlockCgToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f65(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e135); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseUnlockBgmStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseUnlockBgmToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f66(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e136); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseFilmModeStatement() {
+ var s0, s1, s2, s3, s4;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseFilmModeToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteralAllowWhiteSpace();
+ if (s4 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f67(s4);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e137); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetTextboxStatement() {
+ var s0, s1, s2, s3, s4;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseSetTextboxToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f68(s4);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e138); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetAnimationStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseSetAnimationToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f69(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e139); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetTransitionStatement() {
+ var s0, s1, s2, s3, s4;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseSetTransitionToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseArgList();
+ if (s4 === peg$FAILED) {
+ s4 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f70(s4);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e140); }
+ }
+
+ return s0;
+ }
+
+ function peg$parsePlayEffectStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parsePlayEffectToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f71(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e141); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetTempAnimationStatement() {
+ var s0, s1, s2, s3, s4, s5, s6, s7;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseSetTempAnimationToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ if (input.charCodeAt(peg$currPos) === 91) {
+ s4 = peg$c66;
+ peg$currPos++;
+ } else {
+ s4 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e122); }
+ }
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseNoBracketExpression();
+ if (input.charCodeAt(peg$currPos) === 93) {
+ s6 = peg$c68;
+ peg$currPos++;
+ } else {
+ s6 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e123); }
+ }
+ if (s6 !== peg$FAILED) {
+ s7 = peg$parseArgList();
+ if (s7 === peg$FAILED) {
+ s7 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f72(s5, s7);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e142); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSetTransformStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseSetTransformToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseStringLiteral();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f73(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e143); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseGetUserInputStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseGetUserInputToken();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parse__();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s3 = peg$c56;
+ peg$currPos++;
+ } else {
+ s3 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseArgKey();
+ if (s4 !== peg$FAILED) {
+ s5 = peg$parseArgList();
+ if (s5 === peg$FAILED) {
+ s5 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f74(s4, s5);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e144); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseSayStatement() {
+ var s0, s1, s2, s3, s4;
+
+ peg$silentFails++;
+ s0 = peg$currPos;
+ s1 = peg$parseSpeakerLiteral();
+ if (input.charCodeAt(peg$currPos) === 58) {
+ s2 = peg$c56;
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e107); }
+ }
+ if (s2 !== peg$FAILED) {
+ s3 = peg$parseStringLiteralAllowWhiteSpace();
+ if (s3 !== peg$FAILED) {
+ s4 = peg$parseArgList();
+ if (s4 === peg$FAILED) {
+ s4 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f75(s1, s3, s4);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ if (s0 === peg$FAILED) {
+ s0 = peg$currPos;
+ s1 = peg$parseNonEmptyStringLiteralAllowWhiteSpace();
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseArgList();
+ if (s2 === peg$FAILED) {
+ s2 = null;
+ }
+ peg$savedPos = s0;
+ s0 = peg$f76(s1, s2);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e145); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseNonEmptyStringLiteralAllowWhiteSpace() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = [];
+ s2 = peg$parseStringCharacterAllowWhiteSpace();
+ if (s2 !== peg$FAILED) {
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ s2 = peg$parseStringCharacterAllowWhiteSpace();
+ }
+ } else {
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s0 = input.substring(s0, peg$currPos);
+ } else {
+ s0 = s1;
+ }
+
+ return s0;
+ }
+
+ function peg$parseSpeakerLiteral() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = [];
+ s2 = peg$parseSpeakerCharacter();
+ while (s2 !== peg$FAILED) {
+ s1.push(s2);
+ s2 = peg$parseSpeakerCharacter();
+ }
+ s0 = input.substring(s0, peg$currPos);
+
+ return s0;
+ }
+
+ function peg$parseSpeakerCharacter() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ peg$silentFails++;
+ s2 = input.charAt(peg$currPos);
+ if (peg$r16.test(s2)) {
+ peg$currPos++;
+ } else {
+ s2 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e146); }
+ }
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseLineTerminator();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseEOS();
+ if (s2 === peg$FAILED) {
+ s2 = peg$parseArgStart();
+ }
+ }
+ }
+ peg$silentFails--;
+ if (s2 === peg$FAILED) {
+ s1 = undefined;
+ } else {
+ peg$currPos = s1;
+ s1 = peg$FAILED;
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseSourceCharacter();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f77();
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseStatement() {
+ var s0, s1;
+
+ peg$silentFails++;
+ s0 = peg$parseChangeBgStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseChangeFigureStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseBgmStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseVideoStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parsePixiStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parsePixiInitStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseIntroStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseMiniAvatarStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseChangeSceneStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseChooseStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseEndStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseSetComplexAnimationStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseSetFilterStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseLabelStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseJumpLabelStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseSetVarStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseCallSceneStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseShowVarsStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseUnlockCgStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseUnlockBgmStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseFilmModeStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseSetTextboxStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseSetAnimationStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseSetTransitionStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parsePlayEffectStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseSetTempAnimationStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseSetTransformStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseGetUserInputStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseSayStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseEmptyStatement();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseERRORStatement();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ peg$silentFails--;
+ if (s0 === peg$FAILED) {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e147); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseERRORStatement() {
+ var s0, s1, s2, s3, s4, s5;
+
+ s0 = peg$currPos;
+ s1 = peg$currPos;
+ s2 = [];
+ s3 = peg$currPos;
+ s4 = peg$currPos;
+ peg$silentFails++;
+ s5 = peg$parseEOS();
+ peg$silentFails--;
+ if (s5 === peg$FAILED) {
+ s4 = undefined;
+ } else {
+ peg$currPos = s4;
+ s4 = peg$FAILED;
+ }
+ if (s4 !== peg$FAILED) {
+ if (input.length > peg$currPos) {
+ s5 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s5 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e0); }
+ }
+ if (s5 !== peg$FAILED) {
+ s4 = [s4, s5];
+ s3 = s4;
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ if (s3 !== peg$FAILED) {
+ while (s3 !== peg$FAILED) {
+ s2.push(s3);
+ s3 = peg$currPos;
+ s4 = peg$currPos;
+ peg$silentFails++;
+ s5 = peg$parseEOS();
+ peg$silentFails--;
+ if (s5 === peg$FAILED) {
+ s4 = undefined;
+ } else {
+ peg$currPos = s4;
+ s4 = peg$FAILED;
+ }
+ if (s4 !== peg$FAILED) {
+ if (input.length > peg$currPos) {
+ s5 = input.charAt(peg$currPos);
+ peg$currPos++;
+ } else {
+ s5 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e0); }
+ }
+ if (s5 !== peg$FAILED) {
+ s4 = [s4, s5];
+ s3 = s4;
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s3;
+ s3 = peg$FAILED;
+ }
+ }
+ } else {
+ s2 = peg$FAILED;
+ }
+ if (s2 !== peg$FAILED) {
+ s1 = input.substring(s1, peg$currPos);
+ } else {
+ s1 = s2;
+ }
+ if (s1 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s1 = peg$f78(s1);
+ }
+ s0 = s1;
+
+ return s0;
+ }
+
+ function peg$parseLl() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r17.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e148); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseLm() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r18.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e149); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseLo() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r19.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e150); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseLt() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r20.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e151); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseLu() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r21.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e152); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseMc() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r22.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e153); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseMn() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r23.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e154); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseNd() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r24.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e155); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseNl() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r25.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e156); }
+ }
+
+ return s0;
+ }
+
+ function peg$parsePc() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r26.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e157); }
+ }
+
+ return s0;
+ }
+
+ function peg$parseZs() {
+ var s0;
+
+ s0 = input.charAt(peg$currPos);
+ if (peg$r27.test(s0)) {
+ peg$currPos++;
+ } else {
+ s0 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e158); }
+ }
+
+ return s0;
+ }
+
+
+ // ----- Error Handling -----
+ let errors = [];
+ function report(message, loc = location()) {
+ errors.push({
+ location: `${loc.start.offset}(${loc.start.line}:${loc.start.column})..${loc.end.offset}(${loc.end.line}:${loc.end.column})`,
+ message,
+ });
+ }
+
+ peg$result = peg$startRuleFunction();
+
+ if (options.peg$library) {
+ return /** @type {any} */ ({
+ peg$result,
+ peg$currPos,
+ peg$FAILED,
+ peg$maxFailExpected,
+ peg$maxFailPos
+ });
+ }
+ if (peg$result !== peg$FAILED && peg$currPos === input.length) {
+ return peg$result;
+ } else {
+ if (peg$result !== peg$FAILED && peg$currPos < input.length) {
+ peg$fail(peg$endExpectation());
+ }
+
+ throw peg$buildStructuredError(
+ peg$maxFailExpected,
+ peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null,
+ peg$maxFailPos < input.length
+ ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1)
+ : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)
+ );
+ }
+}
+
+const peg$allowedStartRules = [
+ "Start"
+];
+
+export {
+ peg$allowedStartRules as StartRules,
+ peg$SyntaxError as SyntaxError,
+ peg$parse as parse
+};
+//# sourceMappingURL=parser.js.map
diff --git a/packages/parser/src/parser.js.map b/packages/parser/src/parser.js.map
new file mode 100644
index 000000000..1fa566992
--- /dev/null
+++ b/packages/parser/src/parser.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["parser.pegjs"],"names":["$top_level_initializer","Start","program","REPORT","Program","body","SourceElements","head","tail","SourceElementTail","SKIPToNextStatement","REPORTNonCompletedParsing","NonLineTerminator","CommentSymbol","CommentSegment","SourceElement","ArgList","Arg","ArgStart","ArgBody","ArgWithValue","key","value","ArgWithoutValue","ReservedWord","ChangeBgToken","ChangeFigureToken","BgmToken","VideoToken","PixiToken","PixiInitToken","IntroToken","MiniAvatarToken","ChangeSceneToken","ChooseToken","EndToken","SetComplexAnimationToken","SetFilterToken","LabelToken","JumpLabelToken","ChooseLabelToken","SetVarToken","IfToken","CallSceneToken","ShowVarsToken","UnlockCgToken","UnlockBgmToken","FilmModeToken","SetTextboxToken","SetAnimationToken","PlayEffectToken","SetTempAnimationToken","CommentToken","SetTransformToken","SetTransitionToken","GetUserInputToken","ArgKey","KeyCharacter","StringLiteral","sequence","StringLiteralAllowWhiteSpace","SingleStringCharacter","DoubleStringCharacter","StringCharacter","StringCharacterAllowWhiteSpace","EscapeSequence","CharacterEscapeSequence","SingleEscapeCharacter","BraceCharacterSequence","NonEscapeCharacter","EscapeCharacter","DecimalDigit","HexEscapeSequence","digits","UnicodeEscapeSequence","IdentifierStart","HexDigit","IdentifierPart","UnicodeLetter","UnicodeCombiningMark","SourceCharacter","LineTerminator","LineTerminatorSequence","WhiteSpace","_M_","__","_","Comment","EOS","EOF","EmptyStatement","comment","ChangeBgStatement","fileName","args","ChangeFigureStatement","BgmStatement","VideoStatement","PixiStatement","performName","PixiInitStatement","IntroStatement","lines","MiniAvatarStatement","ChangeSceneStatement","ChooseStatement","choices","ChoiceList","VerticalBar","Choice","sexp","cexp","text","dest","NoParenthesisExpression","NoParenthesisExpressionCharacter","NoBracketExpression","NoBracketExpressionCharacter","ChoiceTextLiteral","ChoiceTextCharacter","ChoiceDestinationLiteral","ChoiceDestinationCharacter","EndStatement","SetComplexAnimationStatement","animationName","SetFilterStatement","LabelStatement","labelName","JumpLabelStatement","SetVarStatement","kv","InternalArgWithValue","InternalArgLiteral","InternalArgCharacter","CallSceneStatement","sceneName","ShowVarsStatement","UnlockCgStatement","name","UnlockBgmStatement","FilmModeStatement","content","SetTextboxStatement","SetAnimationStatement","SetTransitionStatement","PlayEffectStatement","SetTempAnimationStatement","json","SetTransformStatement","GetUserInputStatement","into","SayStatement","speaker","NonEmptyStringLiteralAllowWhiteSpace","SpeakerLiteral","SpeakerCharacter","Statement","ERRORStatement","err","Ll","Lm","Lo","Lt","Lu","Mc","Mn","Nd","Nl","Pc","Zs","$initializer"],"mappings":";;;;;AAAEA;AACFA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAkB2B,2CAA2C,E;kCACxC,2CAA2C,E;2BAG/D,mCAAmC,E;+BAGjB;AAC5B;AACA,IAAI,E;qCAGgE;AACpE;AACA,IAAI,E;2BAUsB;AAC1B;AACA,IAAI,E;qCAkB4B;AAChC;AACA,IAAI,E;qCAYqD;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;8BAGc;AAClB;AACA,IAAI,E;2BAsDkH,gBAAgB,E;oCAcvF,2BAA2B,E;oCAC3B,2BAA2B,E;oCACzC,0BAA0B,E;oCAGZ,2BAA2B,E;oCAC3B,2BAA2B,E;oCAC1B,2BAA2B,E;4BAGrB,gBAAgB,E;oCAClC,kBAAkB,E;4BAGA,gBAAgB,E;oCAClC,kBAAkB,E;4BAGiC,gBAAgB,E;4BAG7B,gBAAgB,E;4BAMjE,cAAc,E;4BAY3B,cAAc,E;4BACd,cAAc,E;4BACd,cAAc,E;4BACd,cAAc,E;4BACd,cAAc,E;4BACd,cAAc,E;4BASiC,gBAAgB,E;kCAYpC;AACvC;AACA,IAAI,E;kCAGqD;AACzD;AACA,IAAI,E;oCAQuC,kBAAkB,E;mCA8E7B;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;4BAC+B;AACnC;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;0CAG6D;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;0CAGiE;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;0CAGwD;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;0CAG0D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;6CAG4D;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;4BAGiB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;uCAGsE;AAC1E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;0CAG+D;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;0CAGgE;AACpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;mCAGyC;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;sCAG0C;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;kDAMuI;AAC3I;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;sCAC4D;AAChE;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;oCAG8C,2BAA2B,E;4BAGxB,gBAAgB,E;oCAGvB,2BAA2B,E;4BAGpB,gBAAgB,E;4BAMA,gBAAgB,E;4BAMR,gBAAgB,E;4BAG7E;AAChB;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;+CAG6E;AACjF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;4BAGkB;AACtB;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;qCAG6C;AACjD;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;qCAGiD;AACrD;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;oCAI4D;AAChE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;oCAEoD;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;sCAGmD;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;4BAMuE,gBAAgB,E;qCAGtC;AACrD;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;4BAGiB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;sCAGyD;AAC7D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;sCAG0D;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;mCAG6D;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;mCAGgD;AACpD;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;yCAGgE;AACpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;gCAG2C;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;sCAG2D;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;sCAG+E;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;sCAG6D;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;sCAGsD;AAC1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;kDAIiF;AACrF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;yCAC8D;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,E;4BAS4E,gBAAgB,E;+BA8C/D,+CAA+C,E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA9zBhFC,cAAK;;;AACgB;AAAfC,SAAQ;AAAQ;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAkD;AAClD;AAAfA,WAAQ;AAAQ;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADkD;;;;;WAGvEC,eAAM;;;AACE;AAAD;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAA;AAAA;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAA;AAAA;AAAA;AAAC;AAAA;AAAA;AAAA;AAAA;;;;;WAERC,gBAAO;;;AACmB;AAApBC,SAAK;AAAL;AAAAA,WAAmB;AAAA;AAAC;AAAA;AAAA;;;;;WAI1BC,uBAAc;;;AACU;AAAlBC,SAAK;AAA6B;AAAf;AAAc;AAAA;AAAA;AAAEC,WAAK;AAAC;AAAiB;AAAA;AAAjB;AAAiB;AAAxB;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAIxCC,0BAAiB;;;AACR;AAAH;AAAiB;AAAb;AAAa;AAAC;AAAc;AAAA;AAAA;AAAf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAgB;AACd;AAAnB;AAAiC;AAAb;AAAa;AAAC;AAAc;AAAA;AAAA;AAAf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;;;;;WAGvCC,4BAAmB;;;AACY;AAAzB;AAAyB;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAE/BC,kCAAyB;;;AACD;AAAD;AAAjB;AAAiB;AAAA;AAAA;AAAjB;AAAiB;AAAA;AAAA;AAAA;AAAC;AAAA;AAAA;AAAA;AAAA;;;;;WAIxBC,0BAAiB;;;AACc;AAAzB;AAAA;AAAE;AAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAE/BC,sBAAa;;;AACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAENC,uBAAc;;;AACP;AAAD;AAAE;AAAa;AAAE;AAAC;AAAD;AAAF;AAAA;AAAA;AAAA;AAAA;;;;;WAErBC,sBAAa;;;AAAb;AACM;AAAS;AAAA;AAAA;AAAA;AAAA;;;;;WAKfC,gBAAO;;;AAAP;AACc;AAART,SAAK;AAAG;AAACC,WAAK;AAAI;AAAH;AAAA;AAAC;AAAD;AAAA;AAAA;AAAI;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAK;AAAA;AAAL;AAAH;AAAA;AAAC;AAAD;AAAA;AAAA;AAAI;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAK;AAAf;AAAA;AAAA;AAAA;AAAA;AAAA;AAET;AAAA;AAAA;AAAA;AAAA;;;;;WAELS,YAAG;;;AAAH;AACS;AAAH;AAAA;AAAC;AAAD;AAAA;AAAA;AAAI;AAAQ;AAAE;AAAF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAS;AAAA;AAAA;AAAA;AAAA;;;;;WAE3BC,iBAAQ;;;AACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAENC,gBAAO;;;AACD;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAa;AAAG;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAH;;;;;WAEnBC,qBAAY;;;AAAZ;AACgB;AAAVC,SAAI;AAAU;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAACC,aAAM;AAAP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAef;AAAA;AAAA;AAAA;AAAA;;;;;WAELC,wBAAe;;;AAAf;AACgB;AAAVF,SAAI;AAAM;AAAA;AAAA;AAAA;AAAA;AAEX;AAAA;AAAA;AAAA;AAAA;;;;;WAILG,qBAAY;;;AAAZ;AACM;AAAO;AAAA;AAAA;AAAA;AAAA;;;;;WAObC,sBAAa;;;AAAb;AAAiE;AAAV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAU;AAAe;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA8B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,0BAAiB;;;AAAjB;AAAqE;AAAd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAc;AAAW;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA0B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,iBAAQ;;;AAAR;AAA4D;AAAL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAK;AAAoB;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAApB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAmC;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,mBAAU;;;AAAV;AAAkE;AAAX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAW;AAAc;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA6B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,kBAAS;;;AAAT;AAAoE;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAa;AAAY;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA2B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,sBAAa;;;AAAb;AAAiE;AAAV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAU;AAAe;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA8B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,mBAAU;;;AAAV;AAA8D;AAAP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO;AAAkB;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAlB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAiC;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,wBAAe;;;AAAf;AAAmE;AAAZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAY;AAAa;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA4B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,yBAAgB;;;AAAhB;AAAoE;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAa;AAAY;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA2B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,oBAAW;;;AAAX;AAA+D;AAAR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAQ;AAAiB;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAgC;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,iBAAQ;;;AAAR;AAA4D;AAAL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAK;AAAoB;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAApB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAmC;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,iCAAwB;;;AAAxB;AAA4E;AAArB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAqB;AAAI;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAmB;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,uBAAc;;;AAAd;AAAkE;AAAX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAW;AAAc;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA6B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,mBAAU;;;AAAV;AAA8D;AAAP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO;AAAkB;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAlB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAiC;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,uBAAc;;;AAAd;AAAkE;AAAX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAW;AAAc;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA6B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,yBAAgB;;;AAAhB;AAAoE;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAa;AAAY;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA2B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,oBAAW;;;AAAX;AAA+D;AAAR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAQ;AAAiB;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAgC;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,gBAAO;;;AAAP;AAA2D;AAAJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAI;AAAqB;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAArB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAoC;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,uBAAc;;;AAAd;AAAkE;AAAX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAW;AAAc;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA6B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,sBAAa;;;AAAb;AAAiE;AAAV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAU;AAAe;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA8B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,sBAAa;;;AAAb;AAAiE;AAAV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAU;AAAe;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA8B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,uBAAc;;;AAAd;AAAkE;AAAX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAW;AAAc;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA6B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,sBAAa;;;AAAb;AAAiE;AAAV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAU;AAAe;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA8B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,wBAAe;;;AAAf;AAAmE;AAAZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAY;AAAa;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA4B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,0BAAiB;;;AAAjB;AAAqE;AAAd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAc;AAAW;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA0B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,wBAAe;;;AAAf;AAAmE;AAAZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAY;AAAa;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA4B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,8BAAqB;;;AAArB;AAAyE;AAAlB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAkB;AAAO;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAsB;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,qBAAY;;;AAAZ;AAAgE;AAAT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAS;AAAgB;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAhB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA+B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,0BAAiB;;;AAAjB;AAAqE;AAAd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAc;AAAW;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA0B;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,2BAAkB;;;AAAlB;AAAsE;AAAf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAe;AAAU;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAyB;AAAA;AAAA;AAAA;AAAA;;;;;WAC/FC,0BAAiB;;;AAAjB;AAAqE;AAAd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAc;AAAW;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA0B;AAAA;AAAA;AAAA;AAAA;;;;;WAQ/FC,eAAM;;;AACA;AAAa;AAAZ;AAAY;AAAA;AAAA;AAAZ;AAAY;AAAA;AAAA;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;;;;;WAENC,qBAAY;;;AACwF;AAA9F;AAAA;AAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAgF;AAAjC;AAAiC;AAApB;AAAoB;AAAH;AAAG;AAAG;AAAH;AAAA;AAAA;AAAA;AAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAapGC,sBAAa;;;AAAb;AACS;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAmC;AAA/BC,WAAS;AAAA;AAAqB;AAAA;AAArB;AAAqB;AAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACkC;AAAlE;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAmC;AAA/BA,aAAS;AAAA;AAAqB;AAAA;AAArB;AAAqB;AAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAkC;AAC5C;AAAzBA,aAAS;AAAA;AAAe;AAAA;AAAf;AAAe;AAAC;AAAA;AAAA;AAD4C;AAAA;AACf;AAAA;AAAA;AAAA;AAAA;;;;;WAE5DC,qCAA4B;;;AAA5B;AACS;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAmC;AAA/BD,WAAS;AAAA;AAAqB;AAAA;AAArB;AAAqB;AAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACkC;AAAlE;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAmC;AAA/BA,aAAS;AAAA;AAAqB;AAAA;AAArB;AAAqB;AAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAkC;AAC7B;AAAxCA,aAAS;AAAA;AAA8B;AAAA;AAA9B;AAA8B;AAAC;AAAA;AAAA;AAD6B;AAAA;AACC;AAAA;AAAA;AAAA;AAAA;;;;;WAE5EE,8BAAqB;;;AACe;AAA9B;AAAA;AAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAU;AAAG;AAAH;AAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAmC;AAC7D;AAAJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAI;AAACF,aAAS;AAAV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAD6D;;;;;WAGvEG,8BAAqB;;;AACe;AAA9B;AAAA;AAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAU;AAAG;AAAH;AAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAmC;AAC7D;AAAJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAI;AAACH,aAAS;AAAV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAD6D;;;;;WAGvEI,wBAAe;;;AACsD;AAA/D;AAAA;AAAE;AAAiD;AAAjC;AAAiC;AAAhB;AAAgB;AAAV;AAAU;AAAG;AAAH;AAAA;AAAA;AAAA;AAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAErEC,uCAA8B;;;AAC0B;AAAlD;AAAA;AAAE;AAAoC;AAApB;AAAoB;AAAH;AAAG;AAAG;AAAH;AAAA;AAAA;AAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAIxDC,uBAAc;;;AACR;AAEiB;AADd;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAAC;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACc;AAAjB;AAAiB;AACjB;AADiB;AAAA;AAAA;;;;;WAGvBC,gCAAuB;;;AACjB;AAAqB;AACrB;AADqB;;;;;WAG3BC,8BAAqB;;;AACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOqB;AAJlB;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAAA;AAAA;AAAA;AAAA;AAIkB;AAHlB;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAAA;AAAA;AAAA;AAAA;AAGkB;AAFlB;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAAA;AAAA;AAAA;AAAA;AAEkB;AADlB;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAAA;AAAA;AAAA;AAAA;AACkB;AAAlB;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAAA;AAAA;AAAA;AAAA;AAAkB;AAClB;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAAA;AAAA;AAAA;AAAA;AADkB;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAG3BC,+BAAsB;;;AAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAKNC,2BAAkB;;;AACuB;AAAnC;AAAA;AAAE;AAAe;AAAG;AAAH;AAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAEzCC,wBAAe;;;AACT;AAAqB;AACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADqB;;;;;WAK3BC,qBAAY;;;AACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAENC,0BAAiB;;;AACR;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAACC,WAAO;AAAU;AAAR;AAAQ;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAjB;AAAAA,aAAO;AAAA;AAAPA,aAAO;AAAA;AAAR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAITC,8BAAqB;;;AACZ;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAACD,WAAO;AAAU;AAAR;AAA0B;AAAjB;AAAiB;AAAR;AAAQ;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAnC;AAAAA,aAAO;AAAA;AAAPA,aAAO;AAAA;AAAR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAMTE,wBAAe;;;AACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEG;AACC;AAAJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAI;AAAChB,aAAS;AAAV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADD;;;;;WAGTiB,iBAAQ;;;AACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAENC,uBAAc;;;AACR;AAAe;AACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADe;;;;;WAOrBC,sBAAa;;;AACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAONC,6BAAoB;;;AACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAWNC,wBAAe;;;AACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAENC,uBAAc;;;AAAd;AACM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAkB;AAAA;AAAA;AAAA;AAAA;;;;;WAExBC,+BAAsB;;;AAAtB;AACM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACM;AAAN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAM;AACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADM;AAAA;AAGE;AAAA;AAAA;AAAA;AAAA;;;;;WAEdC,mBAAU;;;AAAV;AACM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAME;AAAA;AAAA;AAAA;AAAA;;;;;WAGRC,YAAG;;;AACG;AAAC;AAAU;AAAG;AAAH;AAAyB;AAAA;AAAA;AAAnC;AAAU;AAAG;AAAH;AAAyB;AAAA;AAAA;AAAA;;;;;WAE1CC,WAAE;;;AACI;AAAC;AAAU;AAAG;AAAH;AAAyB;AAAA;AAAnC;AAAU;AAAG;AAAH;AAAyB;;;;;WAE1CC,UAAC;;;AACK;AAAC;AAAU;AAAA;AAAV;AAAU;;;;;WAEjBC,gBAAO;;;AACD;AAAkC;AAAjB;AAAf;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAe;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAiB;AAAA;AAAjB;AAAf;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAe;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAiB;AAAlC;;;;;WAENC,YAAG;;;AACI;AAAD;AAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAW;AAAP;AAAQ;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAuB;AACnC;AAAD;AAAE;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADmC;;;;;WAG1CC,YAAG;;;AACG;AAAA;AAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAINC,uBAAc;;;AACP;AAAD;AAAEC,SAAQ;AAAT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASF;AAC4B;AAA3B;AAAA;AAAG;AAAD;AAAE;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA2B;AAAA;AAAA;AAAA;AAAA;AAD5B;;;;;WAULC,0BAAiB;;;AAAjB;AACmB;AAAb;AAA2C;AAA7B;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA0B;AAAtBC,aAAS;AAAa;AAACC,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAU5C;AAAA;AAAA;AAAA;AAAA;;;;;WAELC,8BAAqB;;;AAArB;AACuB;AAAjB;AAA+C;AAA7B;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA0B;AAAtBF,aAAS;AAAa;AAACC,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUhD;AAAA;AAAA;AAAA;AAAA;;;;;WAELE,qBAAY;;;AAAZ;AACc;AAAR;AAAsC;AAA7B;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA0B;AAAtBH,aAAS;AAAa;AAACC,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUvC;AAAA;AAAA;AAAA;AAAA;;;;;WAELG,uBAAc;;;AAAd;AACgB;AAAV;AAAwC;AAA7B;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA0B;AAAtBJ,aAAS;AAAa;AAACC,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUzC;AAAA;AAAA;AAAA;AAAA;;;;;WAELI,sBAAa;;;AAAb;AACe;AAAT;AAA0C;AAAhC;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA6B;AAAzBC,aAAY;AAAa;AAACL,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAS3C;AAAA;AAAA;AAAA;AAAA;;;;;WAELM,0BAAiB;;;AAAjB;AACmB;AAAb;AAAa;AAAA;AAAA;AAAA;AAAA;AAOd;AAAA;AAAA;AAAA;AAAA;;;;;WAELC,uBAAc;;;AAAd;AACgB;AAAV;AAAoD;AAAzC;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAsC;AAAlCC,aAAM;AAA4B;AAACR,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASrD;AAAA;AAAA;AAAA;AAAA;;;;;WAELS,4BAAmB;;;AAAnB;AACqB;AAAf;AAA6C;AAA7B;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA0B;AAAtBV,aAAS;AAAa;AAACC,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAS9C;AAAA;AAAA;AAAA;AAAA;;;;;WAELU,6BAAoB;;;AAApB;AACsB;AAAhB;AAA8C;AAA7B;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA0B;AAAtBX,aAAS;AAAa;AAACC,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAS/C;AAAA;AAAA;AAAA;AAAA;;;;;WAELW,wBAAe;;;AAAf;AACiB;AAAX;AAAkB;AAAN;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAACC,aAAQ;AAAT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYnB;AAAA;AAAA;AAAA;AAAA;;;;;WAELC,mBAAU;;;AAAV;AACiB;AAAXpG,SAAK;AAAM;AAACC,WAAK;AAAY;AAAX;AAAW;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAQ;AAAA;AAAR;AAAX;AAAW;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAQ;AAA1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAOZ;AAAA;AAAA;AAAA;AAAA;;;;;WAELoG,oBAAW;;;AACL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAENC,eAAM;;;AAAN;AACS;AAAH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAqG;AAAjGC,WAAK;AAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAoE;AAAhE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAgE;AAA5DC,eAAK;AAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAmC;AAA/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA+B;AAA1BC,mBAAK;AAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAACC,sBAAK;AAAN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOtG;AACuB;AAAtBD,WAAK;AAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAACC,aAAK;AAAN;AAAA;AAAA;AAAA;AAAA;AAAA;AAD3B;AAQA;AAAA;AAAA;AAAA;AAAA;;;;;WAELC,gCAAuB;;;AACyB;AAA1CvD,SAAS;AAAA;AAAgC;AAAA;AAAhC;AAAgC;AAAC;AAAA;AAAA;;;;;WAEhDwD,yCAAgC;;;AACG;AAA7B;AAAA;AAAE;AAAoB;AAAH;AAAG;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAH;AAAA;AAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAEnCC,4BAAmB;;;AACyB;AAAtCzD,SAAS;AAAA;AAA4B;AAAA;AAA5B;AAA4B;AAAC;AAAA;AAAA;;;;;WAE5C0D,qCAA4B;;;AACO;AAA7B;AAAA;AAAE;AAAoB;AAAH;AAAG;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAH;AAAA;AAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAEnCC,0BAAiB;;;AACX;AAAoB;AAAnB;AAAmB;AAAA;AAAnB;AAAmB;AAApB;;;;;WAENC,4BAAmB;;;AACgC;AAA7C;AAAA;AAAE;AAAoC;AAApB;AAAoB;AAAH;AAAG;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAH;AAAA;AAAA;AAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAEnDC,iCAAwB;;;AAClB;AAA2B;AAA1B;AAA0B;AAAA;AAA1B;AAA0B;AAA3B;;;;;WAENC,mCAA0B;;;AACiC;AAArD;AAAA;AAAE;AAAoC;AAApB;AAAoB;AAAH;AAAG;AAAG;AAAH;AAAA;AAAA;AAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAE3DC,qBAAY;;;AAAZ;AACc;AAAR;AAAQ;AAAA;AAAA;AAAA;AAAA;AAOT;AAAA;AAAA;AAAA;AAAA;;;;;WAELC,qCAA4B;;;AAA5B;AAC8B;AAAxB;AAA2D;AAAlC;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA+B;AAA3BC,aAAc;AAAa;AAAC9B,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAS5D;AAAA;AAAA;AAAA;AAAA;;;;;WAEL+B,2BAAkB;;;AAAlB;AACoB;AAAd;AAAc;AAAA;AAAA;AAAA;AAAA;AAOf;AAAA;AAAA;AAAA;AAAA;;;;;WAELC,uBAAc;;;AAAd;AACgB;AAAV;AAAiB;AAAN;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAACC,aAAU;AAAX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOlB;AAAA;AAAA;AAAA;AAAA;;;;;WAELC,2BAAkB;;;AAAlB;AACoB;AAAd;AAAqB;AAAN;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAACD,aAAU;AAAX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOtB;AAAA;AAAA;AAAA;AAAA;;;;;WAELE,wBAAe;;;AAAf;AAEiB;AAAX;AAA0C;AAA9B;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA2B;AAAvBC,aAAG;AAAoB;AAACpC,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAc3C;AAEY;AAAX;AAAkC;AAAtB;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAmB;AAAfoC,eAAG;AAAY;AAACpC,iBAAK;AAAL;AAAAA,mBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAFnC;AAcA;AAAA;AAAA;AAAA;AAAA;;;;;WAELqC,6BAAoB;;;AAApB;AACgB;AAAV9G,SAAI;AAAuC;AAAhC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAgC;AAA5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA4B;AAAxBC,eAAM;AAAkB;AAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAa5C;AAAA;AAAA;AAAA;AAAA;;;;;WAEL8G,2BAAkB;;;AAAlB;AACM;AAAqB;AAApB;AAAoB;AAAA;AAAA;AAApB;AAAoB;AAAA;AAAA;AAAA;AAArB;AAAA;AAAA;AAAA;AAAA;AAAsB;AAAA;AAAA;AAAA;AAAA;;;;;WAE5BC,6BAAoB;;;AACqC;AAAnD;AAAA;AAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA0C;AAApB;AAAoB;AAAH;AAAG;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAH;AAAA;AAAA;AAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAEzDC,2BAAkB;;;AAAlB;AACoB;AAAd;AAAqB;AAAN;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAACC,aAAU;AAAX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOtB;AAAA;AAAA;AAAA;AAAA;;;;;WAELC,0BAAiB;;;AAAjB;AACmB;AAAb;AAAa;AAAA;AAAA;AAAA;AAAA;AAOd;AAAA;AAAA;AAAA;AAAA;;;;;WAELC,0BAAiB;;;AAAjB;AACmB;AAAb;AAAuC;AAAzB;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAsB;AAAlBC,aAAK;AAAa;AAAC5C,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASxC;AAAA;AAAA;AAAA;AAAA;;;;;WAEL6C,2BAAkB;;;AAAlB;AACoB;AAAd;AAAwC;AAAzB;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAsB;AAAlBD,aAAK;AAAa;AAAC5C,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASzC;AAAA;AAAA;AAAA;AAAA;;;;;WAEL8C,0BAAiB;;;AAAjB;AACmB;AAAb;AAAoB;AAAN;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAACC,aAAQ;AAAT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASrB;AAAA;AAAA;AAAA;AAAA;;;;;WAELC,4BAAmB;;;AAAnB;AACqB;AAAf;AAAsB;AAAN;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAACD,aAAQ;AAAT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOvB;AAAA;AAAA;AAAA;AAAA;;;;;WAELE,8BAAqB;;;AAArB;AACuB;AAAjB;AAA8C;AAA5B;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAyB;AAArBF,aAAQ;AAAa;AAAC/C,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAS/C;AAAA;AAAA;AAAA;AAAA;;;;;WAELkD,+BAAsB;;;AAAtB;AACwB;AAAlB;AAAyB;AAAN;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAAClD,aAAK;AAAL;AAAAA,eAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAS1B;AAAA;AAAA;AAAA;AAAA;;;;;WAELmD,4BAAmB;;;AAAnB;AACqB;AAAf;AAAyC;AAAzB;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAsB;AAAlBP,aAAK;AAAa;AAAC5C,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAU1C;AAAA;AAAA;AAAA;AAAA;;;;;WAELoD,kCAAyB;;;AAAzB;AAC2B;AAArB;AAA6D;AAAvC;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAoC;AAAhC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAgC;AAA5BC,eAAK;AAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAACrD,iBAAK;AAAL;AAAAA,mBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAa9D;AAAA;AAAA;AAAA;AAAA;;;;;WAELsD,8BAAqB;;;AAArB;AACuB;AAAjB;AAA2C;AAAzB;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAsB;AAAlBD,aAAK;AAAa;AAACrD,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAS5C;AAAA;AAAA;AAAA;AAAA;;;;;WAELuD,8BAAqB;;;AAArB;AACuB;AAAjB;AAAoC;AAAlB;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAe;AAAXC,aAAK;AAAM;AAACxD,eAAK;AAAL;AAAAA,iBAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASrC;AAAA;AAAA;AAAA;AAAA;;;;;WAGLyD,qBAAY;;;AAAZ;AAC4B;AAAtBC,SAAQ;AAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAwC;AAApCX,WAAQ;AAA4B;AAAC/C,aAAK;AAAL;AAAAA,eAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUhE;AAC6C;AAA5C+C,WAAQ;AAAoC;AAAC/C,aAAK;AAAL;AAAAA,eAAY;AAAA;AAAb;AAAA;AAAA;AAAA;AAAA;AAAA;AAD7C;AAUA;AAAA;AAAA;AAAA;AAAA;;;;;WAEL2D,6CAAoC;;;AAC9B;AAA+B;AAA9B;AAA8B;AAAA;AAAA;AAA9B;AAA8B;AAAA;AAAA;AAAA;AAA/B;AAAA;AAAA;AAAA;AAAA;;;;;WAENC,uBAAc;;;AACR;AAAiB;AAAhB;AAAgB;AAAA;AAAhB;AAAgB;AAAjB;;;;;WAENC,yBAAgB;;;AAC8C;AAAxD;AAAA;AAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA0C;AAApB;AAAoB;AAAH;AAAG;AAAG;AAAH;AAAA;AAAA;AAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAC;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAM9DC,kBAAS;;;AAAT;AACM;AAiCc;AAhCd;AAgCc;AA/Bd;AA+Bc;AA9Bd;AA8Bc;AA7Bd;AA6Bc;AA5Bd;AA4Bc;AA3Bd;AA2Bc;AA1Bd;AA0Bc;AAzBd;AAyBc;AAxBd;AAwBc;AAvBd;AAuBc;AAtBd;AAsBc;AArBd;AAqBc;AApBd;AAoBc;AAnBd;AAmBc;AAlBd;AAkBc;AAjBd;AAiBc;AAhBd;AAgBc;AAfd;AAec;AAdd;AAcc;AAbd;AAac;AAZd;AAYc;AAXd;AAWc;AAVd;AAUc;AATd;AASc;AARd;AAQc;AAPd;AAOc;AANd;AAMc;AAHd;AAGc;AAAd;AAAc;AAEd;AAFc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AAAA;AAAA;AAAA;AAAA;;;;;WAIpBC,uBAAc;;;AAAiB;AAAdC,SAAI;AAAS;AAAH;AAAJ;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAI;AAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAAA;AAAA;AAAH;AAAJ;AAAA;AAAC;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAI;AAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAG;AAAA;AAAA;AAAA;AAAb;AAAAA,WAAI;AAAA;AAAJA,WAAI;AAAA;AAAU;AAAA;AAAA;AAAA;AAAA;;;;;WA2B/BC,WAAE;;;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAGLC,WAAE;;;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAGLC,WAAE;;;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAGLC,WAAE;;;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAGLC,WAAE;;;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAGLC,WAAE;;;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAGLC,WAAE;;;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAGLC,WAAE;;;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAGLC,WAAE;;;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAGLC,WAAE;;;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;WAGLC,WAAE;;;AAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;AAr4BJC;AACDA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA;AACAA","file":"parser.js"}
\ No newline at end of file
diff --git a/packages/parser/src/parser.pegjs b/packages/parser/src/parser.pegjs
new file mode 100644
index 000000000..5313f4459
--- /dev/null
+++ b/packages/parser/src/parser.pegjs
@@ -0,0 +1,979 @@
+{{
+ const commandType = {
+ say: 0, // 对话
+ changeBg: 1, // 更改背景
+ changeFigure: 2, // 更改立绘
+ bgm: 3, // 更改背景音乐
+ video: 4, // 播放视频
+ pixi: 5, // pixi演出
+ pixiInit: 6, // pixi初始化
+ intro: 7, // 黑屏文字演示
+ miniAvatar: 8, // 小头像
+ changeScene: 9, // 切换场景
+ choose: 10, // 分支选择
+ end: 11, // 结束游戏
+ setComplexAnimation: 12, // 动画演出
+ setFilter: 13, // 设置效果
+ label: 14, // 标签
+ jumpLabel: 15, // 跳转标签
+ chooseLabel: 16, // 选择标签
+ setVar: 17, // 设置变量
+ if: 18, // 条件跳转
+ callScene: 19, // 调用场景
+ showVars: 20,
+ unlockCg: 21,
+ unlockBgm: 22,
+ filmMode: 23,
+ setTextbox: 24,
+ setAnimation: 25,
+ playEffect: 26,
+ setTempAnimation: 27,
+ comment: 28,
+ setTransform: 29,
+ setTransition: 30,
+ getUserInput: 31
+ };
+
+
+ function buildList(head, tail, index) {
+ return [head].concat(extractList(tail, index));
+ }
+
+ function extractList(list, index) {
+ return list.map(function(element) { return element[index]; });
+ }
+
+ function optionalList(value) {
+ return value !== null ? value : [];
+ }
+
+ function optionalString(value) {
+ return value != null ? value : "";
+ }
+
+ function filterNulls(value) {
+ return value.filter(function(element) { return element != null; });
+ }
+
+ function processVocalFileName(args) {
+ return args.map((arg) => {
+ if (arg.key.toLowerCase().match(/.ogg|.mp3|.wav/)) {
+ return {
+ key: "vocal",
+ value: arg.key
+ };
+ }
+ return arg;
+ });
+ }
+
+ function processNone(content) {
+ if (content === "" || content.toLowerCase() === "none") {
+ return "";
+ }
+ return content;
+ }
+}}
+
+{
+ // ----- Error Handling -----
+ let errors = [];
+ function report(message, loc = location()) {
+ errors.push({
+ location: `${loc.start.offset}(${loc.start.line}:${loc.start.column})..${loc.end.offset}(${loc.end.line}:${loc.end.column})`,
+ message,
+ });
+ }
+}
+
+
+// ----- Entrypoint -----
+
+Start
+ = program:Program EOF { return { sentenceList: program, errors }; }
+ / program:Program REPORT { return { sentenceList: program, errors }; }
+
+REPORT
+ = .+ { report("parsing cannot preceed"); }
+
+Program
+ = body:SourceElements? {
+ return optionalList(body);
+ }
+
+SourceElements
+ = head:SourceElement CommentSegment? tail:(SourceElementTail)* {
+ return buildList(head, tail, 1);
+ }
+
+SourceElementTail
+ = EOS SourceElement CommentSegment?
+ / SKIPToNextStatement SourceElement CommentSegment?
+
+SKIPToNextStatement
+ = REPORTNonCompletedParsing LineTerminator
+
+REPORTNonCompletedParsing
+ = NonLineTerminator+ {
+ report("non-completed parsing");
+ }
+
+NonLineTerminator
+ = !(LineTerminatorSequence) SourceCharacter
+
+CommentSymbol
+ = ";"
+
+CommentSegment
+ = _ CommentSymbol @$Comment
+
+SourceElement "source element"
+ = Statement
+ // / FunctionDeclaration
+
+// ----- Arguments -----
+
+ArgList "arguments"
+ = head:Arg tail:(&__ Arg)* {
+ return buildList(head, tail, 1);
+ }
+
+Arg "argument"
+ = &__ ArgStart @ArgBody
+
+ArgStart
+ = " -"
+
+ArgBody
+ = @ArgWithValue / @ArgWithoutValue
+
+ArgWithValue "argument with value"
+ = key:ArgKey "=" value:StringLiteralAllowWhiteSpace {
+ value = value.trim()
+
+ if (value === "none") {
+ value = "";
+ } else if (value === "true" || value === "false") {
+ value = (value === "true");
+ } else {
+ const number = Number(value);
+ if (!isNaN(number)) {
+ value = number;
+ }
+ }
+
+ return { key, value };
+ }
+
+ArgWithoutValue "argument without value"
+ = key:ArgKey {
+ return { key, value: true };
+ }
+
+// ----- Reserved Words & Tokens -----
+
+ReservedWord "reserve word"
+ = Keyword
+ // / ArgKeyword
+
+Keyword
+ = ChangeBgToken
+
+
+ChangeBgToken "'changeBg'" = @"changeBg" !IdentifierPart
+ChangeFigureToken "'changeFigure'" = @"changeFigure" !IdentifierPart
+BgmToken "'bgm'" = @"bgm" !IdentifierPart
+VideoToken "'playVideo'" = @"playVideo" !IdentifierPart // NOT SAME
+PixiToken "'pixiPerform'" = @"pixiPerform" !IdentifierPart // NOT SAME
+PixiInitToken "'pixiInit'" = @"pixiInit" !IdentifierPart
+IntroToken "'intro'" = @"intro" !IdentifierPart
+MiniAvatarToken "'miniAvatar'" = @"miniAvatar" !IdentifierPart
+ChangeSceneToken "'changeScene'" = @"changeScene" !IdentifierPart
+ChooseToken "'choose'" = @"choose" !IdentifierPart
+EndToken "'end'" = @"end" !IdentifierPart
+SetComplexAnimationToken "'setComplexAnimation'" = @"setComplexAnimation" !IdentifierPart
+SetFilterToken "'setFilter'" = @"setFilter" !IdentifierPart
+LabelToken "'label'" = @"label" !IdentifierPart
+JumpLabelToken "'jumpLabel'" = @"jumpLabel" !IdentifierPart
+ChooseLabelToken "'chooseLabel'" = @"chooseLabel" !IdentifierPart
+SetVarToken "'setVar'" = @"setVar" !IdentifierPart
+IfToken "'if'" = @"if" !IdentifierPart
+CallSceneToken "'callScene'" = @"callScene" !IdentifierPart
+ShowVarsToken "'showVars'" = @"showVars" !IdentifierPart
+UnlockCgToken "'unlockCg'" = @"unlockCg" !IdentifierPart
+UnlockBgmToken "'unlockBgm'" = @"unlockBgm" !IdentifierPart
+FilmModeToken "'filmMode'" = @"filmMode" !IdentifierPart
+SetTextboxToken "'setTextbox'" = @"setTextbox" !IdentifierPart
+SetAnimationToken "'setAnimation'" = @"setAnimation" !IdentifierPart
+PlayEffectToken "'playEffect'" = @"playEffect" !IdentifierPart
+SetTempAnimationToken "'setTempAnimation'" = @"setTempAnimation" !IdentifierPart
+CommentToken "'comment'" = @"comment" !IdentifierPart // NO HANDLER
+SetTransformToken "'setTransform'" = @"setTransform" !IdentifierPart
+SetTransitionToken "'setTransition'" = @"setTransition" !IdentifierPart
+GetUserInputToken "'getUserInput'" = @"getUserInput" !IdentifierPart
+
+
+/*
+Currently, we do not need to check all argument keys. But the key should be
+more strict than the value, e.g., do not allow symbols and special characters,
+and should not be empty.
+*/
+ArgKey
+ = $KeyCharacter+
+
+KeyCharacter
+ = !(CommentSymbol / "=" / BraceCharacterSequence / WhiteSpace / LineTerminator / EOS / ArgStart) SourceCharacter { return text(); }
+
+// ArgKeyword
+// = NextToken
+// / LeftToken
+// / TransformToken
+
+// NextToken "'next'" = @"next" !IdentifierPart
+// LeftToken "'left'" = @"left" !IdentifierPart
+// TransformToken "'transform'" = @"transform" !IdentifierPart
+
+// ----- String -----
+
+StringLiteral "string"
+ = "'" sequence:SingleStringCharacter* "'" { return sequence.join(""); }
+ / '"' sequence:DoubleStringCharacter* '"' { return sequence.join(""); }
+ / sequence:StringCharacter* { return sequence.join("") }
+
+StringLiteralAllowWhiteSpace "string allow white space"
+ = "'" sequence:SingleStringCharacter* "'" { return sequence.join(""); }
+ / '"' sequence:DoubleStringCharacter* '"' { return sequence.join(""); }
+ / sequence:StringCharacterAllowWhiteSpace* { return sequence.join(""); }
+
+SingleStringCharacter
+ = !("'" / "\\" / LineTerminator) SourceCharacter { return text(); }
+ / "\\" sequence:EscapeSequence { return sequence; }
+
+DoubleStringCharacter
+ = !('"' / "\\" / LineTerminator) SourceCharacter { return text(); }
+ / "\\" sequence:EscapeSequence { return sequence; }
+
+StringCharacter
+ = !(CommentSymbol / LineTerminator / EOS / WhiteSpace / ArgStart) SourceCharacter { return text(); }
+
+StringCharacterAllowWhiteSpace
+ = !(CommentSymbol / LineTerminator / EOS / ArgStart) SourceCharacter { return text(); }
+
+// ----- String: Escape -----
+
+EscapeSequence
+ = CharacterEscapeSequence
+ / "0" !DecimalDigit { return "\0"; }
+ / HexEscapeSequence
+ / UnicodeEscapeSequence
+
+CharacterEscapeSequence
+ = SingleEscapeCharacter
+ / NonEscapeCharacter
+
+SingleEscapeCharacter
+ = "'"
+ / '"'
+ / "\\"
+ / "b" { return "\b"; }
+ / "f" { return "\f"; }
+ / "n" { return "\n"; }
+ / "r" { return "\r"; }
+ / "t" { return "\t"; }
+ / "v" { return "\v"; }
+
+BraceCharacterSequence
+ = "{"
+ / "}"
+ / "("
+ / ")"
+
+NonEscapeCharacter
+ = !(EscapeCharacter / LineTerminator) SourceCharacter { return text(); }
+
+EscapeCharacter
+ = SingleEscapeCharacter
+ / DecimalDigit
+ / "x"
+ / "u"
+
+DecimalDigit
+ = [0-9]
+
+HexEscapeSequence
+ = "x" digits:$(HexDigit HexDigit) {
+ return String.fromCharCode(parseInt(digits, 16));
+ }
+
+UnicodeEscapeSequence
+ = "u" digits:$(HexDigit HexDigit HexDigit HexDigit) {
+ return String.fromCharCode(parseInt(digits, 16));
+ }
+
+// ----- Identifier -----
+
+IdentifierStart
+ = UnicodeLetter
+ / "$"
+ / "_"
+ / "\\" sequence:UnicodeEscapeSequence { return sequence; }
+
+HexDigit
+ = [0-9a-f]i
+
+IdentifierPart
+ = IdentifierStart
+ / UnicodeCombiningMark
+ / UnicodeDigit
+ / UnicodeConnectorPunctuation
+ / "\u200C"
+ / "\u200D"
+
+UnicodeLetter
+ = Lu
+ / Ll
+ / Lt
+ / Lm
+ / Lo
+ / Nl
+
+UnicodeCombiningMark
+ = Mn
+ / Mc
+
+UnicodeDigit
+ = Nd
+
+UnicodeConnectorPunctuation
+ = Pc
+
+// ----- Character, Space & Line Terminators -----
+
+SourceCharacter
+ = .
+
+LineTerminator "line terminator"
+ = [\n\r\u2028\u2029]
+
+LineTerminatorSequence "end of line"
+ = "\n"
+ / "\r\n"
+ / "\r"
+ / "\u2028"
+ / "\u2029"
+
+WhiteSpace "whitespace"
+ = "\t"
+ / "\v"
+ / "\f"
+ / " "
+ / "\u00A0"
+ / "\uFEFF"
+ / Zs
+
+// Mandatory white space
+_M_
+ = (WhiteSpace / LineTerminatorSequence)+
+
+__
+ = (WhiteSpace / LineTerminatorSequence)*
+
+_
+ = (WhiteSpace)*
+
+Comment
+ = $(!LineTerminator SourceCharacter)*
+
+EOS
+ = _ ";" Comment LineTerminatorSequence
+ / _ LineTerminatorSequence
+
+EOF
+ = !.
+
+// ----- Statements -----
+
+EmptyStatement
+ = _ comment:CommentSegment {
+ comment = optionalString(comment);
+
+ return {
+ command: commandType.comment,
+ commandRaw: "comment",
+ content: comment,
+ args: [],
+ }
+ }
+ / &(_ LineTerminatorSequence) {
+ return {
+ command: commandType.comment,
+ commandRaw: "comment",
+ content: "",
+ args: [],
+ }
+ }
+
+ChangeBgStatement "changeBg statement"
+ = ChangeBgToken __ ":" fileName:StringLiteral args:ArgList? {
+ args = optionalList(args);
+ fileName = processNone(fileName.trim());
+
+ return {
+ command: commandType.changeBg,
+ commandRaw: "changeBg",
+ content: fileName,
+ args,
+ };
+ }
+
+ChangeFigureStatement "changeFigure statement"
+ = ChangeFigureToken __ ":" fileName:StringLiteral args:ArgList? {
+ args = optionalList(args);
+ fileName = processNone(fileName.trim());
+
+ return {
+ command: commandType.changeFigure,
+ commandRaw: "changeFigure",
+ content: fileName,
+ args,
+ };
+ }
+
+BgmStatement "bgm statement"
+ = BgmToken __ ":" fileName:StringLiteral args:ArgList? {
+ args = optionalList(args);
+ fileName = processNone(fileName.trim());
+
+ return {
+ command: commandType.bgm,
+ commandRaw: "bgm",
+ content: fileName,
+ args,
+ };
+ }
+
+VideoStatement "video statement"
+ = VideoToken __ ":" fileName:StringLiteral args:ArgList? {
+ args = optionalList(args);
+ fileName = processNone(fileName.trim());
+
+ return {
+ command: commandType.video,
+ commandRaw: "playVideo",
+ content: fileName,
+ args,
+ };
+ }
+
+PixiStatement "pixi statement"
+ = PixiToken __ ":" performName:StringLiteral args:ArgList? {
+ args = optionalList(args);
+
+ return {
+ command: commandType.pixi,
+ commandRaw: "pixiPerform",
+ content: performName,
+ args,
+ };
+ }
+
+PixiInitStatement "pixiInit statement"
+ = PixiInitToken {
+ return {
+ command: commandType.pixiInit,
+ commandRaw: "pixiInit",
+ content: "",
+ args: [],
+ };
+ }
+
+IntroStatement "intro statement"
+ = IntroToken __ ":" lines:StringLiteralAllowWhiteSpace args:ArgList? {
+ args = optionalList(args);
+
+ return {
+ command: commandType.intro,
+ commandRaw: "intro",
+ content: processNone(lines.trim()),
+ args,
+ };
+ }
+
+MiniAvatarStatement "miniAvatar statement"
+ = MiniAvatarToken __ ":" fileName:StringLiteral args:ArgList? {
+ args = optionalList(args);
+
+ return {
+ command: commandType.miniAvatar,
+ commandRaw: "miniAvatar",
+ content: processNone(fileName.trim()),
+ args,
+ };
+ }
+
+ChangeSceneStatement "changeScene statement"
+ = ChangeSceneToken __ ":" fileName:StringLiteral args:ArgList? {
+ args = optionalList(args);
+
+ return {
+ command: commandType.changeScene,
+ commandRaw: "changeScene",
+ content: processNone(fileName.trim()),
+ args,
+ };
+ }
+
+ChooseStatement "choose statement"
+ = ChooseToken __ ":" choices:ChoiceList {
+ choices.choiceList = optionalList(choices.choiceList);
+
+ return {
+ command: commandType.choose,
+ commandRaw: "choose",
+ content: "",
+ args: [
+ { key: "choices", value: choices.choiceList },
+ { key: "contentRawRange", value: choices.contentRawRange }
+ ],
+ };
+ }
+
+ChoiceList "choices"
+ = head:Choice tail:(VerticalBar Choice)* {
+ const loc = location();
+
+ return {
+ contentRawRange: [loc.start.offset, loc.end.offset],
+ choiceList: buildList(head, tail, 1)
+ };
+ }
+
+VerticalBar
+ = "|"
+
+Choice "choice"
+ = "(" sexp:NoParenthesisExpression ")" "[" cexp:NoBracketExpression "]" "->" text:ChoiceTextLiteral ":" dest:ChoiceDestinationLiteral {
+ return {
+ showExpression: sexp,
+ clickExpression: cexp,
+ text,
+ destination: dest,
+ }
+ }
+ / text:ChoiceTextLiteral ":" dest:ChoiceDestinationLiteral {
+ return {
+ showExpression: "",
+ clickExpression: "",
+ text,
+ destination: dest,
+ }
+ }
+
+NoParenthesisExpression
+ = sequence:NoParenthesisExpressionCharacter* { return sequence.join(""); }
+
+NoParenthesisExpressionCharacter
+ = !(LineTerminator / EOS / ")") SourceCharacter { return text(); }
+
+NoBracketExpression
+ = sequence:NoBracketExpressionCharacter* { return sequence.join(""); }
+
+NoBracketExpressionCharacter
+ = !(LineTerminator / EOS / "]") SourceCharacter { return text(); }
+
+ChoiceTextLiteral
+ = $ChoiceTextCharacter*
+
+ChoiceTextCharacter
+ = !(CommentSymbol / LineTerminator / EOS / ":") SourceCharacter { return text(); }
+
+ChoiceDestinationLiteral
+ = $ChoiceDestinationCharacter*
+
+ChoiceDestinationCharacter
+ = !(CommentSymbol / LineTerminator / EOS / VerticalBar) SourceCharacter { return text(); }
+
+EndStatement "end statement"
+ = EndToken {
+ return {
+ command: commandType.end,
+ commandRaw: "end",
+ content: "",
+ args: [],
+ };
+ }
+
+SetComplexAnimationStatement "setComplexAnimation statement"
+ = SetComplexAnimationToken __ ":" animationName:StringLiteral args:ArgList? {
+ args = optionalList(args);
+
+ return {
+ command: commandType.setComplexAnimation,
+ commandRaw: "setComplexAnimation",
+ content: animationName,
+ args,
+ };
+ }
+
+SetFilterStatement "setFilter statement"
+ = SetFilterToken {
+ return {
+ command: commandType.setFilter,
+ commandRaw: "setFilter",
+ content: "",
+ args: [],
+ };
+ }
+
+LabelStatement "label statement"
+ = LabelToken __ ":" labelName:StringLiteral {
+ return {
+ command: commandType.label,
+ commandRaw: "label",
+ content: labelName,
+ args: [],
+ };
+ }
+
+JumpLabelStatement "jumpLabel statement"
+ = JumpLabelToken __ ":" labelName:StringLiteral {
+ return {
+ command: commandType.jumpLabel,
+ commandRaw: "jumpLabel",
+ content: labelName,
+ args: [],
+ };
+ }
+
+SetVarStatement "setVar statement"
+ /* Internal variables */
+ = SetVarToken __ ":" kv:InternalArgWithValue args:ArgList? {
+ // we prefix variableName and expression with `#` to separate it from
+ // user-defined arguments
+ args = optionalList(args);
+
+ return {
+ command: commandType.setVar,
+ commandRaw: "setVar",
+ content: "",
+ args: [
+ { key: "#variableName", value: kv.key },
+ { key: "#internalExpression", value: kv.value },
+ ].concat(args),
+ };
+ }
+ /* Other variables */
+ / SetVarToken __ ":" kv:ArgWithValue args:ArgList? {
+ args = optionalList(args);
+
+ return {
+ command: commandType.setVar,
+ commandRaw: "setVar",
+ content: "",
+ args: [
+ { key: "#variableName", value: kv.key },
+ { key: "#expression", value: kv.value },
+ ].concat(args),
+ };
+ }
+
+InternalArgWithValue "argument with value"
+ = key:ArgKey "=" "(" value:InternalArgLiteral ")" {
+ if (value === "none") {
+ value = "";
+ } else if (value === "true" || value === "false") {
+ value = (value === "true");
+ } else {
+ const number = Number(value);
+ if (!isNaN(number)) {
+ value = number;
+ }
+ }
+
+ return { key, value };
+ }
+
+InternalArgLiteral "internal argument"
+ = $InternalArgCharacter+
+
+InternalArgCharacter
+ = !(CommentSymbol / ")" / LineTerminator / EOS / "=") SourceCharacter { return text(); }
+
+CallSceneStatement "callScene statement"
+ = CallSceneToken __ ":" sceneName:StringLiteral {
+ return {
+ command: commandType.callScene,
+ commandRaw: "callScene",
+ content: sceneName,
+ args: [],
+ };
+ }
+
+ShowVarsStatement "showVars statement"
+ = ShowVarsToken {
+ return {
+ command: commandType.showVars,
+ commandRaw: "showVars",
+ content: "",
+ args: [],
+ };
+ }
+
+UnlockCgStatement "unlockCg statement"
+ = UnlockCgToken __ ":" name:StringLiteral args:ArgList? {
+ args = optionalList(args);
+
+ return {
+ command: commandType.unlockCg,
+ commandRaw: "unlockCg",
+ content: name,
+ args,
+ };
+ }
+
+UnlockBgmStatement "unlockBgm statement"
+ = UnlockBgmToken __ ":" name:StringLiteral args:ArgList? {
+ args = optionalList(args);
+
+ return {
+ command: commandType.unlockBgm,
+ commandRaw: "unlockBgm",
+ content: name,
+ args,
+ };
+ }
+
+FilmModeStatement "filmMode statement"
+ = FilmModeToken __ ":" content:StringLiteralAllowWhiteSpace {
+ content = processNone(content.trim());
+
+ return {
+ command: commandType.filmMode,
+ commandRaw: "filmMode",
+ content,
+ args: [],
+ };
+ }
+
+SetTextboxStatement "setTextbox statement"
+ = SetTextboxToken __ ":" content:StringLiteral {
+ return {
+ command: commandType.setTextbox,
+ commandRaw: "setTextbox",
+ content,
+ args: [],
+ };
+ }
+
+SetAnimationStatement "setAnimation statement"
+ = SetAnimationToken __ ":" content:StringLiteral args:ArgList? {
+ args = optionalList(args);
+
+ return {
+ command: commandType.setAnimation,
+ commandRaw: "setAnimation",
+ content,
+ args,
+ };
+ }
+
+SetTransitionStatement "setTransition statement"
+ = SetTransitionToken __ ":" args:ArgList? {
+ args = optionalList(args);
+
+ return {
+ command: commandType.setTransition,
+ commandRaw: "setTransition",
+ content: "",
+ args,
+ };
+ }
+
+PlayEffectStatement "playEffect statement"
+ = PlayEffectToken __ ":" name:StringLiteral args:ArgList? {
+ name = processNone(name.trim());
+ args = optionalList(args);
+
+ return {
+ command: commandType.playEffect,
+ commandRaw: "playEffect",
+ content: name,
+ args,
+ };
+ }
+
+SetTempAnimationStatement "setTempAnimation statement"
+ = SetTempAnimationToken __ ":" "[" json:NoBracketExpression "]" args:ArgList? {
+ // Here, we do a trick by directly check whether a bracket is in the
+ // expression since we don't want to add extra cost on parsing JSON.
+ // The direct check holds because the content will never encounter a
+ // close bracket.
+ args = optionalList(args);
+
+ return {
+ command: commandType.setTempAnimation,
+ commandRaw: "setTempAnimation",
+ content: `[${json}]`,
+ args,
+ };
+ }
+
+SetTransformStatement "setTransform statement"
+ = SetTransformToken __ ":" json:StringLiteral args:ArgList? {
+ args = optionalList(args);
+
+ return {
+ command: commandType.setTransform,
+ commandRaw: "setTransform",
+ content: json,
+ args,
+ };
+ }
+
+GetUserInputStatement "getUserInput statement"
+ = GetUserInputToken __ ":" into:ArgKey args:ArgList? {
+ args = optionalList(args);
+
+ return {
+ command: commandType.getUserInput,
+ commandRaw: "getUserInput",
+ content: into,
+ args,
+ };
+ }
+
+
+SayStatement "say statement"
+ = speaker:SpeakerLiteral ":" content:StringLiteralAllowWhiteSpace args:ArgList? {
+ args = optionalList(args);
+ args = processVocalFileName(args);
+
+ return {
+ command: commandType.say,
+ commandRaw: speaker,
+ content: content.trim(),
+ args: [{ key: "speaker", value: speaker }].concat(args),
+ }
+ }
+ / content:NonEmptyStringLiteralAllowWhiteSpace args:ArgList? {
+ args = optionalList(args);
+
+ return {
+ command: commandType.say,
+ commandRaw: "say",
+ content: content.trim(),
+ args,
+ }
+ }
+
+NonEmptyStringLiteralAllowWhiteSpace
+ = $StringCharacterAllowWhiteSpace+
+
+SpeakerLiteral
+ = $SpeakerCharacter*
+
+SpeakerCharacter
+ = !(CommentSymbol / ":" / LineTerminator / EOS / ArgStart) SourceCharacter { return text(); }
+
+
+/*****************************************************************************/
+/****************** ! REMEMBER TO ADD NEW STATEMENTS HERE ! ******************/
+/*****************************************************************************/
+Statement "statement"
+ = ChangeBgStatement
+ / ChangeFigureStatement
+ / BgmStatement
+ / VideoStatement
+ / PixiStatement
+ / PixiInitStatement
+ / IntroStatement
+ / MiniAvatarStatement
+ / ChangeSceneStatement
+ / ChooseStatement
+ / EndStatement
+ / SetComplexAnimationStatement
+ / SetFilterStatement
+ / LabelStatement
+ / JumpLabelStatement
+ / SetVarStatement
+ / CallSceneStatement
+ / ShowVarsStatement
+ / UnlockCgStatement
+ / UnlockBgmStatement
+ / FilmModeStatement
+ / SetTextboxStatement
+ / SetAnimationStatement
+ / SetTransitionStatement
+ / PlayEffectStatement
+ / SetTempAnimationStatement
+ / SetTransformStatement
+ / GetUserInputStatement
+// if all commands failed, it should be a say statement
+// (either with or without ':')
+ / SayStatement
+// if still cannot match, it may be a comment without command or an empty line
+// (which is also a comment)
+ / EmptyStatement
+// if still cannot match, it should be an error
+ / ERRORStatement
+/*****************************************************************************/
+/****************** ! REMEMBER TO ADD NEW STATEMENTS HERE ! ******************/
+/*****************************************************************************/
+ERRORStatement = err:$(!EOS .)+ { report("unexpected statement `" + err + "`"); };
+
+
+// ----- Unicode Character Categories -----
+//
+// Extracted from the following Unicode Character Database file:
+//
+// http://www.unicode.org/Public/8.0.0/ucd/extracted/DerivedGeneralCategory.txt
+//
+// Unix magic used:
+//
+// grep "; $CATEGORY" DerivedGeneralCategory.txt | # Filter characters
+// cut -f1 -d " " | # Extract code points
+// grep -v '[0-9a-fA-F]\{5\}' | # Exclude non-BMP characters
+// sed -e 's/\.\./-/' | # Adjust formatting
+// sed -e 's/\([0-9a-fA-F]\{4\}\)/\\u\1/g' | # Adjust formatting
+// tr -d '\n' # Join lines
+//
+// ECMA-262 allows using Unicode 3.0 or later, version 8.0.0 was the latest one
+// at the time of writing.
+//
+// Non-BMP characters are completely ignored to avoid surrogate pair handling
+// (detecting surrogate pairs isn't possible with a simple character class and
+// other methods would degrade performance). I don't consider it a big deal as
+// even parsers in JavaScript engines of common browsers seem to ignore them.
+
+// Letter, Lowercase
+Ll = [\u0061-\u007A\u00B5\u00DF-\u00F6\u00F8-\u00FF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137-\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148-\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C-\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA-\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9-\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC-\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF-\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F-\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0-\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB-\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE-\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0561-\u0587\u13F8-\u13FD\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6-\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FC7\u1FD0-\u1FD3\u1FD6-\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6-\u1FF7\u210A\u210E-\u210F\u2113\u212F\u2134\u2139\u213C-\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5E\u2C61\u2C65-\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73-\u2C74\u2C76-\u2C7B\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3-\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7B5\uA7B7\uA7FA\uAB30-\uAB5A\uAB60-\uAB65\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A]
+
+// Letter, Modifier
+Lm = [\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0374\u037A\u0559\u0640\u06E5-\u06E6\u07F4-\u07F5\u07FA\u081A\u0824\u0828\u0971\u0E46\u0EC6\u10FC\u17D7\u1843\u1AA7\u1C78-\u1C7D\u1D2C-\u1D6A\u1D78\u1D9B-\u1DBF\u2071\u207F\u2090-\u209C\u2C7C-\u2C7D\u2D6F\u2E2F\u3005\u3031-\u3035\u303B\u309D-\u309E\u30FC-\u30FE\uA015\uA4F8-\uA4FD\uA60C\uA67F\uA69C-\uA69D\uA717-\uA71F\uA770\uA788\uA7F8-\uA7F9\uA9CF\uA9E6\uAA70\uAADD\uAAF3-\uAAF4\uAB5C-\uAB5F\uFF70\uFF9E-\uFF9F]
+
+// Letter, Other
+Lo = [\u00AA\u00BA\u01BB\u01C0-\u01C3\u0294\u05D0-\u05EA\u05F0-\u05F2\u0620-\u063F\u0641-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u0800-\u0815\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0972-\u0980\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E45\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u10D0-\u10FA\u10FD-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17DC\u1820-\u1842\u1844-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C77\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5-\u1CF6\u2135-\u2138\u2D30-\u2D67\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3006\u303C\u3041-\u3096\u309F\u30A1-\u30FA\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA014\uA016-\uA48C\uA4D0-\uA4F7\uA500-\uA60B\uA610-\uA61F\uA62A-\uA62B\uA66E\uA6A0-\uA6E5\uA78F\uA7F7\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9E0-\uA9E4\uA9E7-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA6F\uAA71-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADC\uAAE0-\uAAEA\uAAF2\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF66-\uFF6F\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]
+
+// Letter, Titlecase
+Lt = [\u01C5\u01C8\u01CB\u01F2\u1F88-\u1F8F\u1F98-\u1F9F\u1FA8-\u1FAF\u1FBC\u1FCC\u1FFC]
+
+// Letter, Uppercase
+Lu = [\u0041-\u005A\u00C0-\u00D6\u00D8-\u00DE\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178-\u0179\u017B\u017D\u0181-\u0182\u0184\u0186-\u0187\u0189-\u018B\u018E-\u0191\u0193-\u0194\u0196-\u0198\u019C-\u019D\u019F-\u01A0\u01A2\u01A4\u01A6-\u01A7\u01A9\u01AC\u01AE-\u01AF\u01B1-\u01B3\u01B5\u01B7-\u01B8\u01BC\u01C4\u01C7\u01CA\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F1\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A-\u023B\u023D-\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E-\u038F\u0391-\u03A1\u03A3-\u03AB\u03CF\u03D2-\u03D4\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F4\u03F7\u03F9-\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0-\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u10A0-\u10C5\u10C7\u10CD\u13A0-\u13F5\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1FB8-\u1FBB\u1FC8-\u1FCB\u1FD8-\u1FDB\u1FE8-\u1FEC\u1FF8-\u1FFB\u2102\u2107\u210B-\u210D\u2110-\u2112\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u2130-\u2133\u213E-\u213F\u2145\u2183\u2C00-\u2C2E\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D-\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AD\uA7B0-\uA7B4\uA7B6\uFF21-\uFF3A]
+
+// Mark, Spacing Combining
+Mc = [\u0903\u093B\u093E-\u0940\u0949-\u094C\u094E-\u094F\u0982-\u0983\u09BE-\u09C0\u09C7-\u09C8\u09CB-\u09CC\u09D7\u0A03\u0A3E-\u0A40\u0A83\u0ABE-\u0AC0\u0AC9\u0ACB-\u0ACC\u0B02-\u0B03\u0B3E\u0B40\u0B47-\u0B48\u0B4B-\u0B4C\u0B57\u0BBE-\u0BBF\u0BC1-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BD7\u0C01-\u0C03\u0C41-\u0C44\u0C82-\u0C83\u0CBE\u0CC0-\u0CC4\u0CC7-\u0CC8\u0CCA-\u0CCB\u0CD5-\u0CD6\u0D02-\u0D03\u0D3E-\u0D40\u0D46-\u0D48\u0D4A-\u0D4C\u0D57\u0D82-\u0D83\u0DCF-\u0DD1\u0DD8-\u0DDF\u0DF2-\u0DF3\u0F3E-\u0F3F\u0F7F\u102B-\u102C\u1031\u1038\u103B-\u103C\u1056-\u1057\u1062-\u1064\u1067-\u106D\u1083-\u1084\u1087-\u108C\u108F\u109A-\u109C\u17B6\u17BE-\u17C5\u17C7-\u17C8\u1923-\u1926\u1929-\u192B\u1930-\u1931\u1933-\u1938\u1A19-\u1A1A\u1A55\u1A57\u1A61\u1A63-\u1A64\u1A6D-\u1A72\u1B04\u1B35\u1B3B\u1B3D-\u1B41\u1B43-\u1B44\u1B82\u1BA1\u1BA6-\u1BA7\u1BAA\u1BE7\u1BEA-\u1BEC\u1BEE\u1BF2-\u1BF3\u1C24-\u1C2B\u1C34-\u1C35\u1CE1\u1CF2-\u1CF3\u302E-\u302F\uA823-\uA824\uA827\uA880-\uA881\uA8B4-\uA8C3\uA952-\uA953\uA983\uA9B4-\uA9B5\uA9BA-\uA9BB\uA9BD-\uA9C0\uAA2F-\uAA30\uAA33-\uAA34\uAA4D\uAA7B\uAA7D\uAAEB\uAAEE-\uAAEF\uAAF5\uABE3-\uABE4\uABE6-\uABE7\uABE9-\uABEA\uABEC]
+
+// Mark, Nonspacing
+Mn = [\u0300-\u036F\u0483-\u0487\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08E3-\u0902\u093A\u093C\u0941-\u0948\u094D\u0951-\u0957\u0962-\u0963\u0981\u09BC\u09C1-\u09C4\u09CD\u09E2-\u09E3\u0A01-\u0A02\u0A3C\u0A41-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A51\u0A70-\u0A71\u0A75\u0A81-\u0A82\u0ABC\u0AC1-\u0AC5\u0AC7-\u0AC8\u0ACD\u0AE2-\u0AE3\u0B01\u0B3C\u0B3F\u0B41-\u0B44\u0B4D\u0B56\u0B62-\u0B63\u0B82\u0BC0\u0BCD\u0C00\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56\u0C62-\u0C63\u0C81\u0CBC\u0CBF\u0CC6\u0CCC-\u0CCD\u0CE2-\u0CE3\u0D01\u0D41-\u0D44\u0D4D\u0D62-\u0D63\u0DCA\u0DD2-\u0DD4\u0DD6\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB-\u0EBC\u0EC8-\u0ECD\u0F18-\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86-\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039-\u103A\u103D-\u103E\u1058-\u1059\u105E-\u1060\u1071-\u1074\u1082\u1085-\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752-\u1753\u1772-\u1773\u17B4-\u17B5\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u18A9\u1920-\u1922\u1927-\u1928\u1932\u1939-\u193B\u1A17-\u1A18\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ABD\u1B00-\u1B03\u1B34\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80-\u1B81\u1BA2-\u1BA5\u1BA8-\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8-\u1BE9\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8-\u1CF9\u1DC0-\u1DF5\u1DFC-\u1DFF\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u3099-\u309A\uA66F\uA674-\uA67D\uA69E-\uA69F\uA6F0-\uA6F1\uA802\uA806\uA80B\uA825-\uA826\uA8C4\uA8E0-\uA8F1\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC\uA9E5\uAA29-\uAA2E\uAA31-\uAA32\uAA35-\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7-\uAAB8\uAABE-\uAABF\uAAC1\uAAEC-\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F]
+
+// Number, Decimal Digit
+Nd = [\u0030-\u0039\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0BE6-\u0BEF\u0C66-\u0C6F\u0CE6-\u0CEF\u0D66-\u0D6F\u0DE6-\u0DEF\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F29\u1040-\u1049\u1090-\u1099\u17E0-\u17E9\u1810-\u1819\u1946-\u194F\u19D0-\u19D9\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\uA620-\uA629\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uA9F0-\uA9F9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19]
+
+// Number, Letter
+Nl = [\u16EE-\u16F0\u2160-\u2182\u2185-\u2188\u3007\u3021-\u3029\u3038-\u303A\uA6E6-\uA6EF]
+
+// Punctuation, Connector
+Pc = [\u005F\u203F-\u2040\u2054\uFE33-\uFE34\uFE4D-\uFE4F\uFF3F]
+
+// Separator, Space
+Zs = [\u0020\u00A0\u1680\u2000-\u200A\u202F\u205F\u3000]
\ No newline at end of file
diff --git a/packages/parser/src/sceneParser.ts b/packages/parser/src/sceneParser.ts
new file mode 100644
index 000000000..9d2a72b83
--- /dev/null
+++ b/packages/parser/src/sceneParser.ts
@@ -0,0 +1,66 @@
+import {
+ commandType,
+ IAsset,
+ IScene,
+ ISentence,
+} from './interface/sceneInterface';
+import { scriptParser } from './scriptParser/scriptParser';
+import uniqWith from 'lodash/uniqWith';
+import { fileType } from './interface/assets';
+import { ConfigMap } from './config/scriptConfig';
+
+/**
+ * 场景解析器
+ * @param rawScene 原始场景
+ * @param sceneName 场景名称
+ * @param sceneUrl 场景url
+ * @param assetsPrefetcher
+ * @param assetSetter
+ * @param ADD_NEXT_ARG_LIST
+ * @param SCRIPT_CONFIG_MAP
+ * @return {IScene} 解析后的场景
+ */
+export const sceneParser = (
+ rawScene: string,
+ sceneName: string,
+ sceneUrl: string,
+ assetsPrefetcher: (assetList: Array) => void,
+ assetSetter: (fileName: string, assetType: fileType) => string,
+ ADD_NEXT_ARG_LIST: commandType[],
+ SCRIPT_CONFIG_MAP: ConfigMap,
+): IScene => {
+ const rawSentenceList = rawScene.split('\n'); // 原始句子列表
+
+ // 去分号留到后面去做了,现在注释要单独处理
+ const rawSentenceListWithoutEmpty = rawSentenceList;
+ // .map((sentence) => sentence.split(";")[0])
+ // .filter((sentence) => sentence.trim() !== "");
+ let assetsList: Array = []; // 场景资源列表
+ let subSceneList: Array = []; // 子场景列表
+ const sentenceList: Array = rawSentenceListWithoutEmpty.map(
+ (sentence) => {
+ const returnSentence: ISentence = scriptParser(
+ sentence,
+ assetSetter,
+ ADD_NEXT_ARG_LIST,
+ SCRIPT_CONFIG_MAP,
+ );
+ // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList
+ assetsList = [...assetsList, ...returnSentence.sentenceAssets];
+ subSceneList = [...subSceneList, ...returnSentence.subScene];
+ return returnSentence;
+ },
+ );
+
+ // 开始资源的预加载
+ assetsList = uniqWith(assetsList); // 去重
+ assetsPrefetcher(assetsList);
+
+ return {
+ sceneName: sceneName, // 场景名称
+ sceneUrl: sceneUrl,
+ sentenceList: sentenceList, // 语句列表
+ assetsList: assetsList, // 资源列表
+ subSceneList: subSceneList, // 子场景列表
+ };
+};
diff --git a/packages/parser/src/sceneTextPreProcessor.ts b/packages/parser/src/sceneTextPreProcessor.ts
new file mode 100644
index 000000000..244c2c91b
--- /dev/null
+++ b/packages/parser/src/sceneTextPreProcessor.ts
@@ -0,0 +1,223 @@
+/**
+ * Preprocessor for scene text.
+ *
+ * Use two-pass to generate a new scene text that concats multiline sequences
+ * into a single line and add placeholder lines to preserve the original number
+ * of lines.
+ *
+ * @param sceneText The original scene text
+ * @returns The processed scene text
+ */
+export function sceneTextPreProcess(sceneText: string): string {
+ let lines = sceneText.replaceAll('\r', '').split('\n');
+
+ lines = sceneTextPreProcessPassOne(lines);
+ lines = sceneTextPreProcessPassTwo(lines);
+
+ return lines.join('\n');
+}
+
+/**
+ * Pass one.
+ *
+ * Add escape character to all lines that should be multiline.
+ *
+ * @param lines The original lines
+ * @returns The processed lines
+ */
+function sceneTextPreProcessPassOne(lines: string[]): string[] {
+ const processedLines: string[] = [];
+ let lastLineIsMultiline = false;
+ let thisLineIsMultiline = false;
+
+ for (const line of lines) {
+ thisLineIsMultiline = false;
+
+ if (canBeMultiline(line)) {
+ thisLineIsMultiline = true;
+ }
+
+ if (shouldNotBeMultiline(line, lastLineIsMultiline)) {
+ thisLineIsMultiline = false;
+ }
+
+ if (thisLineIsMultiline) {
+ processedLines[processedLines.length - 1] += '\\';
+ }
+
+ processedLines.push(line);
+
+ lastLineIsMultiline = thisLineIsMultiline;
+ }
+
+ return processedLines;
+}
+
+function canBeMultiline(line: string): boolean {
+ if (!line.startsWith(' ')) {
+ return false;
+ }
+
+ const trimmedLine = line.trimStart();
+ return trimmedLine.startsWith('|') || trimmedLine.startsWith('-');
+}
+
+/**
+ * Logic to check if a line should not be multiline.
+ *
+ * @param line The line to check
+ * @returns If the line should not be multiline
+ */
+function shouldNotBeMultiline(line: string, lastLineIsMultiline: boolean): boolean {
+ if (!lastLineIsMultiline && isEmptyLine(line)) {
+ return true;
+ }
+
+ // Custom logic: if the line contains -concat, it should not be multiline
+ if (line.indexOf('-concat') !== -1) {
+ return true;
+ }
+
+ return false;
+}
+
+function isEmptyLine(line: string): boolean {
+ return line.trim() === '';
+}
+
+
+/**
+ * Pass two.
+ *
+ * Traverse the lines to
+ * - remove escape characters
+ * - add placeholder lines to preserve the original number of lines.
+ *
+ * @param lines The lines in pass one
+ * @returns The processed lines
+ */
+function sceneTextPreProcessPassTwo(lines: string[]): string[] {
+ const processedLines: string[] = [];
+ let currentMultilineContent = "";
+ let placeHolderLines: string[] = [];
+
+ function concat(line: string) {
+ let trimmed = line.trim();
+ if (trimmed.startsWith('-')) {
+ trimmed = " " + trimmed;
+ }
+ currentMultilineContent = currentMultilineContent + trimmed;
+ placeHolderLines.push(placeholderLine(line));
+ }
+
+ for (const line of lines) {
+ console.log(line);
+ if (line.endsWith('\\')) {
+ const trueLine = line.slice(0, -1);
+
+ if (currentMultilineContent === "") {
+ // first line
+ currentMultilineContent = trueLine;
+ } else {
+ // middle line
+ concat(trueLine);
+ }
+ continue;
+ }
+
+ if (currentMultilineContent !== "") {
+ // end line
+ concat(line);
+ processedLines.push(currentMultilineContent);
+ processedLines.push(...placeHolderLines);
+
+ placeHolderLines = [];
+ currentMultilineContent = "";
+ continue;
+ }
+
+ processedLines.push(line);
+ }
+
+ return processedLines;
+}
+
+/**
+ * Placeholder Line. Adding this line preserves the original number of lines
+ * in the scene text, so that it can be compatible with the graphical editor.
+ *
+ * @param content The original content on this line
+ * @returns The placeholder line
+ */
+function placeholderLine(content = "") {
+ return ";_WEBGAL_LINE_BREAK_" + content;
+}
+
+// export function sceneTextPreProcess(sceneText: string): string {
+// const lines = sceneText.replaceAll('\r', '').split('\n');
+// const processedLines: string[] = [];
+// let lastNonMultilineIndex = -1;
+// let isInMultilineSequence = false;
+
+// function isMultiline(line: string): boolean {
+// if (!line.startsWith(' ')) return false;
+// const trimmedLine = line.trimStart();
+// return trimmedLine.startsWith('|') || trimmedLine.startsWith('-');
+// }
+
+// for (let i = 0; i < lines.length; i++) {
+// const line = lines[i];
+
+// if (line.trim() === '') {
+// // Empty line handling
+// if (isInMultilineSequence) {
+// // Check if the next line is a multiline line
+
+// let isStillInMulti = false;
+// for (let j = i + 1; j < lines.length; j++) {
+// const lookForwardLine = lines[j] || '';
+// // 遇到正常语句了,直接中断
+// if (lookForwardLine.trim() !== '' && !isMultiline(lookForwardLine)) {
+// isStillInMulti = false;
+// break;
+// }
+// // 必须找到后面接的是参数,并且中间没有遇到任何正常语句才行
+// if (lookForwardLine.trim() !== '' && isMultiline(lookForwardLine)) {
+// isStillInMulti = true;
+// break;
+// }
+// }
+// if (isStillInMulti) {
+// // Still within a multiline sequence
+// processedLines.push(';_WEBGAL_LINE_BREAK_');
+// } else {
+// // End of multiline sequence
+// isInMultilineSequence = false;
+// processedLines.push(line);
+// }
+// } else {
+// // Preserve empty lines outside of multiline sequences
+// processedLines.push(line);
+// }
+// } else if (isMultiline(line)) {
+// // Multiline statement handling
+// if (lastNonMultilineIndex >= 0) {
+// // Concatenate to the previous non-multiline statement
+// const trimedLine = line.trimStart();
+// const addBlank = trimedLine.startsWith('-') ? ' ' : '';
+// processedLines[lastNonMultilineIndex] += addBlank + trimedLine;
+// }
+
+// // Add the special comment line
+// processedLines.push(';_WEBGAL_LINE_BREAK_' + line);
+// isInMultilineSequence = true;
+// } else {
+// // Non-multiline statement handling
+// processedLines.push(line);
+// lastNonMultilineIndex = processedLines.length - 1;
+// isInMultilineSequence = false;
+// }
+// }
+
+// return processedLines.join('\n');
+// }
\ No newline at end of file
diff --git a/packages/parser/src/scriptParser/argsParser.ts b/packages/parser/src/scriptParser/argsParser.ts
new file mode 100644
index 000000000..9cff77451
--- /dev/null
+++ b/packages/parser/src/scriptParser/argsParser.ts
@@ -0,0 +1,71 @@
+import { arg } from '../interface/sceneInterface';
+import { fileType } from '../interface/assets';
+
+/**
+ * 参数解析器
+ * @param argsRaw 原始参数字符串
+ * @param assetSetter
+ * @return {Array} 解析后的参数列表
+ */
+export function argsParser(
+ argsRaw: string,
+ assetSetter: (fileName: string, assetType: fileType) => string,
+): Array {
+ const returnArrayList: Array = [];
+ // 处理参数
+ // 不要去空格
+ let newArgsRaw = argsRaw.replace(/ /g, ' ');
+ // 分割参数列表
+ let rawArgsList: Array = newArgsRaw.split(' -');
+ // 去除空字符串
+ rawArgsList = rawArgsList.filter((e) => {
+ return e !== '';
+ });
+ rawArgsList.forEach((e) => {
+ const equalSignIndex = e.indexOf('=');
+ let argName = e.slice(0, equalSignIndex);
+ let argValue: string | undefined = e.slice(equalSignIndex + 1);
+ if (equalSignIndex < 0) {
+ argName = e;
+ argValue = undefined;
+ }
+ // 判断是不是语音参数
+ if (argName.toLowerCase().match(/.ogg|.mp3|.wav/)) {
+ returnArrayList.push({
+ key: 'vocal',
+ value: assetSetter(e, fileType.vocal),
+ });
+ } else {
+ // 判断是不是省略参数
+ if (argValue === undefined) {
+ returnArrayList.push({
+ key: argName,
+ value: true,
+ });
+ } else {
+ // 是字符串描述的布尔值
+ if (argValue === 'true' || argValue === 'false') {
+ returnArrayList.push({
+ key: argName,
+ value: argValue === 'true',
+ });
+ } else {
+ // 是数字
+ if (!isNaN(Number(argValue))) {
+ returnArrayList.push({
+ key: argName,
+ value: Number(argValue),
+ });
+ } else {
+ // 是普通参数
+ returnArrayList.push({
+ key: argName,
+ value: argValue,
+ });
+ }
+ }
+ }
+ }
+ });
+ return returnArrayList;
+}
diff --git a/packages/parser/src/scriptParser/assetsScanner.ts b/packages/parser/src/scriptParser/assetsScanner.ts
new file mode 100644
index 000000000..cdff61be5
--- /dev/null
+++ b/packages/parser/src/scriptParser/assetsScanner.ts
@@ -0,0 +1,76 @@
+import { arg, commandType, IAsset } from '../interface/sceneInterface';
+import { fileType } from '../interface/assets';
+
+/**
+ * 根据语句类型、语句内容、参数列表,扫描该语句可能携带的资源
+ * @param command 语句类型
+ * @param content 语句内容
+ * @param args 参数列表
+ * @return {Array} 语句携带的参数列表
+ */
+export const assetsScanner = (
+ command: commandType,
+ content: string,
+ args: Array,
+): Array => {
+ let hasVocalArg = false;
+ const returnAssetsList: Array = [];
+ if (command === commandType.say) {
+ args.forEach((e) => {
+ if (e.key === 'vocal') {
+ hasVocalArg = true;
+ returnAssetsList.push({
+ name: e.value as string,
+ url: e.value as string,
+ lineNumber: 0,
+ type: fileType.vocal,
+ });
+ }
+ });
+ }
+ if (content === 'none' || content === '') {
+ return returnAssetsList;
+ }
+ // 处理语句携带的资源
+ if (command === commandType.changeBg) {
+ returnAssetsList.push({
+ name: content,
+ url: content,
+ lineNumber: 0,
+ type: fileType.background,
+ });
+ }
+ if (command === commandType.changeFigure) {
+ returnAssetsList.push({
+ name: content,
+ url: content,
+ lineNumber: 0,
+ type: fileType.figure,
+ });
+ }
+ if (command === commandType.miniAvatar) {
+ returnAssetsList.push({
+ name: content,
+ url: content,
+ lineNumber: 0,
+ type: fileType.figure,
+ });
+ }
+ if (command === commandType.video) {
+ returnAssetsList.push({
+ name: content,
+ url: content,
+ lineNumber: 0,
+ type: fileType.video,
+ });
+ }
+ if (command === commandType.bgm) {
+ returnAssetsList.push({
+ name: content,
+ url: content,
+ lineNumber: 0,
+ type: fileType.bgm,
+ });
+ }
+ return returnAssetsList;
+};
diff --git a/packages/parser/src/scriptParser/commandParser.ts b/packages/parser/src/scriptParser/commandParser.ts
new file mode 100644
index 000000000..b08655881
--- /dev/null
+++ b/packages/parser/src/scriptParser/commandParser.ts
@@ -0,0 +1,65 @@
+import { ConfigMap } from '../config/scriptConfig';
+import { commandType, parsedCommand } from '../interface/sceneInterface';
+
+/**
+ * 处理命令
+ * @param commandRaw
+ * @param ADD_NEXT_ARG_LIST
+ * @param SCRIPT_CONFIG_MAP
+ * @return {parsedCommand} 处理后的命令
+ */
+export const commandParser = (
+ commandRaw: string,
+ ADD_NEXT_ARG_LIST: commandType[],
+ SCRIPT_CONFIG_MAP: ConfigMap,
+): parsedCommand => {
+ let returnCommand: parsedCommand = {
+ type: commandType.say, // 默认是say
+ additionalArgs: [],
+ };
+ // 开始处理命令内容
+ const type: commandType = getCommandType(
+ commandRaw,
+ ADD_NEXT_ARG_LIST,
+ SCRIPT_CONFIG_MAP,
+ );
+ returnCommand.type = type;
+ // 如果是对话,加上额外的参数
+ if (type === commandType.say && commandRaw !== 'say') {
+ returnCommand.additionalArgs.push({
+ key: 'speaker',
+ value: commandRaw,
+ });
+ }
+ returnCommand = addNextArg(returnCommand, type, ADD_NEXT_ARG_LIST);
+ return returnCommand;
+};
+
+/**
+ * 根据command原始值判断是什么命令
+ * @param command command原始值
+ * @param ADD_NEXT_ARG_LIST
+ * @param SCRIPT_CONFIG_MAP
+ * @return {commandType} 得到的command类型
+ */
+function getCommandType(
+ command: string,
+ ADD_NEXT_ARG_LIST: commandType[],
+ SCRIPT_CONFIG_MAP: ConfigMap,
+): commandType {
+ return SCRIPT_CONFIG_MAP.get(command)?.scriptType ?? commandType.say;
+}
+
+function addNextArg(
+ commandToParse: parsedCommand,
+ thisCommandType: commandType,
+ ADD_NEXT_ARG_LIST: commandType[],
+) {
+ if (ADD_NEXT_ARG_LIST.includes(thisCommandType)) {
+ commandToParse.additionalArgs.push({
+ key: 'next',
+ value: true,
+ });
+ }
+ return commandToParse;
+}
diff --git a/packages/parser/src/scriptParser/contentParser.ts b/packages/parser/src/scriptParser/contentParser.ts
new file mode 100644
index 000000000..cbb6fd161
--- /dev/null
+++ b/packages/parser/src/scriptParser/contentParser.ts
@@ -0,0 +1,70 @@
+import { commandType } from '../interface/sceneInterface';
+import { fileType } from '../interface/assets';
+
+/**
+ * 解析语句内容的函数,主要作用是把文件名改为绝对地址或相对地址(根据使用情况而定)
+ * @param contentRaw 原始语句内容
+ * @param type 语句类型
+ * @param assetSetter
+ * @return {string} 解析后的语句内容
+ */
+export const contentParser = (
+ contentRaw: string,
+ type: commandType,
+ assetSetter: any,
+) => {
+ if (contentRaw === 'none' || contentRaw === '') {
+ return '';
+ }
+ switch (type) {
+ case commandType.playEffect:
+ return assetSetter(contentRaw, fileType.vocal);
+ case commandType.changeBg:
+ return assetSetter(contentRaw, fileType.background);
+ case commandType.changeFigure:
+ return assetSetter(contentRaw, fileType.figure);
+ case commandType.bgm:
+ return assetSetter(contentRaw, fileType.bgm);
+ case commandType.callScene:
+ return assetSetter(contentRaw, fileType.scene);
+ case commandType.changeScene:
+ return assetSetter(contentRaw, fileType.scene);
+ case commandType.miniAvatar:
+ return assetSetter(contentRaw, fileType.figure);
+ case commandType.video:
+ return assetSetter(contentRaw, fileType.video);
+ case commandType.choose:
+ return getChooseContent(contentRaw, assetSetter);
+ case commandType.unlockBgm:
+ return assetSetter(contentRaw, fileType.bgm);
+ case commandType.unlockCg:
+ return assetSetter(contentRaw, fileType.background);
+ default:
+ return contentRaw;
+ }
+};
+
+function getChooseContent(contentRaw: string, assetSetter: any): string {
+ const chooseList = contentRaw.split(/(? = [];
+ const chooseValueList: Array = [];
+ for (const e of chooseList) {
+ chooseKeyList.push(e.split(/(? {
+ if (e.match(/\./)) {
+ return assetSetter(e, fileType.scene);
+ } else {
+ return e;
+ }
+ });
+ let ret = '';
+ for (let i = 0; i < chooseKeyList.length; i++) {
+ if (i !== 0) {
+ ret = ret + '|';
+ }
+ ret = ret + `${chooseKeyList[i]}:${parsedChooseList[i]}`;
+ }
+ return ret;
+}
diff --git a/packages/parser/src/scriptParser/scriptParser.ts b/packages/parser/src/scriptParser/scriptParser.ts
new file mode 100644
index 000000000..10bbfe197
--- /dev/null
+++ b/packages/parser/src/scriptParser/scriptParser.ts
@@ -0,0 +1,114 @@
+import {
+ arg,
+ commandType,
+ IAsset,
+ ISentence,
+ parsedCommand,
+} from '../interface/sceneInterface';
+import { commandParser } from './commandParser';
+import { argsParser } from './argsParser';
+import { contentParser } from './contentParser';
+import { assetsScanner } from './assetsScanner';
+import { subSceneScanner } from './subSceneScanner';
+import { ConfigMap } from '../config/scriptConfig';
+
+/**
+ * 语句解析器
+ * @param sentenceRaw 原始语句
+ * @param assetSetter
+ * @param ADD_NEXT_ARG_LIST
+ * @param SCRIPT_CONFIG_MAP
+ */
+export const scriptParser = (
+ sentenceRaw: string,
+ assetSetter: any,
+ ADD_NEXT_ARG_LIST: commandType[],
+ SCRIPT_CONFIG_MAP: ConfigMap,
+): ISentence => {
+ let command: commandType; // 默认为对话
+ let content: string; // 语句内容
+ let subScene: Array; // 语句携带的子场景(可能没有)
+ const args: Array = []; // 语句参数列表
+ let sentenceAssets: Array; // 语句携带的资源列表
+ let parsedCommand: parsedCommand; // 解析后的命令
+ let commandRaw: string;
+
+ // 正式开始解析
+
+ // 去分号
+ let newSentenceRaw = sentenceRaw.split(/(?} 子场景列表
+ */
+import { commandType } from '../interface/sceneInterface';
+
+export const subSceneScanner = (
+ command: commandType,
+ content: string,
+): Array => {
+ const subSceneList: Array = [];
+ if (
+ command === commandType.changeScene ||
+ command === commandType.callScene
+ ) {
+ subSceneList.push(content);
+ }
+ if (command === commandType.choose) {
+ const chooseList = content.split('|');
+ const chooseValue = chooseList.map((e) => e.split(':')[1] ?? '');
+ chooseValue.forEach((e) => {
+ if (e.match(/\./)) {
+ subSceneList.push(e);
+ }
+ });
+ }
+ return subSceneList;
+};
diff --git a/packages/parser/src/styleParser/index.ts b/packages/parser/src/styleParser/index.ts
new file mode 100644
index 000000000..a97d0f621
--- /dev/null
+++ b/packages/parser/src/styleParser/index.ts
@@ -0,0 +1,39 @@
+export interface IWebGALStyleObj {
+ classNameStyles: Record;
+ others: string;
+}
+
+export function scss2cssinjsParser(scssString: string): IWebGALStyleObj {
+ const [classNameStyles, others] = parseCSS(scssString);
+ return {
+ classNameStyles,
+ others,
+ };
+}
+
+/**
+ * GPT 4 写的,临时用,以后要重构!!!
+ * TODO:用人类智能重构,要是用着一直没问题,也不是不可以 trust AI
+ * @param css
+ */
+function parseCSS(css: string): [Record, string] {
+ const result: Record = {};
+ let specialRules = '';
+ let matches;
+
+ // 使用非贪婪匹配,尝试正确处理任意层次的嵌套
+ const classRegex = /\.([^{\s]+)\s*{((?:[^{}]*|{[^}]*})*)}/g;
+ const specialRegex = /(@[^{]+{\s*(?:[^{}]*{[^}]*}[^{}]*)+\s*})/g;
+
+ while ((matches = classRegex.exec(css)) !== null) {
+ const key = matches[1];
+ const value = matches[2].trim().replace(/\s*;\s*/g, ';\n');
+ result[key] = value;
+ }
+
+ while ((matches = specialRegex.exec(css)) !== null) {
+ specialRules += matches[1].trim() + '\n';
+ }
+
+ return [result, specialRules.trim()];
+}
diff --git a/packages/parser/test/bgm.test.ts b/packages/parser/test/bgm.test.ts
new file mode 100644
index 000000000..ccd4f8436
--- /dev/null
+++ b/packages/parser/test/bgm.test.ts
@@ -0,0 +1,56 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+import { fileType } from "../src/interface/assets";
+
+test("bgm-1", async () => {
+ expectContainEqual(`bgm:夏影.mp3;`,
+ [{
+ command: commandType.bgm,
+ commandRaw: "bgm",
+ content: "夏影.mp3",
+ args: [],
+ sentenceAssets: [{ name: '夏影.mp3', url: '夏影.mp3', lineNumber: 0, type: fileType.bgm }],
+ subScene: []
+ }]
+ );
+});
+
+test("bgm-2", async () => {
+ expectContainEqual(`bgm:夏影.mp3 -volume=30;`,
+ [{
+ command: commandType.bgm,
+ commandRaw: "bgm",
+ content: "夏影.mp3",
+ args: [{ key: "volume", value: 30 }],
+ sentenceAssets: [{ name: '夏影.mp3', url: '夏影.mp3', lineNumber: 0, type: fileType.bgm }],
+ subScene: []
+ }]
+ );
+});
+
+test("bgm-3", async () => {
+ expectContainEqual(`bgm:夏影.mp3 -enter=3000;`,
+ [{
+ command: commandType.bgm,
+ commandRaw: "bgm",
+ content: "夏影.mp3",
+ args: [{ key: "enter", value: 3000 }],
+ sentenceAssets: [{ name: '夏影.mp3', url: '夏影.mp3', lineNumber: 0, type: fileType.bgm }],
+ subScene: []
+ }]
+ );
+});
+
+test("bgm-4", async () => {
+ expectContainEqual(`bgm:none -enter=3000;`,
+ [{
+ command: commandType.bgm,
+ commandRaw: "bgm",
+ content: "",
+ args: [{ key: "enter", value: 3000 }],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/callScene.test.ts b/packages/parser/test/callScene.test.ts
new file mode 100644
index 000000000..acf2bdc53
--- /dev/null
+++ b/packages/parser/test/callScene.test.ts
@@ -0,0 +1,16 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("callScene-1", async () => {
+ expectContainEqual(`callScene:Chapter-2.txt;`,
+ [{
+ command: commandType.callScene,
+ commandRaw: "callScene",
+ content: "Chapter-2.txt",
+ args: [],
+ sentenceAssets: [],
+ subScene: ["Chapter-2.txt"]
+ }]
+ );
+});
diff --git a/packages/parser/test/changeBg.test.ts b/packages/parser/test/changeBg.test.ts
new file mode 100644
index 000000000..cf906ae33
--- /dev/null
+++ b/packages/parser/test/changeBg.test.ts
@@ -0,0 +1,130 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+import { fileType } from "../src/interface/assets";
+
+test("changeBg-1", async () => {
+ expectContainEqual(`changeBg:1.jpg -left="https://example-url.com" -next; 引号字符串允许包含任何特殊字符`,
+ [{
+ command: commandType.changeBg,
+ commandRaw: "changeBg",
+ content: "1.jpg",
+ args: [
+ { key: "left", value: "https://example-url.com" },
+ { key: "next", value: true }
+ ],
+ sentenceAssets: [{ name: '1.jpg', url: '1.jpg', lineNumber: 0, type: fileType.background }],
+ subScene: []
+ }]
+ );
+});
+
+test("changeBg-2", async () => {
+ expectContainEqual(`changeBg:2-1.jpg -left=" ; 不匹配的引号不会被解析为引号字符串`,
+ [{
+ command: commandType.changeBg,
+ commandRaw: "changeBg",
+ content: "2-1.jpg",
+ args: [
+ { key: "left", value: '"' }
+ ],
+ sentenceAssets: [{ name: '2-1.jpg', url: '2-1.jpg', lineNumber: 0, type: fileType.background }],
+ subScene: []
+ }]
+ );
+});
+
+test("changeBg-3", async () => {
+ expectContainEqual(
+`changeBg:3_1.jpg -transform='{"hello": "world"}' ; JSON字符串`,
+ [{
+ command: commandType.changeBg,
+ commandRaw: "changeBg",
+ content: "3_1.jpg",
+ args: [
+ { key: "transform", value: '{"hello": "world"}' }
+ ],
+ sentenceAssets: [{ name: '3_1.jpg', url: '3_1.jpg', lineNumber: 0, type: fileType.background }],
+ subScene: []
+ }]
+ );
+});
+
+test("changeBg-4", async () => {
+ expectContainEqual(`changeBg:4-4-4.jpg -transform={"hello":"world"} ; 不加单引号也可以,但不能有空格`,
+ [{
+ command: commandType.changeBg,
+ commandRaw: "changeBg",
+ content: "4-4-4.jpg",
+ args: [
+ { key: "transform", value: '{"hello":"world"}' }
+ ],
+ sentenceAssets: [{ name: '4-4-4.jpg', url: '4-4-4.jpg', lineNumber: 0, type: fileType.background }],
+ subScene: []
+ }]
+ );
+});
+
+test("changeBg-5", async () => {
+ expectContainEqual(`changeBg:5.jpg -transform="{"hello": "world"}" ; 显然这个会解析失败`, [
+ {
+ command: commandType.changeBg,
+ commandRaw: "changeBg",
+ content: "5.jpg",
+ args: [
+ { key: "transform", value: '{' }
+ ],
+ sentenceAssets: [{ name: '5.jpg', url: '5.jpg', lineNumber: 0, type: fileType.background }],
+ subScene: []
+ }
+ ], [{
+ location: '29(1:30)..61(1:62)',
+ message: "parsing cannot preceed"
+ }]);
+});
+
+test("changeBg-6", async () => {
+ expectContainEqual(`changeBg:6.jpg -transform="{\\"hello\\": \\"world\\"}" ; 但引号字符串支持转义又比较好地弥补了这一点`,
+ [{
+ command: commandType.changeBg,
+ commandRaw: "changeBg",
+ content: "6.jpg",
+ args: [
+ { key: "transform", value: '{"hello": "world"}' }
+ ],
+ sentenceAssets: [{ name: '6.jpg', url: '6.jpg', lineNumber: 0, type: fileType.background }],
+ subScene: []
+ }]
+ );
+});
+
+test("changeBg-7", async () => {
+ expectContainEqual(`changeBg:7.jpg -next=true ; 不含引号的值直接解析`,
+ [{
+ command: commandType.changeBg,
+ commandRaw: "changeBg",
+ content: "7.jpg",
+ args: [
+ { key: "next", value: true }
+ ],
+ sentenceAssets: [{ name: '7.jpg', url: '7.jpg', lineNumber: 0, type: fileType.background }],
+ subScene: []
+ }]
+ );
+});
+
+test("changeBg-8", async () => {
+ expectContainEqual(`changeBg:8.jpg -next -left=none ; 测试多个参数`,
+ [{
+ command: commandType.changeBg,
+ commandRaw: "changeBg",
+ content: "8.jpg",
+ args: [
+ { key: "next", value: true },
+ { key: "left", value: "" }
+ ],
+ sentenceAssets: [{ name: '8.jpg', url: '8.jpg', lineNumber: 0, type: fileType.background }],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/changeFigure.test.ts b/packages/parser/test/changeFigure.test.ts
new file mode 100644
index 000000000..a87006440
--- /dev/null
+++ b/packages/parser/test/changeFigure.test.ts
@@ -0,0 +1,64 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+import { fileType } from "../src/interface/assets";
+
+test("changeFigure-1", async () => {
+ expectContainEqual(`changeFigure:testFigure02.png -next; 改变人物立绘`,
+ [{
+ command: commandType.changeFigure,
+ commandRaw: "changeFigure",
+ content: "testFigure02.png",
+ args: [
+ { key: "next", value: true },
+ ],
+ sentenceAssets: [
+ {
+ name: 'testFigure02.png',
+ url: 'testFigure02.png',
+ lineNumber: 0,
+ type: fileType.figure
+ }
+ ],
+ subScene: []
+ }]
+ );
+});
+
+test("changeFigure-2", async () => {
+ expectContainEqual(`changeFigure:testFigure03.png -left -id=test1; 一个初始位置在右侧的自由立绘`,
+ [{
+ command: commandType.changeFigure,
+ commandRaw: "changeFigure",
+ content: "testFigure03.png",
+ args: [
+ { key: "left", value: true },
+ { key: "id", value: 'test1' },
+ ],
+ sentenceAssets: [
+ {
+ name: 'testFigure03.png',
+ url: 'testFigure03.png',
+ lineNumber: 0,
+ type: fileType.figure
+ }
+ ],
+ subScene: []
+ }]
+ );
+});
+
+test("changeFigure-2", async () => {
+ expectContainEqual(`changeFigure:none -id=test1; 通过 id 关闭立绘`,
+ [{
+ command: commandType.changeFigure,
+ commandRaw: "changeFigure",
+ content: "",
+ args: [
+ { key: "id", value: 'test1' },
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/changeScene.test.ts b/packages/parser/test/changeScene.test.ts
new file mode 100644
index 000000000..0776db952
--- /dev/null
+++ b/packages/parser/test/changeScene.test.ts
@@ -0,0 +1,16 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("changeScene-1", async () => {
+ expectContainEqual(`changeScene:Chapter-2.txt;`,
+ [{
+ command: commandType.changeScene,
+ commandRaw: "changeScene",
+ content: "Chapter-2.txt",
+ args: [],
+ sentenceAssets: [],
+ subScene: ["Chapter-2.txt"]
+ }]
+ );
+});
diff --git a/packages/parser/test/choose.test.ts b/packages/parser/test/choose.test.ts
new file mode 100644
index 000000000..1b8abb6ad
--- /dev/null
+++ b/packages/parser/test/choose.test.ts
@@ -0,0 +1,106 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("choose-1", async () => {
+ expectContainEqual(`choose:叫住她:Chapter-2.txt|回家:Chapter-3.txt;`,
+ [{
+ command: commandType.choose,
+ commandRaw: "choose",
+ content: "叫住她:Chapter-2.txt|回家:Chapter-3.txt",
+ args: [
+ {
+ key: "choices",
+ value: [
+ {
+ showExpression: "",
+ clickExpression: "",
+ text: "叫住她",
+ destination: "Chapter-2.txt"
+ },
+ {
+ showExpression: "",
+ clickExpression: "",
+ text: "回家",
+ destination: "Chapter-3.txt"
+ }
+ ]
+ },
+ {
+ key: "contentRawRange",
+ value: [7, 41],
+ }
+ ],
+ sentenceAssets: [],
+ subScene: ["Chapter-2.txt", "Chapter-3.txt"]
+ }]);
+});
+
+test("choose-2", async () => {
+ expectContainEqual(`choose:(showConditionVar>1)[enableConditionVar>2]->叫住她:Chapter-2.txt|回家:Chapter-3.txt;`,
+ [{
+ command: commandType.choose,
+ commandRaw: "choose",
+ content: "(showConditionVar>1)[enableConditionVar>2]->叫住她:Chapter-2.txt|回家:Chapter-3.txt",
+ args: [
+ {
+ key: "choices",
+ value: [
+ {
+ showExpression: "showConditionVar>1",
+ clickExpression: "enableConditionVar>2",
+ text: "叫住她",
+ destination: "Chapter-2.txt"
+ },
+ {
+ showExpression: "",
+ clickExpression: "",
+ text: "回家",
+ destination: "Chapter-3.txt"
+ }
+ ]
+ },
+ {
+ key: "contentRawRange",
+ value: [7, 85],
+ }
+ ],
+ sentenceAssets: [],
+ subScene: ["Chapter-2.txt", "Chapter-3.txt"]
+ }]
+ );
+});
+
+test("choose-3", async () => {
+ expectContainEqual(`choose:分支 1:label_1|分支 2:label_2;`,
+ [{
+ command: commandType.choose,
+ commandRaw: "choose",
+ content: "分支 1:label_1|分支 2:label_2",
+ args: [
+ {
+ key: "choices",
+ value: [
+ {
+ showExpression: "",
+ clickExpression: "",
+ text: "分支 1",
+ destination: "label_1"
+ },
+ {
+ showExpression: "",
+ clickExpression: "",
+ text: "分支 2",
+ destination: "label_2"
+ }
+ ]
+ },
+ {
+ key: "contentRawRange",
+ value: [7, 32],
+ }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]);
+});
diff --git a/packages/parser/test/comment.test.ts b/packages/parser/test/comment.test.ts
new file mode 100644
index 000000000..c3bebfa51
--- /dev/null
+++ b/packages/parser/test/comment.test.ts
@@ -0,0 +1,17 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+import { fileType } from "../src/interface/assets";
+
+test("comment-1", async () => {
+ expectContainEqual(`
+`,
+ [{
+ command: commandType.comment,
+ commandRaw: "comment",
+ content: "",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]);
+});
diff --git a/packages/parser/test/debug-linebreak.ts b/packages/parser/test/debug-linebreak.ts
new file mode 100644
index 000000000..03e32dff7
--- /dev/null
+++ b/packages/parser/test/debug-linebreak.ts
@@ -0,0 +1,13 @@
+import {sceneTextPreProcess} from "../src/sceneTextPreProcessor";
+import * as fsp from "fs/promises";
+
+
+async function debug() {
+ const sceneRaw = await fsp.readFile('test/test-resources/line-break.txt');
+ const sceneText = sceneRaw.toString();
+ const result = sceneTextPreProcess(sceneText)
+ console.log(result)
+ console.log(result.split('\n').length)
+}
+
+debug();
diff --git a/packages/parser/test/end.test.ts b/packages/parser/test/end.test.ts
new file mode 100644
index 000000000..7e5dcf8a8
--- /dev/null
+++ b/packages/parser/test/end.test.ts
@@ -0,0 +1,16 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual } from './util';
+
+test("end-1", async () => {
+ expectContainEqual(`end;`,
+ [{
+ command: commandType.end,
+ commandRaw: "end",
+ content: "",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/errorHandling.test.ts b/packages/parser/test/errorHandling.test.ts
new file mode 100644
index 000000000..c535c1d45
--- /dev/null
+++ b/packages/parser/test/errorHandling.test.ts
@@ -0,0 +1,74 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("err-1", async () => {
+ expectContainEqual(`end:shouldNotAddContentHere -();
+end;`,
+ [
+ {
+ command: commandType.end,
+ commandRaw: "end",
+ content: "",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ },
+ {
+ command: commandType.end,
+ commandRaw: "end",
+ content: "",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }
+ ],
+ [
+ {
+ location: '3(1:4)..32(1:33)',
+ message: "non-completed parsing"
+ }
+ ]
+ );
+});
+
+test("err-2", async () => {
+ expectContainEqual(`one command:is not affected -next
+end:shouldNotAddContentHere -()
+another command:is not affected as well`, [
+ {
+ command: commandType.say,
+ commandRaw: "one command",
+ content: "is not affected",
+ args: [
+ { key: "speaker", value: "one command" },
+ { key: "next", value: true }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ },
+ {
+ command: commandType.end,
+ commandRaw: "end",
+ content: "",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ },
+ {
+ command: commandType.say,
+ commandRaw: "another command",
+ content: "is not affected as well",
+ args: [
+ { key: "speaker", value: "another command" },
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }
+ ], [
+ {
+ location: '37(2:4)..65(2:32)',
+ message: "non-completed parsing"
+ }
+ ]);
+});
\ No newline at end of file
diff --git a/packages/parser/test/filmMode.test.ts b/packages/parser/test/filmMode.test.ts
new file mode 100644
index 000000000..e28266b81
--- /dev/null
+++ b/packages/parser/test/filmMode.test.ts
@@ -0,0 +1,42 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("filmMode-1", async () => {
+ expectContainEqual(`filmMode:Film Mode Test;`,
+ [{
+ command: commandType.filmMode,
+ commandRaw: "filmMode",
+ content: "Film Mode Test",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("filmMode-2", async () => {
+ expectContainEqual(`filmMode:none;`,
+ [{
+ command: commandType.filmMode,
+ commandRaw: "filmMode",
+ content: "",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("filmMode-3", async () => {
+ expectContainEqual(`filmMode:;`,
+ [{
+ command: commandType.filmMode,
+ commandRaw: "filmMode",
+ content: "",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/getUserInput.test.ts b/packages/parser/test/getUserInput.test.ts
new file mode 100644
index 000000000..dc808f016
--- /dev/null
+++ b/packages/parser/test/getUserInput.test.ts
@@ -0,0 +1,19 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual } from './util';
+
+test("getUserInput-1", async () => {
+ expectContainEqual(`getUserInput:name -title=如何称呼你 -buttonText=确认; 将用户输入写入 name 变量中`,
+ [{
+ command: commandType.getUserInput,
+ commandRaw: "getUserInput",
+ content: "name",
+ args: [
+ { key: "title", value: "如何称呼你" },
+ { key: "buttonText", value: "确认" }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/integration.disablefornow.ts b/packages/parser/test/integration.disablefornow.ts
new file mode 100644
index 000000000..f0ea3183b
--- /dev/null
+++ b/packages/parser/test/integration.disablefornow.ts
@@ -0,0 +1,197 @@
+import { SceneParser } from "../src/index";
+import { ADD_NEXT_ARG_LIST, SCRIPT_CONFIG } from "../src/config/scriptConfig";
+import { expect, test } from "vitest";
+import { commandType, ISentence } from "../src/interface/sceneInterface";
+import * as fsp from 'fs/promises';
+import { fileType } from "../src/interface/assets";
+
+
+test("label", async () => {
+ const sceneRaw = await fsp.readFile('test/test-resources/start.txt');
+ const sceneText = sceneRaw.toString();
+
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const result = parser.parse(sceneText, "start", "/start.txt");
+ const expectSentenceItem: ISentence = {
+ command: commandType.label,
+ commandRaw: "label",
+ content: "end",
+ args: [
+ { key: "next", value: true }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ };
+ expect(result.sentenceList).toContainEqual(expectSentenceItem);
+});
+
+test("args", async () => {
+
+ const sceneRaw = await fsp.readFile('test/test-resources/start.txt');
+ const sceneText = sceneRaw.toString();
+
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const result = parser.parse(sceneText, "start", "/start.txt");
+ const expectSentenceItem: ISentence = {
+ command: commandType.changeFigure,
+ commandRaw: "changeFigure",
+ content: "m2.png",
+ args: [
+ { key: "left", value: true },
+ { key: "next", value: true }
+ ],
+ sentenceAssets: [{ name: "m2.png", url: 'm2.png', type: fileType.figure, lineNumber: 0 }],
+ subScene: []
+ };
+ expect(result.sentenceList).toContainEqual(expectSentenceItem);
+});
+
+test("choose", async () => {
+
+ const sceneRaw = await fsp.readFile('test/test-resources/choose.txt');
+ const sceneText = sceneRaw.toString();
+
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const result = parser.parse(sceneText, "choose", "/choose.txt");
+ console.error(result.errors);
+ const expectSentenceItem: ISentence = {
+ command: commandType.choose,
+ commandRaw: "choose",
+ content: "",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ };
+ expect(result.sentenceList).toContainEqual(expectSentenceItem);
+});
+
+test("long-script", async () => {
+
+ const sceneRaw = await fsp.readFile('test/test-resources/long-script.txt');
+ const sceneText = sceneRaw.toString();
+
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ console.log('line count:', sceneText.split('\n').length);
+ console.time('parse-time-consumed');
+ const result = parser.parse(sceneText, "start", "/start.txt");
+ console.timeEnd('parse-time-consumed');
+ const expectSentenceItem: ISentence = {
+ command: commandType.label,
+ commandRaw: "label",
+ content: "end",
+ args: [
+ { key: "next", value: true }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ };
+ expect(result.sentenceList).toContainEqual(expectSentenceItem);
+});
+
+test("var", async () => {
+
+ const sceneRaw = await fsp.readFile('test/test-resources/var.txt');
+ const sceneText = sceneRaw.toString();
+
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const result = parser.parse(sceneText, "var", "/var.txt");
+ const expectSentenceItem: ISentence = {
+ command: commandType.say,
+ commandRaw: "WebGAL",
+ content: "a=1?",
+ args: [{ key: 'speaker', value: 'WebGAL' }, { key: 'when', value: "a==1" }],
+ sentenceAssets: [],
+ subScene: []
+ };
+ expect(result.sentenceList).toContainEqual(expectSentenceItem);
+});
+
+test("config", async () => {
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const configFesult = parser.parseConfig(`
+Game_name:欢迎使用WebGAL!;
+Game_key:0f86dstRf;
+Title_img:WebGAL_New_Enter_Image.png;
+Title_bgm:s_Title.mp3;
+Title_logos: 1.png | 2.png | Image Logo.png| -show -active=false -add=op! -count=3;This is a fake config, do not reference anything.
+ `);
+ expect(configFesult).toContainEqual({
+ command: 'Title_logos',
+ args: ['1.png', '2.png', 'Image Logo.png'],
+ options: [
+ { key: 'show', value: true },
+ { key: 'active', value: false },
+ { key: 'add', value: 'op!' },
+ { key: 'count', value: 3 },
+ ]
+ });
+});
+
+test("config-stringify", async () => {
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const configFesult = parser.parseConfig(`
+Game_name:欢迎使用WebGAL!;
+Game_key:0f86dstRf;
+Title_img:WebGAL_New_Enter_Image.png;
+Title_bgm:s_Title.mp3;
+Title_logos: 1.png | 2.png | Image Logo.png| -show -active=false -add=op! -count=3;This is a fake config, do not reference anything.
+ `);
+ const stringifyResult = parser.stringifyConfig(configFesult);
+ const configResult2 = parser.parseConfig(stringifyResult);
+ expect(configResult2).toContainEqual({
+ command: 'Title_logos',
+ args: ['1.png', '2.png', 'Image Logo.png'],
+ options: [
+ { key: 'show', value: true },
+ { key: 'active', value: false },
+ { key: 'add', value: 'op!' },
+ { key: 'count', value: 3 },
+ ]
+ });
+});
+
+
+test("say statement", async () => {
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const result = parser.parse(`say:123 -speaker=xx;`, 'test', 'test');
+ expect(result.sentenceList).toContainEqual({
+ command: commandType.say,
+ commandRaw: "say",
+ content: "123",
+ args: [{ key: 'speaker', value: 'xx' }],
+ sentenceAssets: [],
+ subScene: []
+ });
+});
diff --git a/packages/parser/test/intro.test.ts b/packages/parser/test/intro.test.ts
new file mode 100644
index 000000000..363ddf692
--- /dev/null
+++ b/packages/parser/test/intro.test.ts
@@ -0,0 +1,42 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("intro-1", async () => {
+ expectContainEqual(`intro:回忆不需要适合的剧本,|反正一说出口,|都成了戏言。;`,
+ [{
+ command: commandType.intro,
+ commandRaw: "intro",
+ content: "回忆不需要适合的剧本,|反正一说出口,|都成了戏言。",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("intro-2", async () => {
+ expectContainEqual(`intro:回忆不需要适合的剧本,|反正一说出口,|都成了戏言。 -hold;`,
+ [{
+ command: commandType.intro,
+ commandRaw: "intro",
+ content: "回忆不需要适合的剧本,|反正一说出口,|都成了戏言。",
+ args: [{ key: "hold", value: true }],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("intro-3", async () => {
+ expectContainEqual(`intro:你好|欢迎来到 WebGAL 的世界;`,
+ [{
+ command: commandType.intro,
+ commandRaw: "intro",
+ content: "你好|欢迎来到 WebGAL 的世界",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/jumpLabel.test.ts b/packages/parser/test/jumpLabel.test.ts
new file mode 100644
index 000000000..b447694a0
--- /dev/null
+++ b/packages/parser/test/jumpLabel.test.ts
@@ -0,0 +1,16 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("jumpLabel-1", async () => {
+ expectContainEqual(`jumpLabel:label_1; // 跳转到 label_1`,
+ [{
+ command: commandType.jumpLabel,
+ commandRaw: "jumpLabel",
+ content: "label_1",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/label.test.ts b/packages/parser/test/label.test.ts
new file mode 100644
index 000000000..93e106461
--- /dev/null
+++ b/packages/parser/test/label.test.ts
@@ -0,0 +1,16 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("label-1", async () => {
+ expectContainEqual(`label:label_1; // 创建名为 label_1 的 label`,
+ [{
+ command: commandType.label,
+ commandRaw: "label",
+ content: "label_1",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/miniAvatar.test.ts b/packages/parser/test/miniAvatar.test.ts
new file mode 100644
index 000000000..acae3b53b
--- /dev/null
+++ b/packages/parser/test/miniAvatar.test.ts
@@ -0,0 +1,50 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+import { fileType } from "../src/interface/assets";
+
+test("miniAvatar-1", async () => {
+ expectContainEqual(`miniAvatar:minipic_test.png; 在左下角显示minipic_test.png`,
+ [{
+ command: commandType.miniAvatar,
+ commandRaw: "miniAvatar",
+ content: "minipic_test.png",
+ args: [],
+ sentenceAssets: [
+ {
+ name: 'minipic_test.png',
+ url: 'minipic_test.png',
+ lineNumber: 0,
+ type: fileType.figure
+ }
+ ],
+ subScene: []
+ }]
+ );
+});
+
+test("miniAvatar-2", async () => {
+ expectContainEqual(`miniAvatar:none; 关闭这个小头像`,
+ [{
+ command: commandType.miniAvatar,
+ commandRaw: "miniAvatar",
+ content: "",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("miniAvatar-3", async () => {
+ expectContainEqual(`miniAvatar:; 关闭这个小头像`,
+ [{
+ command: commandType.miniAvatar,
+ commandRaw: "miniAvatar",
+ content: "",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/parserMultiline.test.ts b/packages/parser/test/parserMultiline.test.ts
new file mode 100644
index 000000000..42815bcd2
--- /dev/null
+++ b/packages/parser/test/parserMultiline.test.ts
@@ -0,0 +1,148 @@
+import { sceneTextPreProcess } from "../src/sceneTextPreProcessor";
+import { expect, test } from "vitest";
+
+test("parser-multiline-basic", async () => {
+ const testScene = `changeFigure:a.png -left
+ -next
+ -id=id1
+
+saySomething`;
+ const expected = `changeFigure:a.png -left -next -id=id1
+;_WEBGAL_LINE_BREAK_ -next
+;_WEBGAL_LINE_BREAK_ -id=id1
+
+saySomething`;
+
+ const preprocessedScene = sceneTextPreProcess(testScene);
+ expect(preprocessedScene).toEqual(expected);
+});
+
+
+test("parser-multiline-disable-when-encounter-concat-1", async () => {
+ const testScene = `intro:aaa
+ |bbb -concat
+`;
+ const expected = `intro:aaa
+ |bbb -concat
+`;
+
+ const preprocessedScene = sceneTextPreProcess(testScene);
+ expect(preprocessedScene).toEqual(expected);
+});
+
+
+test("parser-multiline-disable-when-encounter-concat-2", async () => {
+ const testScene = `intro:aaa
+ |bbb
+ |ccc -concat
+`;
+ const expected = `intro:aaa|bbb
+;_WEBGAL_LINE_BREAK_ |bbb
+ |ccc -concat
+`;
+
+ const preprocessedScene = sceneTextPreProcess(testScene);
+ expect(preprocessedScene).toEqual(expected);
+});
+
+test("parser-multiline-user-force-allow-multiline-in-concat", async () => {
+ const testScene = String.raw`intro:aaa\
+|bbb\
+|ccc -concat
+`;
+ const expected = `intro:aaa|bbb|ccc -concat
+;_WEBGAL_LINE_BREAK_|bbb
+;_WEBGAL_LINE_BREAK_|ccc -concat
+`;
+
+ const preprocessedScene = sceneTextPreProcess(testScene);
+ expect(preprocessedScene).toEqual(expected);
+});
+
+test("parser-multiline-others-same-as-before", async () => {
+ const testScene = `听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+`;
+
+ const preprocessedScene = sceneTextPreProcess(testScene);
+ expect(preprocessedScene).toEqual(testScene);
+});
+
+test("parser-multiline-full", async () => {
+ const testScene = `changeFigure:a.png -left
+ -next
+ -id=id1
+
+intro:aaa
+ |bbb|ccc
+ |ddd
+ -next;
+
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好
+|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left
+ -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。
+ -v1.wav;
+changeFigure:stand2.png
+ -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back
+ -target=fig-left
+ -next;
+
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。
+ -v5.wav;
+`;
+
+ const expected = `changeFigure:a.png -left -next -id=id1
+;_WEBGAL_LINE_BREAK_ -next
+;_WEBGAL_LINE_BREAK_ -id=id1
+
+intro:aaa|bbb|ccc|ddd -next;
+;_WEBGAL_LINE_BREAK_ |bbb|ccc
+;_WEBGAL_LINE_BREAK_ |ddd
+;_WEBGAL_LINE_BREAK_ -next;
+
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好
+|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+;_WEBGAL_LINE_BREAK_ -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+;_WEBGAL_LINE_BREAK_ -v1.wav;
+changeFigure:stand2.png -right -next;
+;_WEBGAL_LINE_BREAK_ -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+;_WEBGAL_LINE_BREAK_ -target=fig-left
+;_WEBGAL_LINE_BREAK_ -next;
+
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+;_WEBGAL_LINE_BREAK_ -v5.wav;
+`;
+
+ const preprocessedScene = sceneTextPreProcess(testScene);
+ expect(preprocessedScene).toEqual(expected);
+});
diff --git a/packages/parser/test/pixi.test.ts b/packages/parser/test/pixi.test.ts
new file mode 100644
index 000000000..ea02299d4
--- /dev/null
+++ b/packages/parser/test/pixi.test.ts
@@ -0,0 +1,29 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("pixi-1", async () => {
+ expectContainEqual(`pixiInit;`,
+ [{
+ command: commandType.pixiInit,
+ commandRaw: "pixiInit",
+ content: "",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("pixi-2", async () => {
+ expectContainEqual(`pixiPerform:rain; // 添加一个下雨的特效`,
+ [{
+ command: commandType.pixi,
+ commandRaw: "pixiPerform",
+ content: "rain",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/playEffect.test.ts b/packages/parser/test/playEffect.test.ts
new file mode 100644
index 000000000..458077020
--- /dev/null
+++ b/packages/parser/test/playEffect.test.ts
@@ -0,0 +1,51 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("playEffect-1", async () => {
+ expectContainEqual(`playEffect:xxx.mp3;`,
+ [{
+ command: commandType.playEffect,
+ commandRaw: "playEffect",
+ content: "xxx.mp3",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("playEffect-2", async () => {
+ expectContainEqual(`playEffect:xxx.mp3 -volume=30;`,
+ [{
+ command: commandType.playEffect,
+ commandRaw: "playEffect",
+ content: "xxx.mp3",
+ args: [{ key: "volume", value: 30 }],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("playEffect-3", async () => {
+ expectContainEqual(`playEffect:xxx.mp3 -id=xxx;
+playEffect:none -id=xxx; // 停止这个循环的效果音`, [
+ {
+ command: commandType.playEffect,
+ commandRaw: "playEffect",
+ content: "xxx.mp3",
+ args: [{ key: "id", value: "xxx" }],
+ sentenceAssets: [],
+ subScene: []
+ },
+ {
+ command: commandType.playEffect,
+ commandRaw: "playEffect",
+ content: "",
+ args: [{ key: "id", value: "xxx" }],
+ sentenceAssets: [],
+ subScene: []
+ }
+ ]);
+});
diff --git a/packages/parser/test/playVideo.test.ts b/packages/parser/test/playVideo.test.ts
new file mode 100644
index 000000000..f6a100dfa
--- /dev/null
+++ b/packages/parser/test/playVideo.test.ts
@@ -0,0 +1,17 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+import { fileType } from "../src/interface/assets";
+
+test("playVideo-1", async () => {
+ expectContainEqual(`playVideo:OP.mp4;`,
+ [{
+ command: commandType.video,
+ commandRaw: "playVideo",
+ content: "OP.mp4",
+ args: [],
+ sentenceAssets: [{ name: 'OP.mp4', url: 'OP.mp4', lineNumber: 0, type: fileType.video }],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/say.test.ts b/packages/parser/test/say.test.ts
new file mode 100644
index 000000000..797d18522
--- /dev/null
+++ b/packages/parser/test/say.test.ts
@@ -0,0 +1,112 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+import { fileType } from "../src/interface/assets";
+
+test("say-1", async () => {
+ expectContainEqual(`雪之下雪乃:你到得真早;`,
+ [{
+ command: commandType.say,
+ commandRaw: "雪之下雪乃",
+ content: "你到得真早",
+ args: [
+ { key: "speaker", value: "雪之下雪乃" },
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("say-2", async () => {
+ expectContainEqual(`对不起,等很久了吗?;`,
+ [{
+ command: commandType.say,
+ commandRaw: "say",
+ content: "对不起,等很久了吗?",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("say-3", async () => {
+ expectContainEqual(`:这是一句旁白;`,
+ [{
+ command: commandType.say,
+ commandRaw: "",
+ content: "这是一句旁白",
+ args: [
+ { key: "speaker", value: "" },
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("say-4", async () => {
+ expectContainEqual(`WebGAL:a=1? -when=a==1;`,
+ [{
+ command: commandType.say,
+ commandRaw: "WebGAL",
+ content: "a=1?",
+ args: [
+ { key: "speaker", value: "WebGAL" },
+ { key: "when", value: "a==1" },
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("say-5", async () => {
+ expectContainEqual(`WebGAL: 你 打 字 带 空 格 -when=a==1;`,
+ [{
+ command: commandType.say,
+ commandRaw: "WebGAL",
+ content: "你 打 字 带 空 格",
+ args: [
+ { key: "speaker", value: "WebGAL" },
+ { key: "when", value: "a==1" },
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("say-6", async () => {
+ expectContainEqual(`WebGAL: 你 打 字 带 空 格 -when=a==1;`,
+ [{
+ command: commandType.say,
+ commandRaw: "WebGAL",
+ content: "你 打 字 带 空 格",
+ args: [
+ { key: "speaker", value: "WebGAL" },
+ { key: "when", value: "a==1" },
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("say-7", async () => {
+ expectContainEqual(`比企谷八幡:刚到而已 -V3.ogg -volume=30;`,
+ [{
+ command: commandType.say,
+ commandRaw: "比企谷八幡",
+ content: "刚到而已",
+ args: [
+ { key: "speaker", value: "比企谷八幡" },
+ { key: "vocal", value: "V3.ogg" },
+ { key: "volume", value: 30 },
+ ],
+ sentenceAssets: [{ name: 'V3.ogg', url: 'V3.ogg', lineNumber: 0, type: fileType.vocal }],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/setAnimation.test.ts b/packages/parser/test/setAnimation.test.ts
new file mode 100644
index 000000000..29b95f504
--- /dev/null
+++ b/packages/parser/test/setAnimation.test.ts
@@ -0,0 +1,29 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("setAnimation-1", async () => {
+ expectContainEqual(`; // 为中间立绘设置一个从下方进入的动画,并转到下一句
+setAnimation:enter-from-bottom -target=fig-center -next;`,
+ [
+ {
+ command: commandType.comment,
+ commandRaw: "comment",
+ content: " // 为中间立绘设置一个从下方进入的动画,并转到下一句",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ },
+ {
+ command: commandType.setAnimation,
+ commandRaw: "setAnimation",
+ content: "enter-from-bottom",
+ args: [
+ { key: "target", value: "fig-center" },
+ { key: "next", value: true },
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }
+ ]);
+});
diff --git a/packages/parser/test/setTempAnimation.test.ts b/packages/parser/test/setTempAnimation.test.ts
new file mode 100644
index 000000000..900da8600
--- /dev/null
+++ b/packages/parser/test/setTempAnimation.test.ts
@@ -0,0 +1,19 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("setTempAnimation-1", async () => {
+ expectContainEqual(`setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0}, {"position": {"x": 400,"y": 0},"duration": 250}, {"position": {"x": 600,"y": 0},"duration": 500}, {"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;`,
+ [{
+ command: commandType.setTempAnimation,
+ commandRaw: "setTempAnimation",
+ content: `[{"position": {"x": 500,"y": 0},"duration": 0}, {"position": {"x": 400,"y": 0},"duration": 250}, {"position": {"x": 600,"y": 0},"duration": 500}, {"position": {"x": 500,"y": 0},"duration": 250}]`,
+ args: [
+ { key: "target", value: "fig-left" },
+ { key: "next", value: true }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
\ No newline at end of file
diff --git a/packages/parser/test/setTextbox.test.ts b/packages/parser/test/setTextbox.test.ts
new file mode 100644
index 000000000..2932d52bd
--- /dev/null
+++ b/packages/parser/test/setTextbox.test.ts
@@ -0,0 +1,29 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("setTextbox-1", async () => {
+ expectContainEqual(`setTextbox:hide; // 关闭文本框`,
+ [{
+ command: commandType.setTextbox,
+ commandRaw: "setTextbox",
+ content: "hide",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("setTextbox-2", async () => {
+ expectContainEqual(`setTextbox:on; // 可以是除 hide 以外的任意值。`,
+ [{
+ command: commandType.setTextbox,
+ commandRaw: "setTextbox",
+ content: "on",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/setTransform.test.ts b/packages/parser/test/setTransform.test.ts
new file mode 100644
index 000000000..b8aabfa59
--- /dev/null
+++ b/packages/parser/test/setTransform.test.ts
@@ -0,0 +1,19 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("setTransform-1", async () => {
+ expectContainEqual(`setTransform:{"position":{"x":100,"y":0}} -target=fig-center -duration=0;`,
+ [{
+ command: commandType.setTransform,
+ commandRaw: "setTransform",
+ content: '{"position":{"x":100,"y":0}}',
+ args: [
+ { key: "target", value: "fig-center" },
+ { key: "duration", value: 0 }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/setTransition.test.ts b/packages/parser/test/setTransition.test.ts
new file mode 100644
index 000000000..dd23ac33d
--- /dev/null
+++ b/packages/parser/test/setTransition.test.ts
@@ -0,0 +1,20 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("setTransition-1", async () => {
+ expectContainEqual(`setTransition: -target=fig-center -enter=enter-from-bottom -exit=exit;`,
+ [{
+ command: commandType.setTransition,
+ commandRaw: "setTransition",
+ content: "",
+ args: [
+ { key: "target", value: "fig-center" },
+ { key: "enter", value: "enter-from-bottom" },
+ { key: "exit", value: "exit" },
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/setVar.test.ts b/packages/parser/test/setVar.test.ts
new file mode 100644
index 000000000..73ae7ed40
--- /dev/null
+++ b/packages/parser/test/setVar.test.ts
@@ -0,0 +1,161 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("setVar-1", async () => {
+ expectContainEqual(`setVar:a=1; // 可以设置数字`,
+ [{
+ command: commandType.setVar,
+ commandRaw: "setVar",
+ content: "",
+ args: [
+ { key: "#variableName", value: "a" },
+ { key: "#expression", value: 1 }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("setVar-2", async () => {
+ expectContainEqual(`setVar:a=true; // 可以设置布尔值`,
+ [{
+ command: commandType.setVar,
+ commandRaw: "setVar",
+ content: "",
+ args: [
+ { key: "#variableName", value: "a" },
+ { key: "#expression", value: true }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("setVar-3", async () => {
+ expectContainEqual(`setVar:a=人物名称; // 可以设置字符串`,
+ [{
+ command: commandType.setVar,
+ commandRaw: "setVar",
+ content: "",
+ args: [
+ { key: "#variableName", value: "a" },
+ { key: "#expression", value: "人物名称" }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("setVar-4", async () => {
+ expectContainEqual(`setVar:a=random();`,
+ [{
+ command: commandType.setVar,
+ commandRaw: "setVar",
+ content: "",
+ args: [
+ { key: "#variableName", value: "a" },
+ { key: "#expression", value: "random()" }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("setVar-5", async () => {
+ expectContainEqual(`setVar:a=5+a*5;`,
+ [{
+ command: commandType.setVar,
+ commandRaw: "setVar",
+ content: "",
+ args: [
+ { key: "#variableName", value: "a" },
+ { key: "#expression", value: "5+a*5" }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("setVar-6", async () => {
+ expectContainEqual(`setVar:a=5+a*5;`,
+ [{
+ command: commandType.setVar,
+ commandRaw: "setVar",
+ content: "",
+ args: [
+ { key: "#variableName", value: "a" },
+ { key: "#expression", value: "5+a*5" }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("setVar-7", async () => {
+ expectContainEqual(`setVar:a=1;
+setVar:b=a+1;`, [
+ {
+ command: commandType.setVar,
+ commandRaw: "setVar",
+ content: "",
+ args: [
+ { key: "#variableName", value: "a" },
+ { key: "#expression", value: 1 }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ },
+ {
+ command: commandType.setVar,
+ commandRaw: "setVar",
+ content: "",
+ args: [
+ { key: "#variableName", value: "b" },
+ { key: "#expression", value: "a+1" }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ },
+ ]);
+});
+
+test("setVar-8", async () => {
+ expectContainEqual(`setVar:a=1 -global;`,
+ [{
+ command: commandType.setVar,
+ commandRaw: "setVar",
+ content: "",
+ args: [
+ { key: "#variableName", value: "a" },
+ { key: "#expression", value: 1 },
+ { key: "global", value: true }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
+
+test("setVar-9", async () => {
+ expectContainEqual(`setVar:a=($stage.bgm.volume) -global;`,
+ [{
+ command: commandType.setVar,
+ commandRaw: "setVar",
+ content: "",
+ args: [
+ { key: "#variableName", value: "a" },
+ { key: "#internalExpression", value: "$stage.bgm.volume" },
+ { key: "global", value: true }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/showVars.test.ts b/packages/parser/test/showVars.test.ts
new file mode 100644
index 000000000..d7d05fa16
--- /dev/null
+++ b/packages/parser/test/showVars.test.ts
@@ -0,0 +1,16 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual } from './util';
+
+test("showVars-1", async () => {
+ expectContainEqual(`showVars;`,
+ [{
+ command: commandType.showVars,
+ commandRaw: "showVars",
+ content: "",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ }]
+ );
+});
diff --git a/packages/parser/test/test-resources/choose.txt b/packages/parser/test/test-resources/choose.txt
new file mode 100644
index 000000000..adcacf08f
--- /dev/null
+++ b/packages/parser/test/test-resources/choose.txt
@@ -0,0 +1,5 @@
+choose:;
+choose:|;
+choose:fff:;
+choose::ff;
+choose:|:ff;
diff --git a/packages/parser/test/test-resources/line-break.txt b/packages/parser/test/test-resources/line-break.txt
new file mode 100644
index 000000000..750e21cae
--- /dev/null
+++ b/packages/parser/test/test-resources/line-break.txt
@@ -0,0 +1,38 @@
+changeFigure:a.png -left
+ -next
+
+
+ -id=id1
+
+intro:aaa
+ |bbb|ccc
+ |ddd
+ -next;
+
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好
+|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left
+ -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。
+ -v1.wav;
+changeFigure:stand2.png
+ -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back
+ -target=fig-left
+
+
+ -next;
+
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。
+ -v5.wav;
diff --git a/packages/parser/test/test-resources/long-script.txt b/packages/parser/test/test-resources/long-script.txt
new file mode 100644
index 000000000..548a33a72
--- /dev/null
+++ b/packages/parser/test/test-resources/long-script.txt
@@ -0,0 +1,3672 @@
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
diff --git a/packages/parser/test/test-resources/start.txt b/packages/parser/test/test-resources/start.txt
new file mode 100644
index 000000000..a1240cea9
--- /dev/null
+++ b/packages/parser/test/test-resources/start.txt
@@ -0,0 +1,105 @@
+; 初始场景,以及特效演示
+changeBg:c4.jpg -next;
+unlockCg:c4.jpg -name=街前; 解锁部分CG
+unlockCg:xgmain.jpeg -name=星光咖啡馆与死神之蝶;
+unlockBgm:s_Title.mp3 -name=Smiling-Swinging!!;
+WebGAL:特效系统是3.9.2版本新引入的系统,你想要看特效的演示吗?;
+choose:观看特效演示:demo|我不要看特效,直接来吧!:toStart;
+label:demo;
+pixiInit;
+pixiPerform:rain;
+WebGAL:现在展示的是下雨的特效。;
+pixiInit;
+pixiPerform:snow;
+WebGAL:现在展示的是下雪的特效。;
+setTextbox:hide;
+pixiInit;
+setTextbox:on -next;
+WebGAL:特效的演示已经结束,现在我们正式开始吧!;
+
+; 正式场景
+label:toStart;
+playVideo:OP.mp4;
+changeBg:c3.jpg -next;
+unlockCg:c3.jpg -name=cafe;
+changeFigure:m2.png -left -next;
+bgm:cb1.mp3;
+unlockBgm:cb1.mp3 -name=ひとすじの光明;
+setAnimation:enter-from-left -target=fig-left -next;
+米咖多:蛋包饭是栞那做的,但红茶是夏目泡的。 -v1.ogg;
+昂晴:......;
+顺便问一下,你是打算做什么的?;
+changeFigure:m1.png -left -next;
+米咖多:就是倒饮料。然后咖啡和红茶的冲泡方式我还是记住了的。 -v2.ogg;
+昂晴:这、这样么......;
+:这猫爪子真的能泡茶么。;
+拿得了水壶吗?就凭他那个肉球爪子......;
+难道这些也是凭借猫妖的奇特力量做到的吗?;
+changeFigure:none -left -next;
+changeFigure:k1.png -next;
+setAnimation:enter-from-bottom -target=fig-center -next;
+栞那:那么,你想先尝哪个? -v3.ogg;
+choose:品尝蛋包饭:dbf|品尝红茶:hc;
+
+; 栞那选项
+label:dbf;
+昂晴:总之,先确认下蛋包饭的味道吧;
+changeFigure:k2.png -next;
+栞那:明白了,交给我吧 -v4.ogg;
+changeFigure:none -next;
+changeFigure:m2.png -left -next;
+changeFigure:k3.png -right -next;
+setAnimation:enter-from-left -target=fig-left -next;
+setAnimation:enter-from-right -target=fig-right -next;
+栞那:那么米咖多先生,我去做一下试作品 -v5.ogg;
+米咖多:嗯。去吧 -v6.ogg;
+changeFigure:none -left -next;
+changeFigure:none -right -next;
+changeFigure:k4.png -next;
+setAnimation:enter-from-bottom -target=fig-center -next;
+栞那:那么高岭同学,我们移动到厨房吧 -v7.ogg;
+changeFigure:none -next;
+bgm:cb2.mp3;
+unlockBgm:cb2.mp3 -name=Tea Break;
+changeBg:c2.jpg;
+unlockCg:c2.jpg -name=厨房;
+changeFigure:k2.png -next;
+栞那:话不多说开始做吧 -v8.ogg;
+jumpLabel:end;
+
+; 夏目选项
+label:hc;
+changeFigure:none -next;
+changeFigure:m1.png -left -next;
+changeFigure:k1.png -right -next;
+setAnimation:enter-from-left -target=fig-left -next;
+setAnimation:enter-from-right -target=fig-right -next;
+米咖多:那么就是,夏目了吧 -v9.ogg;
+changeFigure:k6.png -right -next;
+栞那:她刚去休息,要不要我叫回来呢? -v10.ogg;
+昂晴:没事,我自己去吧;
+changeFigure:none -left -next;
+changeFigure:none -right -next;
+bgm:cb2.mp3;
+changeBg:c1.jpg -next;
+unlockCg:c1.jpg -name=休息室;
+:我先敲了敲门;
+miniAvatar:n1.png;
+夏目:哪位? -v11.ogg;
+昂晴:我是高岭,可以进去吗?;
+夏目:可以,没问题 -v12.ogg;
+昂晴:打搅了;
+miniAvatar:none;
+changeFigure:n4.png -next;
+昂晴:打搅你休息了;
+夏目:不用在意,怎么了? -v13.ogg;
+changeFigure:n2.png -next;
+:她并没有无精打采,比想象中精神多了;
+昂晴:问道拿手菜谱,就听说四季同学泡的红茶味道不错,我也想品尝一下;
+夏目:明白了,那我们回去吧 -v14.ogg;
+jumpLabel:end;
+
+; 结束场景
+label:end;
+changeFigure:none -next;
+WebGAL:基础演出的展示已经结束。;
diff --git a/packages/parser/test/test-resources/var.txt b/packages/parser/test/test-resources/var.txt
new file mode 100644
index 000000000..33c2f0a00
--- /dev/null
+++ b/packages/parser/test/test-resources/var.txt
@@ -0,0 +1,2 @@
+setVar:a=1;
+WebGAL:a=1? -when=a==1;
diff --git a/packages/parser/test/unlockBgm.test.ts b/packages/parser/test/unlockBgm.test.ts
new file mode 100644
index 000000000..99848b33b
--- /dev/null
+++ b/packages/parser/test/unlockBgm.test.ts
@@ -0,0 +1,28 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("unlockBgm-1", async () => {
+ expectContainEqual(`; // 解锁bgm并赋予名称
+unlockBgm:s_Title.mp3 -name=Smiling-Swinging!!!;`,
+ [
+ {
+ command: commandType.comment,
+ commandRaw: "comment",
+ content: " // 解锁bgm并赋予名称",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ },
+ {
+ command: commandType.unlockBgm,
+ commandRaw: "unlockBgm",
+ content: "s_Title.mp3",
+ args: [
+ { key: "name", value: "Smiling-Swinging!!!" },
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }
+ ]);
+});
diff --git a/packages/parser/test/unlockCg.test.ts b/packages/parser/test/unlockCg.test.ts
new file mode 100644
index 000000000..902f770a0
--- /dev/null
+++ b/packages/parser/test/unlockCg.test.ts
@@ -0,0 +1,29 @@
+import { test } from "vitest";
+import { commandType } from "../src/interface/sceneInterface";
+import { expectContainEqual, expectThrow } from './util';
+
+test("unlockCg-1", async () => {
+ expectContainEqual(`; // 解锁CG并赋予名称
+unlockCg:xgmain.jpeg -name=星光咖啡馆与死神之蝶 -series=1;`,
+ [
+ {
+ command: commandType.comment,
+ commandRaw: "comment",
+ content: " // 解锁CG并赋予名称",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ },
+ {
+ command: commandType.unlockCg,
+ commandRaw: "unlockCg",
+ content: "xgmain.jpeg",
+ args: [
+ { key: "name", value: "星光咖啡馆与死神之蝶" },
+ { key: "series", value: 1 }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ }
+ ]);
+});
diff --git a/packages/parser/test/util.ts b/packages/parser/test/util.ts
new file mode 100644
index 000000000..5e6900217
--- /dev/null
+++ b/packages/parser/test/util.ts
@@ -0,0 +1,39 @@
+
+import SceneParser, { parserSyntaxError } from "../src/index";
+import { ADD_NEXT_ARG_LIST, SCRIPT_CONFIG } from "../src/config/scriptConfig";
+import { expect } from "vitest";
+import { IError, ISentence } from "../src/interface/sceneInterface";
+import { chai } from 'vitest';
+
+chai.config.truncateThreshold = 100000;
+
+export function expectContainEqual(rawScene: string, expectedSentenceItem: Array, errors: Array = []) {
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const result = parser.parse(rawScene, "start", "/start.txt");
+
+ expect(result.sentenceList).toEqual(expectedSentenceItem);
+ // if (Array.isArray(expectedSentenceItem)) {
+ // expectedSentenceItem.forEach((s) => expect(result.sentenceList).toContainEqual(s));
+ // } else {
+ // expect(result.sentenceList).toEqual(expectedSentenceItem);
+ // }
+
+ if (errors) {
+ for (const error of errors) {
+ expect(result.errors).toContainEqual(error);
+ }
+ }
+}
+
+export function expectThrow(rawScene: string) {
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ expect(() => parser.parse(rawScene, "start", "/start.txt")).toThrow(parserSyntaxError);
+}
\ No newline at end of file
diff --git a/packages/parser/tsconfig.json b/packages/parser/tsconfig.json
new file mode 100644
index 000000000..5fb839743
--- /dev/null
+++ b/packages/parser/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "declaration": true,
+ "declarationDir": "build/types",
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "allowJs": false,
+ "skipLibCheck": false,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "noImplicitAny": false,
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": [
+ "src"
+ ],
+ "references": [{ "path": "./tsconfig.node.json"}]
+}
diff --git a/packages/parser/tsconfig.node.json b/packages/parser/tsconfig.node.json
new file mode 100644
index 000000000..fde6cf327
--- /dev/null
+++ b/packages/parser/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "module": "esnext",
+ "moduleResolution": "node"
+ },
+ "include": [
+ "vite.config.ts"
+ ]
+}
diff --git a/packages/parser_legacy/.babelrc b/packages/parser_legacy/.babelrc
new file mode 100644
index 000000000..cc4476abb
--- /dev/null
+++ b/packages/parser_legacy/.babelrc
@@ -0,0 +1,14 @@
+{
+ "presets": [
+ [
+ "@babel/preset-env",
+ {
+ "useBuiltIns": "entry",
+ "corejs": "3.6.4",
+ "modules": false
+ },
+ "@babel/preset-typescript"
+ ]
+ ],
+ "exclude": "node_modules/**"
+}
diff --git a/packages/parser_legacy/.gitignore b/packages/parser_legacy/.gitignore
new file mode 100644
index 000000000..549164b2f
--- /dev/null
+++ b/packages/parser_legacy/.gitignore
@@ -0,0 +1,4 @@
+coverage
+node_modules
+.idea
+.vscode
diff --git a/packages/parser_legacy/.npmignore b/packages/parser_legacy/.npmignore
new file mode 100644
index 000000000..7bcd9b489
--- /dev/null
+++ b/packages/parser_legacy/.npmignore
@@ -0,0 +1,6 @@
+coverage
+test
+.babelrc
+rollup.config.js
+tsconfig.json
+tsconfig.node.json
diff --git a/packages/parser_legacy/.prettierrc b/packages/parser_legacy/.prettierrc
new file mode 100644
index 000000000..33f6d8eaf
--- /dev/null
+++ b/packages/parser_legacy/.prettierrc
@@ -0,0 +1,19 @@
+{
+ "printWidth": 80,
+ "tabWidth": 2,
+ "useTabs": false,
+ "semi": true,
+ "singleQuote": true,
+ "quoteProps": "as-needed",
+ "jsxSingleQuote": false,
+ "trailingComma": "all",
+ "bracketSpacing": true,
+ "bracketSameLine": false,
+ "arrowParens": "always",
+ "requirePragma": false,
+ "insertPragma": false,
+ "proseWrap": "preserve",
+ "htmlWhitespaceSensitivity": "css",
+ "endOfLine": "lf",
+ "embeddedLanguageFormatting": "auto"
+}
diff --git a/packages/parser_legacy/package.json b/packages/parser_legacy/package.json
new file mode 100644
index 000000000..28c1d49d1
--- /dev/null
+++ b/packages/parser_legacy/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "webgal-parser-legacy",
+ "version": "4.5.2",
+ "description": "WebGAL script parser",
+ "scripts": {
+ "test": "vitest",
+ "coverage": "vitest run --coverage",
+ "build": "rimraf -rf ./build && rollup --config",
+ "build-ci": "rollup --config",
+ "debug": "tsx test/debug.ts",
+ "debug-scss-parser": "tsx test/debugCssParser.ts"
+ },
+ "types": "./build/types/index.d.ts",
+ "module": "./build/es/index.js",
+ "main": "./build/cjs/index.cjs",
+ "author": "Mahiru ",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "chevrotain": "^10.5.0",
+ "cloudlogjs": "^1.0.11",
+ "lodash": "^4.17.21",
+ "tsx": "^3.12.7"
+ },
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^23.0.2",
+ "@rollup/plugin-json": "^5.0.1",
+ "@rollup/plugin-node-resolve": "^15.0.1",
+ "@rollup/plugin-typescript": "^9.0.2",
+ "@types/lodash": "^4.14.189",
+ "@types/node": "^18.14.0",
+ "@vitest/coverage-c8": "^0.28.5",
+ "rollup": "^3.3.0",
+ "rollup-plugin-babel": "^4.4.0",
+ "rollup-plugin-terser": "^7.0.2",
+ "rollup-plugin-typescript2": "^0.34.1",
+ "ts-node": "^10.9.1",
+ "tslib": "^2.4.1",
+ "typescript": "^4.9.3",
+ "vitest": "^0.28.5"
+ },
+ "type": "module"
+}
diff --git a/packages/parser_legacy/rollup.config.js b/packages/parser_legacy/rollup.config.js
new file mode 100644
index 000000000..787b18bc7
--- /dev/null
+++ b/packages/parser_legacy/rollup.config.js
@@ -0,0 +1,69 @@
+import typescript from "rollup-plugin-typescript2";
+import resolve from "@rollup/plugin-node-resolve";
+import commonjs from "@rollup/plugin-commonjs";
+
+const mode = process.env.MODE ?? 'prod';
+const isProd = mode === "prod";
+
+export default [
+ {
+ input: `./src/index.ts`,
+ output:
+ {
+ file: "./build/es/index.js",
+ format: "es",
+ sourcemap: !isProd
+ },
+ plugins: [
+ resolve(), commonjs(), typescript({
+ useTsconfigDeclarationDir: true,
+ tsconfigOverride: {
+ compilerOptions: {
+ sourceMap: !isProd,
+ declarationDir: "build/es"
+ }, include: ["src"]
+ }
+ })]
+ }, {
+ input: `./src/index.ts`,
+ output: [
+ {
+ file: "./build/cjs/index.cjs",
+ exports: "named",
+ format: "cjs",
+ sourcemap: !isProd
+ },
+ ],
+ plugins: [
+ resolve(), commonjs(), typescript({
+ useTsconfigDeclarationDir: true,
+ tsconfigOverride: {
+ compilerOptions: {
+ sourceMap: !isProd,
+ declarationDir: "build/cjs"
+ }, include: ["src"]
+ }
+ })],
+ },
+ {
+ input: `./src/index.ts`,
+ output: [
+ {
+ file: "./build/umd/index.global.js",
+ name: 'webgalParser',
+ format: 'iife',
+ sourcemap: !isProd
+ },
+ ],
+ plugins: [
+ resolve(), commonjs(), typescript({
+ useTsconfigDeclarationDir: true,
+ tsconfigOverride: {
+ compilerOptions: {
+ sourceMap: !isProd,
+ declarationDir: "build/types"
+ }, include: ["src"]
+ }
+ })],
+ }
+];
diff --git a/packages/parser_legacy/src/config/scriptConfig.ts b/packages/parser_legacy/src/config/scriptConfig.ts
new file mode 100644
index 000000000..c8463fd7b
--- /dev/null
+++ b/packages/parser_legacy/src/config/scriptConfig.ts
@@ -0,0 +1,50 @@
+import {commandType} from '../interface/sceneInterface';
+
+export const SCRIPT_CONFIG = [
+ { scriptString: 'intro', scriptType: commandType.intro },
+ { scriptString: 'changeBg', scriptType: commandType.changeBg },
+ { scriptString: 'changeFigure', scriptType: commandType.changeFigure },
+ { scriptString: 'miniAvatar', scriptType: commandType.miniAvatar },
+ { scriptString: 'changeScene', scriptType: commandType.changeScene },
+ { scriptString: 'choose', scriptType: commandType.choose },
+ { scriptString: 'end', scriptType: commandType.end },
+ { scriptString: 'bgm', scriptType: commandType.bgm },
+ { scriptString: 'playVideo', scriptType: commandType.video },
+ {
+ scriptString: 'setComplexAnimation',
+ scriptType: commandType.setComplexAnimation,
+ },
+ { scriptString: 'setFilter', scriptType: commandType.setFilter },
+ { scriptString: 'pixiInit', scriptType: commandType.pixiInit },
+ { scriptString: 'pixiPerform', scriptType: commandType.pixi },
+ { scriptString: 'label', scriptType: commandType.label },
+ { scriptString: 'jumpLabel', scriptType: commandType.jumpLabel },
+ { scriptString: 'setVar', scriptType: commandType.setVar },
+ { scriptString: 'callScene', scriptType: commandType.callScene },
+ { scriptString: 'showVars', scriptType: commandType.showVars },
+ { scriptString: 'unlockCg', scriptType: commandType.unlockCg },
+ { scriptString: 'unlockBgm', scriptType: commandType.unlockBgm },
+ { scriptString: 'say', scriptType: commandType.say },
+ { scriptString: 'filmMode', scriptType: commandType.filmMode },
+ { scriptString: 'callScene', scriptType: commandType.callScene },
+ { scriptString: 'setTextbox', scriptType: commandType.setTextbox },
+ { scriptString: 'setAnimation', scriptType: commandType.setAnimation },
+ { scriptString: 'playEffect', scriptType: commandType.playEffect },
+ { scriptString: 'applyStyle', scriptType: commandType.applyStyle },
+];
+export const ADD_NEXT_ARG_LIST = [
+ commandType.bgm,
+ commandType.pixi,
+ commandType.pixiInit,
+ commandType.label,
+ commandType.if,
+ commandType.miniAvatar,
+ commandType.setVar,
+ commandType.unlockBgm,
+ commandType.unlockCg,
+ commandType.filmMode,
+ commandType.playEffect,
+];
+
+export type ConfigMap = Map;
+export type ConfigItem = { scriptString: string; scriptType: commandType };
diff --git a/packages/parser_legacy/src/configParser/configParser.ts b/packages/parser_legacy/src/configParser/configParser.ts
new file mode 100644
index 000000000..cca9e66ec
--- /dev/null
+++ b/packages/parser_legacy/src/configParser/configParser.ts
@@ -0,0 +1,72 @@
+import { argsParser } from '../scriptParser/argsParser';
+
+interface IOptionItem {
+ key: string;
+ value: string | number | boolean;
+}
+interface IConfigItem {
+ command: string;
+ args: string[];
+ options: IOptionItem[];
+}
+
+export type WebgalConfig = IConfigItem[];
+
+function configLineParser(inputLine: string): IConfigItem {
+ const options: Array = [];
+ let command: string;
+
+ let newSentenceRaw = inputLine.split(';')[0];
+ if (newSentenceRaw === '') {
+ // 注释提前返回
+ return {
+ command: '',
+ args: [],
+ options: [],
+ };
+ }
+ // 截取命令
+ const getCommandResult = /\s*:\s*/.exec(newSentenceRaw);
+
+ // 没有command
+ if (getCommandResult === null) {
+ command = '';
+ } else {
+ command = newSentenceRaw.substring(0, getCommandResult.index);
+ // 划分命令区域和content区域
+ newSentenceRaw = newSentenceRaw.substring(
+ getCommandResult.index + 1,
+ newSentenceRaw.length,
+ );
+ }
+ // 截取 Options 区域
+ const getOptionsResult = / -/.exec(newSentenceRaw);
+ // 获取到参数
+ if (getOptionsResult) {
+ const optionsRaw = newSentenceRaw.substring(
+ getOptionsResult.index,
+ newSentenceRaw.length,
+ );
+ newSentenceRaw = newSentenceRaw.substring(0, getOptionsResult.index);
+ for (const e of argsParser(optionsRaw, (name, _) => {
+ return name;
+ })) {
+ options.push(e);
+ }
+ }
+ return {
+ command,
+ args: newSentenceRaw
+ .split('|')
+ .map((e) => e.trim())
+ .filter((e) => e !== ''),
+ options,
+ };
+}
+
+export function configParser(configText: string): WebgalConfig {
+ const configLines = configText.replaceAll(`\r`, '').split('\n');
+ return configLines
+ .map((e) => configLineParser(e))
+ .filter((e) => e.command !== '');
+}
diff --git a/packages/parser_legacy/src/index.ts b/packages/parser_legacy/src/index.ts
new file mode 100644
index 000000000..9bcdd4197
--- /dev/null
+++ b/packages/parser_legacy/src/index.ts
@@ -0,0 +1,78 @@
+import {
+ ADD_NEXT_ARG_LIST,
+ SCRIPT_CONFIG,
+ ConfigMap,
+ ConfigItem,
+} from './config/scriptConfig';
+import { configParser, WebgalConfig } from './configParser/configParser';
+import { fileType } from './interface/assets';
+import { IAsset } from './interface/sceneInterface';
+import { sceneParser } from './sceneParser';
+import {IWebGALStyleObj, scss2cssinjsParser} from "./styleParser";
+
+export default class SceneParser {
+ private readonly SCRIPT_CONFIG_MAP: ConfigMap;
+ constructor(
+ private readonly assetsPrefetcher: (assetList: IAsset[]) => void,
+ private readonly assetSetter: (
+ fileName: string,
+ assetType: fileType,
+ ) => string,
+ private readonly ADD_NEXT_ARG_LIST: number[],
+ SCRIPT_CONFIG_INPUT: ConfigItem[] | ConfigMap,
+ ) {
+ if (Array.isArray(SCRIPT_CONFIG_INPUT)) {
+ this.SCRIPT_CONFIG_MAP = new Map();
+ SCRIPT_CONFIG_INPUT.forEach((config) => {
+ this.SCRIPT_CONFIG_MAP.set(config.scriptString, config);
+ });
+ } else {
+ this.SCRIPT_CONFIG_MAP = SCRIPT_CONFIG_INPUT;
+ }
+ }
+ /**
+ * 解析场景
+ * @param rawScene 原始场景
+ * @param sceneName 场景名称
+ * @param sceneUrl 场景url
+ * @return 解析后的场景
+ */
+ parse(rawScene: string, sceneName: string, sceneUrl: string) {
+ return sceneParser(
+ rawScene,
+ sceneName,
+ sceneUrl,
+ this.assetsPrefetcher,
+ this.assetSetter,
+ this.ADD_NEXT_ARG_LIST,
+ this.SCRIPT_CONFIG_MAP,
+ );
+ }
+
+ parseConfig(configText: string) {
+ return configParser(configText);
+ }
+
+ stringifyConfig(config: WebgalConfig) {
+ return config.reduce(
+ (previousValue, curr) =>
+ previousValue +
+ `${curr.command}:${curr.args.join('|')}${
+ curr.options.length <= 0
+ ? ''
+ : curr.options.reduce(
+ (p, c) => p + ' -' + c.key + '=' + c.value,
+ '',
+ )
+ };\n`,
+ '',
+ );
+ }
+
+ parseScssToWebgalStyleObj(scssString: string): IWebGALStyleObj{
+ return scss2cssinjsParser(scssString);
+ }
+
+}
+
+export { ADD_NEXT_ARG_LIST, SCRIPT_CONFIG };
diff --git a/packages/parser_legacy/src/interface/assets.ts b/packages/parser_legacy/src/interface/assets.ts
new file mode 100644
index 000000000..0fb0e2cfa
--- /dev/null
+++ b/packages/parser_legacy/src/interface/assets.ts
@@ -0,0 +1,12 @@
+/**
+ * 内置资源类型的枚举
+ */
+export enum fileType {
+ background,
+ bgm,
+ figure,
+ scene,
+ tex,
+ vocal,
+ video,
+}
diff --git a/packages/parser_legacy/src/interface/runtimeInterface.ts b/packages/parser_legacy/src/interface/runtimeInterface.ts
new file mode 100644
index 000000000..d10f30fbd
--- /dev/null
+++ b/packages/parser_legacy/src/interface/runtimeInterface.ts
@@ -0,0 +1,9 @@
+/**
+ * 子场景结束后回到父场景的入口
+ * @interface sceneEntry
+ */
+export interface sceneEntry {
+ sceneName: string; // 场景名称
+ sceneUrl: string; // 场景url
+ continueLine: number; // 继续原场景的行号
+}
diff --git a/packages/parser_legacy/src/interface/sceneInterface.ts b/packages/parser_legacy/src/interface/sceneInterface.ts
new file mode 100644
index 000000000..e5419af3b
--- /dev/null
+++ b/packages/parser_legacy/src/interface/sceneInterface.ts
@@ -0,0 +1,105 @@
+/**
+ * 语句类型
+ */
+import { sceneEntry } from './runtimeInterface';
+import { fileType } from './assets';
+
+export enum commandType {
+ say, // 对话
+ changeBg, // 更改背景
+ changeFigure, // 更改立绘
+ bgm, // 更改背景音乐
+ video, // 播放视频
+ pixi, // pixi演出
+ pixiInit, // pixi初始化
+ intro, // 黑屏文字演示
+ miniAvatar, // 小头像
+ changeScene, // 切换场景
+ choose, // 分支选择
+ end, // 结束游戏
+ setComplexAnimation, // 动画演出
+ setFilter, // 设置效果
+ label, // 标签
+ jumpLabel, // 跳转标签
+ chooseLabel, // 选择标签
+ setVar, // 设置变量
+ if, // 条件跳转
+ callScene, // 调用场景
+ showVars,
+ unlockCg,
+ unlockBgm,
+ filmMode,
+ setTextbox,
+ setAnimation,
+ playEffect,
+ setTempAnimation,
+ comment,
+ setTransform,
+ setTransition,
+ getUserInput,
+ applyStyle
+}
+
+/**
+ * 单个参数接口
+ * @interface arg
+ */
+export interface arg {
+ key: string; // 参数键
+ value: string | boolean | number; // 参数值
+}
+
+/**
+ * 资源接口
+ * @interface IAsset
+ */
+export interface IAsset {
+ name: string; // 资源名称
+ type: fileType; // 资源类型
+ url: string; // 资源url
+ lineNumber: number; // 触发资源语句的行号
+}
+
+/**
+ * 单条语句接口
+ * @interface ISentence
+ */
+export interface ISentence {
+ command: commandType; // 语句类型
+ commandRaw: string; // 命令的原始内容,方便调试
+ content: string; // 语句内容
+ args: Array; // 参数列表
+ sentenceAssets: Array; // 语句携带的资源列表
+ subScene: Array; // 语句包含子场景列表
+}
+
+/**
+ * 场景接口
+ * @interface IScene
+ */
+export interface IScene {
+ sceneName: string; // 场景名称
+ sceneUrl: string; // 场景url
+ sentenceList: Array; // 语句列表
+ assetsList: Array; // 资源列表
+ subSceneList: Array; // 子场景的url列表
+}
+
+/**
+ * 当前的场景数据
+ * @interface ISceneData
+ */
+export interface ISceneData {
+ currentSentenceId: number; // 当前语句ID
+ sceneStack: Array; // 场景栈
+ currentScene: IScene; // 当前场景数据
+}
+
+/**
+ * 处理后的命令接口
+ * @interface parsedCommand
+ */
+export interface parsedCommand {
+ type: commandType;
+ additionalArgs: Array;
+}
diff --git a/packages/parser_legacy/src/parser4/index.ts b/packages/parser_legacy/src/parser4/index.ts
new file mode 100644
index 000000000..3456958b9
--- /dev/null
+++ b/packages/parser_legacy/src/parser4/index.ts
@@ -0,0 +1,158 @@
+// (function jsonGrammarOnlyExample() {
+// // ----------------- Lexer -----------------
+// const createToken = chevrotain.createToken;
+// const Lexer = chevrotain.Lexer;
+//
+// const True = createToken({ name: "True", pattern: /true/ });
+// const False = createToken({ name: "False", pattern: /false/ });
+// const LCurly = createToken({ name: "LCurly", pattern: /{/ });
+// const RCurly = createToken({ name: "RCurly", pattern: /}/ });
+// const LSquare = createToken({ name: "LSquare", pattern: /\[/ });
+// const RSquare = createToken({ name: "RSquare", pattern: /]/ });
+// const Comma = createToken({ name: "Comma", pattern: /,/ });
+// const Colon = createToken({ name: "Colon", pattern: /:/ });
+// const Lf = createToken({ name: "LF", pattern: /\n/ });
+// const Semi = createToken({ name: ";", pattern: /;/ });
+// const Command = createToken({ name: "Command", pattern: /Command/ });
+// const ArgS = createToken({ name: " -", pattern: / -/ });
+// const StringLiteral = createToken({
+// name: "StringLiteral", pattern: /(?!(?:true|false|Command)\b)\w+/
+// });
+// const NumberLiteral = createToken({
+// name: "NumberLiteral", pattern: /-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/
+// });
+//
+// const Equal = createToken({
+// name: "=",
+// pattern: /=/
+// });
+//
+// const webgalTokens = [NumberLiteral, StringLiteral, RCurly, LCurly,
+// LSquare, RSquare, Comma, Colon, True, False, Equal, Command, ArgS, Lf, Semi];
+//
+// const WebGalLexer = new Lexer(webgalTokens, {
+// // Less position info tracked, reduces verbosity of the playground output.
+// positionTracking: "onlyStart"
+// });
+//
+// // Labels only affect error messages and Diagrams.
+// LCurly.LABEL = "'{'";
+// RCurly.LABEL = "'}'";
+// LSquare.LABEL = "'['";
+// RSquare.LABEL = "']'";
+// Comma.LABEL = "','";
+// Colon.LABEL = "':'";
+//
+//
+// // ----------------- parser -----------------
+// const CstParser = chevrotain.CstParser;
+//
+// class webgalScriptParser extends CstParser {
+// constructor() {
+// super(webgalTokens, {
+// recoveryEnabled: true
+// });
+//
+// const $ = this;
+//
+// $.RULE("scene", () => {
+// $.MANY_SEP({
+// SEP: Lf, DEF: () => {
+// $.SUBRULE($.sentence);
+// }
+// });
+// });
+//
+// $.RULE("sentence", () => {
+// $.OR([
+// { ALT: () => $.SUBRULE($.commonSentence) },
+// { ALT: () => $.SUBRULE($.comment) }
+// ]);
+// });
+//
+// $.RULE("commonSentence", () => {
+// $.OR([
+// { ALT: () => $.SUBRULE($.commandSentence) },
+// { ALT: () => $.SUBRULE($.dialog) }
+// ]);
+// $.OPTION(() => {
+// $.CONSUME(Semi);
+// });
+// });
+//
+// $.RULE("comment", () => {
+// $.CONSUME(Semi);
+// $.CONSUME(StringLiteral);
+// });
+//
+// $.RULE("dialog", () => {
+// $.CONSUME(StringLiteral);
+//
+// });
+//
+//
+// $.RULE("commandSentence", () => {
+// $.CONSUME(Command);
+// $.CONSUME(Colon);
+// $.CONSUME(StringLiteral);
+// $.OPTION(() => {
+// $.SUBRULE($.args);
+// });
+// });
+//
+// $.RULE("args", () => {
+// $.CONSUME(ArgS);
+// $.MANY_SEP({
+// SEP: ArgS, DEF: () => {
+// $.SUBRULE($.arg);
+// }
+// });
+// });
+//
+// $.RULE("arg", () => {
+// $.CONSUME(StringLiteral);
+// $.CONSUME(Equal);
+// $.SUBRULE($.argv);
+// });
+//
+// $.RULE("argv", () => {
+// $.OR([
+// {
+// ALT: () => {
+// this.CONSUME(StringLiteral);
+// }
+// },
+// {
+// ALT: () => {
+// this.CONSUME(NumberLiteral);
+// }
+// },
+// {
+// ALT: () => {
+// this.CONSUME(True);
+// }
+// },
+// {
+// ALT: () => {
+// this.CONSUME(False);
+// }
+// }]);
+// });
+//
+// // very important to call this after all the rules have been setup.
+// // otherwise the parser may not work correctly as it will lack information
+// // derived from the self analysis.
+// this.performSelfAnalysis();
+// }
+//
+// }
+//
+//
+// // for the playground to work the returned object must contain these fields
+// return {
+// lexer: WebGalLexer,
+// parser: webgalScriptParser,
+// defaultRule: "scene"
+// };
+// }());
+export const parser4 = 'unreleased';
diff --git a/packages/parser_legacy/src/sceneParser.ts b/packages/parser_legacy/src/sceneParser.ts
new file mode 100644
index 000000000..9d2a72b83
--- /dev/null
+++ b/packages/parser_legacy/src/sceneParser.ts
@@ -0,0 +1,66 @@
+import {
+ commandType,
+ IAsset,
+ IScene,
+ ISentence,
+} from './interface/sceneInterface';
+import { scriptParser } from './scriptParser/scriptParser';
+import uniqWith from 'lodash/uniqWith';
+import { fileType } from './interface/assets';
+import { ConfigMap } from './config/scriptConfig';
+
+/**
+ * 场景解析器
+ * @param rawScene 原始场景
+ * @param sceneName 场景名称
+ * @param sceneUrl 场景url
+ * @param assetsPrefetcher
+ * @param assetSetter
+ * @param ADD_NEXT_ARG_LIST
+ * @param SCRIPT_CONFIG_MAP
+ * @return {IScene} 解析后的场景
+ */
+export const sceneParser = (
+ rawScene: string,
+ sceneName: string,
+ sceneUrl: string,
+ assetsPrefetcher: (assetList: Array) => void,
+ assetSetter: (fileName: string, assetType: fileType) => string,
+ ADD_NEXT_ARG_LIST: commandType[],
+ SCRIPT_CONFIG_MAP: ConfigMap,
+): IScene => {
+ const rawSentenceList = rawScene.split('\n'); // 原始句子列表
+
+ // 去分号留到后面去做了,现在注释要单独处理
+ const rawSentenceListWithoutEmpty = rawSentenceList;
+ // .map((sentence) => sentence.split(";")[0])
+ // .filter((sentence) => sentence.trim() !== "");
+ let assetsList: Array = []; // 场景资源列表
+ let subSceneList: Array = []; // 子场景列表
+ const sentenceList: Array = rawSentenceListWithoutEmpty.map(
+ (sentence) => {
+ const returnSentence: ISentence = scriptParser(
+ sentence,
+ assetSetter,
+ ADD_NEXT_ARG_LIST,
+ SCRIPT_CONFIG_MAP,
+ );
+ // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList
+ assetsList = [...assetsList, ...returnSentence.sentenceAssets];
+ subSceneList = [...subSceneList, ...returnSentence.subScene];
+ return returnSentence;
+ },
+ );
+
+ // 开始资源的预加载
+ assetsList = uniqWith(assetsList); // 去重
+ assetsPrefetcher(assetsList);
+
+ return {
+ sceneName: sceneName, // 场景名称
+ sceneUrl: sceneUrl,
+ sentenceList: sentenceList, // 语句列表
+ assetsList: assetsList, // 资源列表
+ subSceneList: subSceneList, // 子场景列表
+ };
+};
diff --git a/packages/parser_legacy/src/scriptParser/argsParser.ts b/packages/parser_legacy/src/scriptParser/argsParser.ts
new file mode 100644
index 000000000..9cff77451
--- /dev/null
+++ b/packages/parser_legacy/src/scriptParser/argsParser.ts
@@ -0,0 +1,71 @@
+import { arg } from '../interface/sceneInterface';
+import { fileType } from '../interface/assets';
+
+/**
+ * 参数解析器
+ * @param argsRaw 原始参数字符串
+ * @param assetSetter
+ * @return {Array} 解析后的参数列表
+ */
+export function argsParser(
+ argsRaw: string,
+ assetSetter: (fileName: string, assetType: fileType) => string,
+): Array {
+ const returnArrayList: Array = [];
+ // 处理参数
+ // 不要去空格
+ let newArgsRaw = argsRaw.replace(/ /g, ' ');
+ // 分割参数列表
+ let rawArgsList: Array = newArgsRaw.split(' -');
+ // 去除空字符串
+ rawArgsList = rawArgsList.filter((e) => {
+ return e !== '';
+ });
+ rawArgsList.forEach((e) => {
+ const equalSignIndex = e.indexOf('=');
+ let argName = e.slice(0, equalSignIndex);
+ let argValue: string | undefined = e.slice(equalSignIndex + 1);
+ if (equalSignIndex < 0) {
+ argName = e;
+ argValue = undefined;
+ }
+ // 判断是不是语音参数
+ if (argName.toLowerCase().match(/.ogg|.mp3|.wav/)) {
+ returnArrayList.push({
+ key: 'vocal',
+ value: assetSetter(e, fileType.vocal),
+ });
+ } else {
+ // 判断是不是省略参数
+ if (argValue === undefined) {
+ returnArrayList.push({
+ key: argName,
+ value: true,
+ });
+ } else {
+ // 是字符串描述的布尔值
+ if (argValue === 'true' || argValue === 'false') {
+ returnArrayList.push({
+ key: argName,
+ value: argValue === 'true',
+ });
+ } else {
+ // 是数字
+ if (!isNaN(Number(argValue))) {
+ returnArrayList.push({
+ key: argName,
+ value: Number(argValue),
+ });
+ } else {
+ // 是普通参数
+ returnArrayList.push({
+ key: argName,
+ value: argValue,
+ });
+ }
+ }
+ }
+ }
+ });
+ return returnArrayList;
+}
diff --git a/packages/parser_legacy/src/scriptParser/assetsScanner.ts b/packages/parser_legacy/src/scriptParser/assetsScanner.ts
new file mode 100644
index 000000000..cdff61be5
--- /dev/null
+++ b/packages/parser_legacy/src/scriptParser/assetsScanner.ts
@@ -0,0 +1,76 @@
+import { arg, commandType, IAsset } from '../interface/sceneInterface';
+import { fileType } from '../interface/assets';
+
+/**
+ * 根据语句类型、语句内容、参数列表,扫描该语句可能携带的资源
+ * @param command 语句类型
+ * @param content 语句内容
+ * @param args 参数列表
+ * @return {Array} 语句携带的参数列表
+ */
+export const assetsScanner = (
+ command: commandType,
+ content: string,
+ args: Array,
+): Array => {
+ let hasVocalArg = false;
+ const returnAssetsList: Array = [];
+ if (command === commandType.say) {
+ args.forEach((e) => {
+ if (e.key === 'vocal') {
+ hasVocalArg = true;
+ returnAssetsList.push({
+ name: e.value as string,
+ url: e.value as string,
+ lineNumber: 0,
+ type: fileType.vocal,
+ });
+ }
+ });
+ }
+ if (content === 'none' || content === '') {
+ return returnAssetsList;
+ }
+ // 处理语句携带的资源
+ if (command === commandType.changeBg) {
+ returnAssetsList.push({
+ name: content,
+ url: content,
+ lineNumber: 0,
+ type: fileType.background,
+ });
+ }
+ if (command === commandType.changeFigure) {
+ returnAssetsList.push({
+ name: content,
+ url: content,
+ lineNumber: 0,
+ type: fileType.figure,
+ });
+ }
+ if (command === commandType.miniAvatar) {
+ returnAssetsList.push({
+ name: content,
+ url: content,
+ lineNumber: 0,
+ type: fileType.figure,
+ });
+ }
+ if (command === commandType.video) {
+ returnAssetsList.push({
+ name: content,
+ url: content,
+ lineNumber: 0,
+ type: fileType.video,
+ });
+ }
+ if (command === commandType.bgm) {
+ returnAssetsList.push({
+ name: content,
+ url: content,
+ lineNumber: 0,
+ type: fileType.bgm,
+ });
+ }
+ return returnAssetsList;
+};
diff --git a/packages/parser_legacy/src/scriptParser/commandParser.ts b/packages/parser_legacy/src/scriptParser/commandParser.ts
new file mode 100644
index 000000000..b08655881
--- /dev/null
+++ b/packages/parser_legacy/src/scriptParser/commandParser.ts
@@ -0,0 +1,65 @@
+import { ConfigMap } from '../config/scriptConfig';
+import { commandType, parsedCommand } from '../interface/sceneInterface';
+
+/**
+ * 处理命令
+ * @param commandRaw
+ * @param ADD_NEXT_ARG_LIST
+ * @param SCRIPT_CONFIG_MAP
+ * @return {parsedCommand} 处理后的命令
+ */
+export const commandParser = (
+ commandRaw: string,
+ ADD_NEXT_ARG_LIST: commandType[],
+ SCRIPT_CONFIG_MAP: ConfigMap,
+): parsedCommand => {
+ let returnCommand: parsedCommand = {
+ type: commandType.say, // 默认是say
+ additionalArgs: [],
+ };
+ // 开始处理命令内容
+ const type: commandType = getCommandType(
+ commandRaw,
+ ADD_NEXT_ARG_LIST,
+ SCRIPT_CONFIG_MAP,
+ );
+ returnCommand.type = type;
+ // 如果是对话,加上额外的参数
+ if (type === commandType.say && commandRaw !== 'say') {
+ returnCommand.additionalArgs.push({
+ key: 'speaker',
+ value: commandRaw,
+ });
+ }
+ returnCommand = addNextArg(returnCommand, type, ADD_NEXT_ARG_LIST);
+ return returnCommand;
+};
+
+/**
+ * 根据command原始值判断是什么命令
+ * @param command command原始值
+ * @param ADD_NEXT_ARG_LIST
+ * @param SCRIPT_CONFIG_MAP
+ * @return {commandType} 得到的command类型
+ */
+function getCommandType(
+ command: string,
+ ADD_NEXT_ARG_LIST: commandType[],
+ SCRIPT_CONFIG_MAP: ConfigMap,
+): commandType {
+ return SCRIPT_CONFIG_MAP.get(command)?.scriptType ?? commandType.say;
+}
+
+function addNextArg(
+ commandToParse: parsedCommand,
+ thisCommandType: commandType,
+ ADD_NEXT_ARG_LIST: commandType[],
+) {
+ if (ADD_NEXT_ARG_LIST.includes(thisCommandType)) {
+ commandToParse.additionalArgs.push({
+ key: 'next',
+ value: true,
+ });
+ }
+ return commandToParse;
+}
diff --git a/packages/parser_legacy/src/scriptParser/contentParser.ts b/packages/parser_legacy/src/scriptParser/contentParser.ts
new file mode 100644
index 000000000..cbb6fd161
--- /dev/null
+++ b/packages/parser_legacy/src/scriptParser/contentParser.ts
@@ -0,0 +1,70 @@
+import { commandType } from '../interface/sceneInterface';
+import { fileType } from '../interface/assets';
+
+/**
+ * 解析语句内容的函数,主要作用是把文件名改为绝对地址或相对地址(根据使用情况而定)
+ * @param contentRaw 原始语句内容
+ * @param type 语句类型
+ * @param assetSetter
+ * @return {string} 解析后的语句内容
+ */
+export const contentParser = (
+ contentRaw: string,
+ type: commandType,
+ assetSetter: any,
+) => {
+ if (contentRaw === 'none' || contentRaw === '') {
+ return '';
+ }
+ switch (type) {
+ case commandType.playEffect:
+ return assetSetter(contentRaw, fileType.vocal);
+ case commandType.changeBg:
+ return assetSetter(contentRaw, fileType.background);
+ case commandType.changeFigure:
+ return assetSetter(contentRaw, fileType.figure);
+ case commandType.bgm:
+ return assetSetter(contentRaw, fileType.bgm);
+ case commandType.callScene:
+ return assetSetter(contentRaw, fileType.scene);
+ case commandType.changeScene:
+ return assetSetter(contentRaw, fileType.scene);
+ case commandType.miniAvatar:
+ return assetSetter(contentRaw, fileType.figure);
+ case commandType.video:
+ return assetSetter(contentRaw, fileType.video);
+ case commandType.choose:
+ return getChooseContent(contentRaw, assetSetter);
+ case commandType.unlockBgm:
+ return assetSetter(contentRaw, fileType.bgm);
+ case commandType.unlockCg:
+ return assetSetter(contentRaw, fileType.background);
+ default:
+ return contentRaw;
+ }
+};
+
+function getChooseContent(contentRaw: string, assetSetter: any): string {
+ const chooseList = contentRaw.split(/(? = [];
+ const chooseValueList: Array = [];
+ for (const e of chooseList) {
+ chooseKeyList.push(e.split(/(? {
+ if (e.match(/\./)) {
+ return assetSetter(e, fileType.scene);
+ } else {
+ return e;
+ }
+ });
+ let ret = '';
+ for (let i = 0; i < chooseKeyList.length; i++) {
+ if (i !== 0) {
+ ret = ret + '|';
+ }
+ ret = ret + `${chooseKeyList[i]}:${parsedChooseList[i]}`;
+ }
+ return ret;
+}
diff --git a/packages/parser_legacy/src/scriptParser/scriptParser.ts b/packages/parser_legacy/src/scriptParser/scriptParser.ts
new file mode 100644
index 000000000..10bbfe197
--- /dev/null
+++ b/packages/parser_legacy/src/scriptParser/scriptParser.ts
@@ -0,0 +1,114 @@
+import {
+ arg,
+ commandType,
+ IAsset,
+ ISentence,
+ parsedCommand,
+} from '../interface/sceneInterface';
+import { commandParser } from './commandParser';
+import { argsParser } from './argsParser';
+import { contentParser } from './contentParser';
+import { assetsScanner } from './assetsScanner';
+import { subSceneScanner } from './subSceneScanner';
+import { ConfigMap } from '../config/scriptConfig';
+
+/**
+ * 语句解析器
+ * @param sentenceRaw 原始语句
+ * @param assetSetter
+ * @param ADD_NEXT_ARG_LIST
+ * @param SCRIPT_CONFIG_MAP
+ */
+export const scriptParser = (
+ sentenceRaw: string,
+ assetSetter: any,
+ ADD_NEXT_ARG_LIST: commandType[],
+ SCRIPT_CONFIG_MAP: ConfigMap,
+): ISentence => {
+ let command: commandType; // 默认为对话
+ let content: string; // 语句内容
+ let subScene: Array; // 语句携带的子场景(可能没有)
+ const args: Array = []; // 语句参数列表
+ let sentenceAssets: Array; // 语句携带的资源列表
+ let parsedCommand: parsedCommand; // 解析后的命令
+ let commandRaw: string;
+
+ // 正式开始解析
+
+ // 去分号
+ let newSentenceRaw = sentenceRaw.split(/(?} 子场景列表
+ */
+import { commandType } from '../interface/sceneInterface';
+
+export const subSceneScanner = (
+ command: commandType,
+ content: string,
+): Array => {
+ const subSceneList: Array = [];
+ if (
+ command === commandType.changeScene ||
+ command === commandType.callScene
+ ) {
+ subSceneList.push(content);
+ }
+ if (command === commandType.choose) {
+ const chooseList = content.split('|');
+ const chooseValue = chooseList.map((e) => e.split(':')[1] ?? '');
+ chooseValue.forEach((e) => {
+ if (e.match(/\./)) {
+ subSceneList.push(e);
+ }
+ });
+ }
+ return subSceneList;
+};
diff --git a/packages/parser_legacy/src/styleParser/index.ts b/packages/parser_legacy/src/styleParser/index.ts
new file mode 100644
index 000000000..a97d0f621
--- /dev/null
+++ b/packages/parser_legacy/src/styleParser/index.ts
@@ -0,0 +1,39 @@
+export interface IWebGALStyleObj {
+ classNameStyles: Record;
+ others: string;
+}
+
+export function scss2cssinjsParser(scssString: string): IWebGALStyleObj {
+ const [classNameStyles, others] = parseCSS(scssString);
+ return {
+ classNameStyles,
+ others,
+ };
+}
+
+/**
+ * GPT 4 写的,临时用,以后要重构!!!
+ * TODO:用人类智能重构,要是用着一直没问题,也不是不可以 trust AI
+ * @param css
+ */
+function parseCSS(css: string): [Record, string] {
+ const result: Record = {};
+ let specialRules = '';
+ let matches;
+
+ // 使用非贪婪匹配,尝试正确处理任意层次的嵌套
+ const classRegex = /\.([^{\s]+)\s*{((?:[^{}]*|{[^}]*})*)}/g;
+ const specialRegex = /(@[^{]+{\s*(?:[^{}]*{[^}]*}[^{}]*)+\s*})/g;
+
+ while ((matches = classRegex.exec(css)) !== null) {
+ const key = matches[1];
+ const value = matches[2].trim().replace(/\s*;\s*/g, ';\n');
+ result[key] = value;
+ }
+
+ while ((matches = specialRegex.exec(css)) !== null) {
+ specialRules += matches[1].trim() + '\n';
+ }
+
+ return [result, specialRules.trim()];
+}
diff --git a/packages/parser_legacy/test/debug.ts b/packages/parser_legacy/test/debug.ts
new file mode 100644
index 000000000..cf58e6467
--- /dev/null
+++ b/packages/parser_legacy/test/debug.ts
@@ -0,0 +1,27 @@
+import * as fsp from "fs/promises";
+import SceneParser, {ADD_NEXT_ARG_LIST, SCRIPT_CONFIG} from "../src";
+
+
+async function debug() {
+ const sceneRaw = await fsp.readFile('test/test-resources/var.txt');
+ const sceneText = sceneRaw.toString();
+
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const result = parser.parse(sceneText, "var", "/var.txt");
+ console.log(result)
+ const configFesult = parser.parseConfig(`
+Game_name:欢迎使用WebGAL!;
+Game_key:0f86dstRf;
+Title_img:WebGAL_New_Enter_Image.png;
+Title_bgm:s_Title.mp3;
+Title_logos: 1.png | 2.png | Image Logo.png| -show -active=false -add=op! -count=3;This is a fake config, do not reference anything.
+ `)
+ console.log(configFesult)
+ console.log(parser.stringifyConfig(configFesult))
+}
+
+debug();
diff --git a/packages/parser_legacy/test/debugCssParser.ts b/packages/parser_legacy/test/debugCssParser.ts
new file mode 100644
index 000000000..039aac4b6
--- /dev/null
+++ b/packages/parser_legacy/test/debugCssParser.ts
@@ -0,0 +1,18 @@
+import * as fsp from "fs/promises";
+import SceneParser, {ADD_NEXT_ARG_LIST, SCRIPT_CONFIG} from "../src";
+
+
+async function debug() {
+ const sceneRaw = await fsp.readFile('test/test-resources/debug.scss');
+ const sceneText = sceneRaw.toString();
+
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const configFesult = parser.parseScssToWebgalStyleObj(sceneText)
+ console.log(configFesult)
+}
+
+debug();
diff --git a/packages/parser_legacy/test/parser.test.ts b/packages/parser_legacy/test/parser.test.ts
new file mode 100644
index 000000000..05161f6b4
--- /dev/null
+++ b/packages/parser_legacy/test/parser.test.ts
@@ -0,0 +1,196 @@
+import SceneParser from "../src/index";
+import { ADD_NEXT_ARG_LIST, SCRIPT_CONFIG } from "../src/config/scriptConfig";
+import { expect, test } from "vitest";
+import { commandType, ISentence } from "../src/interface/sceneInterface";
+import * as fsp from 'fs/promises';
+import { fileType } from "../src/interface/assets";
+
+test("label", async () => {
+
+ const sceneRaw = await fsp.readFile('test/test-resources/start.txt');
+ const sceneText = sceneRaw.toString();
+
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const result = parser.parse(sceneText, "start", "/start.txt");
+ const expectSentenceItem: ISentence = {
+ command: commandType.label,
+ commandRaw: "label",
+ content: "end",
+ args: [
+ { key: "next", value: true }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ };
+ expect(result.sentenceList).toContainEqual(expectSentenceItem);
+});
+
+test("args", async () => {
+
+ const sceneRaw = await fsp.readFile('test/test-resources/start.txt');
+ const sceneText = sceneRaw.toString();
+
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const result = parser.parse(sceneText, "start", "/start.txt");
+ const expectSentenceItem: ISentence = {
+ command: commandType.changeFigure,
+ commandRaw: "changeFigure",
+ content: "m2.png",
+ args: [
+ { key: "left", value: true },
+ { key: "next", value: true }
+ ],
+ sentenceAssets: [{ name: "m2.png", url: 'm2.png', type: fileType.figure, lineNumber: 0 }],
+ subScene: []
+ };
+ expect(result.sentenceList).toContainEqual(expectSentenceItem);
+});
+
+test("choose", async () => {
+
+ const sceneRaw = await fsp.readFile('test/test-resources/choose.txt');
+ const sceneText = sceneRaw.toString();
+
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const result = parser.parse(sceneText, "choose", "/choose.txt");
+ const expectSentenceItem: ISentence = {
+ command: commandType.choose,
+ commandRaw: "choose",
+ content: "",
+ args: [],
+ sentenceAssets: [],
+ subScene: []
+ };
+ expect(result.sentenceList).toContainEqual(expectSentenceItem);
+});
+
+test("long-script", async () => {
+
+ const sceneRaw = await fsp.readFile('test/test-resources/long-script.txt');
+ const sceneText = sceneRaw.toString();
+
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ console.log('line count:', sceneText.split('\n').length);
+ console.time('parse-time-consumed');
+ const result = parser.parse(sceneText, "start", "/start.txt");
+ console.timeEnd('parse-time-consumed');
+ const expectSentenceItem: ISentence = {
+ command: commandType.label,
+ commandRaw: "label",
+ content: "end",
+ args: [
+ { key: "next", value: true }
+ ],
+ sentenceAssets: [],
+ subScene: []
+ };
+ expect(result.sentenceList).toContainEqual(expectSentenceItem);
+});
+
+test("var", async () => {
+
+ const sceneRaw = await fsp.readFile('test/test-resources/var.txt');
+ const sceneText = sceneRaw.toString();
+
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const result = parser.parse(sceneText, "var", "/var.txt");
+ const expectSentenceItem: ISentence = {
+ command: commandType.say,
+ commandRaw: "WebGAL",
+ content: "a=1?",
+ args: [{ key: 'speaker', value: 'WebGAL' }, { key: 'when', value: "a==1" }],
+ sentenceAssets: [],
+ subScene: []
+ };
+ expect(result.sentenceList).toContainEqual(expectSentenceItem);
+});
+
+test("config", async () => {
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const configFesult = parser.parseConfig(`
+Game_name:欢迎使用WebGAL!;
+Game_key:0f86dstRf;
+Title_img:WebGAL_New_Enter_Image.png;
+Title_bgm:s_Title.mp3;
+Title_logos: 1.png | 2.png | Image Logo.png| -show -active=false -add=op! -count=3;This is a fake config, do not reference anything.
+ `);
+ expect(configFesult).toContainEqual({
+ command: 'Title_logos',
+ args: ['1.png', '2.png', 'Image Logo.png'],
+ options: [
+ { key: 'show', value: true },
+ { key: 'active', value: false },
+ { key: 'add', value: 'op!' },
+ { key: 'count', value: 3 },
+ ]
+ });
+});
+
+test("config-stringify", async () => {
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const configFesult = parser.parseConfig(`
+Game_name:欢迎使用WebGAL!;
+Game_key:0f86dstRf;
+Title_img:WebGAL_New_Enter_Image.png;
+Title_bgm:s_Title.mp3;
+Title_logos: 1.png | 2.png | Image Logo.png| -show -active=false -add=op! -count=3;This is a fake config, do not reference anything.
+ `);
+ const stringifyResult = parser.stringifyConfig(configFesult);
+ const configResult2 = parser.parseConfig(stringifyResult);
+ expect(configResult2).toContainEqual({
+ command: 'Title_logos',
+ args: ['1.png', '2.png', 'Image Logo.png'],
+ options: [
+ { key: 'show', value: true },
+ { key: 'active', value: false },
+ { key: 'add', value: 'op!' },
+ { key: 'count', value: 3 },
+ ]
+ });
+});
+
+
+test("say statement", async () => {
+ const parser = new SceneParser((assetList) => {
+ }, (fileName, assetType) => {
+ return fileName;
+ }, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+ const result = parser.parse(`say:123 -speaker=xx;`, 'test', 'test');
+ expect(result.sentenceList).toContainEqual({
+ command: commandType.say,
+ commandRaw: "say",
+ content: "123",
+ args: [{ key: 'speaker', value: 'xx' }],
+ sentenceAssets: [],
+ subScene: []
+ });
+});
diff --git a/packages/parser_legacy/test/test-resources/choose.txt b/packages/parser_legacy/test/test-resources/choose.txt
new file mode 100644
index 000000000..adcacf08f
--- /dev/null
+++ b/packages/parser_legacy/test/test-resources/choose.txt
@@ -0,0 +1,5 @@
+choose:;
+choose:|;
+choose:fff:;
+choose::ff;
+choose:|:ff;
diff --git a/packages/parser_legacy/test/test-resources/debug.scss b/packages/parser_legacy/test/test-resources/debug.scss
new file mode 100644
index 000000000..bffd61c0b
--- /dev/null
+++ b/packages/parser_legacy/test/test-resources/debug.scss
@@ -0,0 +1,64 @@
+.Title_main {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ z-index: 13;
+}
+
+.Title_buttonList {
+ font-family: "思源宋体", serif;
+ display: flex;
+ position: absolute;
+ left: 0;
+ min-width: 25%;
+ height: 100%;
+ justify-content: center;
+ align-items: flex-start;
+ flex-flow: column;
+ transition: background 0.75s;
+ padding-left: 120px;
+}
+
+.Title_button {
+ font-weight: bold;
+ text-align: center;
+ flex: 0 1 auto;
+ cursor: pointer;
+ padding: 1em 2em 1em 2em;
+ margin: 20px 0;
+ transition: all 0.33s;
+ background: rgba(255, 255, 255, 0.15);
+ backdrop-filter: blur(5px);
+ border-radius: 4px;
+ transform: skewX(-10deg);
+ background: linear-gradient(to right, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.1));
+
+ &:hover {
+ font-weight: bold;
+ text-shadow: 0 0 10px rgba(255, 255, 255, 1);
+ padding: 1em 6em 1em 3em;
+ }
+ &:active {
+ font-weight: bold;
+ }
+}
+
+.Title_button_text {
+ color: transparent;
+ background: linear-gradient(135deg, #fdfbfb 0%, #dcddde 100%);
+ -webkit-background-clip: text;
+ padding: 0 0.5em 0 0.5em;
+ font-size: 200%;
+ font-family: WebgalUI, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+ letter-spacing: 0.15em;
+}
+
+.Title_backup_background {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ z-index: 13;
+ background: linear-gradient(135deg, #fdfbfb 0%, #dcddde 100%);
+}
+
+
diff --git a/packages/parser_legacy/test/test-resources/long-script.txt b/packages/parser_legacy/test/test-resources/long-script.txt
new file mode 100644
index 000000000..548a33a72
--- /dev/null
+++ b/packages/parser_legacy/test/test-resources/long-script.txt
@@ -0,0 +1,3672 @@
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
+; WebGAL 引擎会默认读取 start.txt 作为初始场景,因此请不要删除,并在初始场景内跳转到其他场景
+bgm:s_Title.mp3;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 WebGAL 的世界;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -next;
+setAnimation:enter-from-left -target=fig-left -next;
+WebGAL:欢迎使用 WebGAL!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+WebGAL 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 WebGAL 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+WebGAL 引擎也具有动画系统和特效系统,使用 WebGAL 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+你可以通过以下两个分支了解 WebGAL 的更多故事。 -v8.wav;
+pixiInit;
+choose:WebGAL 发展历程:choose1|WebGAL 冷知识:choose2;
+
+;分支1
+label:choose1;
+WebGAL 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,WebGAL 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,WebGAL 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+WebGAL 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+WebGAL 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+WebGAL 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+
+;结束分支
+label:end;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+WebGAL 项目组期待你的作品能够在 WebGAL 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 WebGAL 项目! -v23.wav;
+end;
diff --git a/packages/parser_legacy/test/test-resources/start.txt b/packages/parser_legacy/test/test-resources/start.txt
new file mode 100644
index 000000000..a1240cea9
--- /dev/null
+++ b/packages/parser_legacy/test/test-resources/start.txt
@@ -0,0 +1,105 @@
+; 初始场景,以及特效演示
+changeBg:c4.jpg -next;
+unlockCg:c4.jpg -name=街前; 解锁部分CG
+unlockCg:xgmain.jpeg -name=星光咖啡馆与死神之蝶;
+unlockBgm:s_Title.mp3 -name=Smiling-Swinging!!;
+WebGAL:特效系统是3.9.2版本新引入的系统,你想要看特效的演示吗?;
+choose:观看特效演示:demo|我不要看特效,直接来吧!:toStart;
+label:demo;
+pixiInit;
+pixiPerform:rain;
+WebGAL:现在展示的是下雨的特效。;
+pixiInit;
+pixiPerform:snow;
+WebGAL:现在展示的是下雪的特效。;
+setTextbox:hide;
+pixiInit;
+setTextbox:on -next;
+WebGAL:特效的演示已经结束,现在我们正式开始吧!;
+
+; 正式场景
+label:toStart;
+playVideo:OP.mp4;
+changeBg:c3.jpg -next;
+unlockCg:c3.jpg -name=cafe;
+changeFigure:m2.png -left -next;
+bgm:cb1.mp3;
+unlockBgm:cb1.mp3 -name=ひとすじの光明;
+setAnimation:enter-from-left -target=fig-left -next;
+米咖多:蛋包饭是栞那做的,但红茶是夏目泡的。 -v1.ogg;
+昂晴:......;
+顺便问一下,你是打算做什么的?;
+changeFigure:m1.png -left -next;
+米咖多:就是倒饮料。然后咖啡和红茶的冲泡方式我还是记住了的。 -v2.ogg;
+昂晴:这、这样么......;
+:这猫爪子真的能泡茶么。;
+拿得了水壶吗?就凭他那个肉球爪子......;
+难道这些也是凭借猫妖的奇特力量做到的吗?;
+changeFigure:none -left -next;
+changeFigure:k1.png -next;
+setAnimation:enter-from-bottom -target=fig-center -next;
+栞那:那么,你想先尝哪个? -v3.ogg;
+choose:品尝蛋包饭:dbf|品尝红茶:hc;
+
+; 栞那选项
+label:dbf;
+昂晴:总之,先确认下蛋包饭的味道吧;
+changeFigure:k2.png -next;
+栞那:明白了,交给我吧 -v4.ogg;
+changeFigure:none -next;
+changeFigure:m2.png -left -next;
+changeFigure:k3.png -right -next;
+setAnimation:enter-from-left -target=fig-left -next;
+setAnimation:enter-from-right -target=fig-right -next;
+栞那:那么米咖多先生,我去做一下试作品 -v5.ogg;
+米咖多:嗯。去吧 -v6.ogg;
+changeFigure:none -left -next;
+changeFigure:none -right -next;
+changeFigure:k4.png -next;
+setAnimation:enter-from-bottom -target=fig-center -next;
+栞那:那么高岭同学,我们移动到厨房吧 -v7.ogg;
+changeFigure:none -next;
+bgm:cb2.mp3;
+unlockBgm:cb2.mp3 -name=Tea Break;
+changeBg:c2.jpg;
+unlockCg:c2.jpg -name=厨房;
+changeFigure:k2.png -next;
+栞那:话不多说开始做吧 -v8.ogg;
+jumpLabel:end;
+
+; 夏目选项
+label:hc;
+changeFigure:none -next;
+changeFigure:m1.png -left -next;
+changeFigure:k1.png -right -next;
+setAnimation:enter-from-left -target=fig-left -next;
+setAnimation:enter-from-right -target=fig-right -next;
+米咖多:那么就是,夏目了吧 -v9.ogg;
+changeFigure:k6.png -right -next;
+栞那:她刚去休息,要不要我叫回来呢? -v10.ogg;
+昂晴:没事,我自己去吧;
+changeFigure:none -left -next;
+changeFigure:none -right -next;
+bgm:cb2.mp3;
+changeBg:c1.jpg -next;
+unlockCg:c1.jpg -name=休息室;
+:我先敲了敲门;
+miniAvatar:n1.png;
+夏目:哪位? -v11.ogg;
+昂晴:我是高岭,可以进去吗?;
+夏目:可以,没问题 -v12.ogg;
+昂晴:打搅了;
+miniAvatar:none;
+changeFigure:n4.png -next;
+昂晴:打搅你休息了;
+夏目:不用在意,怎么了? -v13.ogg;
+changeFigure:n2.png -next;
+:她并没有无精打采,比想象中精神多了;
+昂晴:问道拿手菜谱,就听说四季同学泡的红茶味道不错,我也想品尝一下;
+夏目:明白了,那我们回去吧 -v14.ogg;
+jumpLabel:end;
+
+; 结束场景
+label:end;
+changeFigure:none -next;
+WebGAL:基础演出的展示已经结束。;
diff --git a/packages/parser_legacy/test/test-resources/var.txt b/packages/parser_legacy/test/test-resources/var.txt
new file mode 100644
index 000000000..8185817d4
--- /dev/null
+++ b/packages/parser_legacy/test/test-resources/var.txt
@@ -0,0 +1,11 @@
+setVar:a=1;
+WebGAL:a=1? -when=a==1;
+
+;正常解析
+WebGAL:test;
+
+; : 异常测试
+WebGAL : test;
+
+分号后应该被截断;看不到我!
+转义分号后应该不截断\;能看到我!
diff --git a/packages/parser_legacy/tsconfig.json b/packages/parser_legacy/tsconfig.json
new file mode 100644
index 000000000..5fb839743
--- /dev/null
+++ b/packages/parser_legacy/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "declaration": true,
+ "declarationDir": "build/types",
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "allowJs": false,
+ "skipLibCheck": false,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "noImplicitAny": false,
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": [
+ "src"
+ ],
+ "references": [{ "path": "./tsconfig.node.json"}]
+}
diff --git a/packages/parser_legacy/tsconfig.node.json b/packages/parser_legacy/tsconfig.node.json
new file mode 100644
index 000000000..fde6cf327
--- /dev/null
+++ b/packages/parser_legacy/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "module": "esnext",
+ "moduleResolution": "node"
+ },
+ "include": [
+ "vite.config.ts"
+ ]
+}
diff --git a/packages/server/.gitattributes b/packages/server/.gitattributes
new file mode 100644
index 000000000..dfe077042
--- /dev/null
+++ b/packages/server/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/packages/server/.gitignore b/packages/server/.gitignore
new file mode 100644
index 000000000..70dffce3c
--- /dev/null
+++ b/packages/server/.gitignore
@@ -0,0 +1,130 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+debug
+WebGAL
diff --git a/packages/server/LICENSE b/packages/server/LICENSE
new file mode 100644
index 000000000..a612ad981
--- /dev/null
+++ b/packages/server/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/packages/server/README.md b/packages/server/README.md
new file mode 100644
index 000000000..0909c5ea2
--- /dev/null
+++ b/packages/server/README.md
@@ -0,0 +1,17 @@
+# WebGAL-Server
+WebGAL Server and Command LIne Interface.
+
+WebGAL 本地调试服务器
+
+## 使用说明
+### 自动探测模式
+自动探测模式在以下两种情况下探测 WebGAL 工程文件:
+
+1、当前目录就是 WebGAL 工程的根目录。
+
+2、当前目录包含名为 'WebGAL' 的目录(不区分大小写),这个目录是 WebGAL 工程的根目录。
+
+### 指定目录模式
+通过命令行运行此程序,第一个(且唯一一个)参数就是要指定的绝对或相对路径,是 WebGAL 的工程目录。
+
+**注意:Windows 下的路径不能以 \ 分隔,请以 \\ 或 / 分隔,否则会发生错误。**
diff --git a/packages/server/index-linux b/packages/server/index-linux
new file mode 100644
index 000000000..241939b51
Binary files /dev/null and b/packages/server/index-linux differ
diff --git a/packages/server/index-macos b/packages/server/index-macos
new file mode 100644
index 000000000..d8c3cae7a
Binary files /dev/null and b/packages/server/index-macos differ
diff --git a/packages/server/index-win.exe b/packages/server/index-win.exe
new file mode 100644
index 000000000..5db3afea7
Binary files /dev/null and b/packages/server/index-win.exe differ
diff --git a/packages/server/index.js b/packages/server/index.js
new file mode 100644
index 000000000..b4d39d7ec
--- /dev/null
+++ b/packages/server/index.js
@@ -0,0 +1,81 @@
+const express = require('express');
+const Cloudlog = require("cloudlogjs");
+const fs = require('fs');
+const path = require('path');
+const rl = require('readline');
+const open = require('open');
+
+// 读取控制台输入
+const readline = rl.createInterface({
+ input: process.stdin,
+ output: process.stdout
+})
+
+const server = new express();
+const Port = process.env.PORT || 3000;
+const logger = new Cloudlog();
+
+// 读取控制台数据
+const args = process.argv;
+logger.info(`WebGAL Server 启动参数:`, args);
+
+// 读取工作目录
+const cwd = process.cwd();
+logger.info(`WebGAL 工作目录当前为 ${cwd}`);
+
+// 检测并获取 WebGAL 游戏资源所在目录
+let webgalWd = '';
+
+// 参数模式
+if (args.length >= 3) {
+ // 参数就是工作目录
+ const wdr = args[2];
+ logger.info(`指定工作目录:${wdr}`);
+ if (wdr[0] === '/' || wdr.match(/^\w:/)) {
+ // 绝对路径模式
+ logger.debug('绝对路径模式');
+ webgalWd = wdr;
+ } else {
+ // 相对路径模式
+ logger.debug('相对路径模式');
+ const rwd = wdr.split(/[\\\/]/g);
+ webgalWd = path.join(cwd, ...rwd);
+ }
+ // 输出
+ logger.info(`工作目录被设置为 ${webgalWd}`);
+}
+
+// 自动探测模式
+if (webgalWd === '') {
+ const dirInfo = fs.readdirSync(cwd);
+ if (dirInfo.includes('index.html')) {
+ logger.info(`在当前工作目录下启动 WebGAL`);
+ webgalWd = cwd;
+ } else {
+ // 全转成大写,复制一份副本
+ const dirInfoUpperCase = dirInfo.map(e => e.toUpperCase());
+ if (dirInfoUpperCase.includes('WEBGAL')) {
+ // 找 index
+ const index = dirInfoUpperCase.findIndex(e => e === 'WEBGAL');
+ const trueDirName = dirInfo[index];
+ webgalWd = path.join(cwd, trueDirName);
+ } else {
+ // 没找到
+ logger.info(`未找到 WebGAL 文件,请在 WebGAL 项目目录下启动 WebGAL-Server 或在本目录下的 WebGAL 文件夹下启动。`);
+ }
+ }
+}
+
+if (webgalWd) {
+ // 监听端口
+ server.use(express.static(webgalWd))//allow browser access resources
+ server.listen(Port, () => {
+ logger.info(`启动 WebGAL 服务器,运行于 http://localhost:${Port} .`)
+ })
+ open(`http://localhost:${Port}`);
+} else {
+ logger.error(`未找到启动文件,请退出`);
+ readline.on('line', () => {
+ process.exit();
+ })
+}
diff --git a/packages/server/package.json b/packages/server/package.json
new file mode 100644
index 000000000..6e2de1951
--- /dev/null
+++ b/packages/server/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "WebGAL-Server",
+ "scripts": {
+ "start": "node index.js",
+ "pkg": "pkg index.js"
+ },
+ "version": "0.1.0",
+ "description": "WebGAL Server and Command LIne Interface.",
+ "main": "index.js",
+ "repository": "https://github.com/WebGAL-Technical-Committee/WebGAL-Server.git",
+ "author": "Mahiru ",
+ "license": "MPL-2.0",
+ "devDependencies": {
+ "pkg": "^5.8.0"
+ },
+ "dependencies": {
+ "cloudlogjs": "^1.0.11",
+ "express": "^4.20.0",
+ "open": "^8.4.0"
+ }
+}
diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock
new file mode 100644
index 000000000..1a8c9536f
--- /dev/null
+++ b/packages/server/yarn.lock
@@ -0,0 +1,1407 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/generator@7.18.2":
+ version "7.18.2"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d"
+ integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==
+ dependencies:
+ "@babel/types" "^7.18.2"
+ "@jridgewell/gen-mapping" "^0.3.0"
+ jsesc "^2.5.1"
+
+"@babel/helper-string-parser@^7.18.10":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56"
+ integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==
+
+"@babel/helper-validator-identifier@^7.16.7", "@babel/helper-validator-identifier@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
+ integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
+
+"@babel/parser@7.18.4":
+ version "7.18.4"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.4.tgz#6774231779dd700e0af29f6ad8d479582d7ce5ef"
+ integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==
+
+"@babel/types@7.18.4":
+ version "7.18.4"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354"
+ integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.16.7"
+ to-fast-properties "^2.0.0"
+
+"@babel/types@^7.18.2":
+ version "7.18.10"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.10.tgz#4908e81b6b339ca7c6b7a555a5fc29446f26dde6"
+ integrity sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==
+ dependencies:
+ "@babel/helper-string-parser" "^7.18.10"
+ "@babel/helper-validator-identifier" "^7.18.6"
+ to-fast-properties "^2.0.0"
+
+"@jridgewell/gen-mapping@^0.3.0":
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
+ integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/resolve-uri@^3.0.3":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
+ integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
+
+"@jridgewell/set-array@^1.0.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
+ integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
+
+"@jridgewell/sourcemap-codec@^1.4.10":
+ version "1.4.14"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
+ integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
+
+"@jridgewell/trace-mapping@^0.3.9":
+ version "0.3.14"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
+ integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.0.3"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+
+"@nodelib/fs.scandir@2.1.5":
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
+ integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
+ dependencies:
+ "@nodelib/fs.stat" "2.0.5"
+ run-parallel "^1.1.9"
+
+"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
+ integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
+
+"@nodelib/fs.walk@^1.2.3":
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
+ integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
+ dependencies:
+ "@nodelib/fs.scandir" "2.1.5"
+ fastq "^1.6.0"
+
+accepts@~1.3.8:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
+ integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
+ dependencies:
+ mime-types "~2.1.34"
+ negotiator "0.6.3"
+
+agent-base@6:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
+ integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
+ dependencies:
+ debug "4"
+
+ansi-regex@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+ integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+aproba@^1.0.3:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
+ integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
+
+are-we-there-yet@~1.1.2:
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146"
+ integrity sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==
+ dependencies:
+ delegates "^1.0.0"
+ readable-stream "^2.0.6"
+
+array-flatten@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+ integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
+
+array-union@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
+ integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
+
+at-least-node@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
+ integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
+
+axios@^0.24.0:
+ version "0.24.0"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6"
+ integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==
+ dependencies:
+ follow-redirects "^1.14.4"
+
+base64-js@^1.3.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
+ integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
+
+bl@^4.0.3:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
+ integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
+ dependencies:
+ buffer "^5.5.0"
+ inherits "^2.0.4"
+ readable-stream "^3.4.0"
+
+body-parser@1.20.0:
+ version "1.20.0"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5"
+ integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==
+ dependencies:
+ bytes "3.1.2"
+ content-type "~1.0.4"
+ debug "2.6.9"
+ depd "2.0.0"
+ destroy "1.2.0"
+ http-errors "2.0.0"
+ iconv-lite "0.4.24"
+ on-finished "2.4.1"
+ qs "6.10.3"
+ raw-body "2.5.1"
+ type-is "~1.6.18"
+ unpipe "1.0.0"
+
+braces@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+ integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+ dependencies:
+ fill-range "^7.0.1"
+
+buffer@^5.5.0:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
+ integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
+ dependencies:
+ base64-js "^1.3.1"
+ ieee754 "^1.1.13"
+
+bytes@3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
+ integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
+
+call-bind@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
+ integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
+ dependencies:
+ function-bind "^1.1.1"
+ get-intrinsic "^1.0.2"
+
+chalk@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+chownr@^1.1.1:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
+ integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
+
+cliui@^7.0.2:
+ version "7.0.4"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
+ integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.0"
+ wrap-ansi "^7.0.0"
+
+cloudlogjs@^1.0.11:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/cloudlogjs/-/cloudlogjs-1.0.11.tgz#986ee884b786434c9bf3812e2afdbea9163e31a3"
+ integrity sha512-0dfQYXHCpPfGOrOEJjRRWcRArjqKxBSYb01+YLdFVj5bQ+9sZ1Uf5EuXpfqZh+r9Xlugpipi2uvNcdChAKHq2w==
+ dependencies:
+ axios "^0.24.0"
+
+code-point-at@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
+ integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+console-control-strings@^1.0.0, console-control-strings@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
+ integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
+
+content-disposition@0.5.4:
+ version "0.5.4"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
+ integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
+ dependencies:
+ safe-buffer "5.2.1"
+
+content-type@~1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
+ integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
+
+cookie-signature@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+ integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
+
+cookie@0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
+ integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
+
+core-util-is@~1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
+ integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
+
+debug@2.6.9:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+debug@4:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
+decompress-response@^4.2.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986"
+ integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
+ dependencies:
+ mimic-response "^2.0.0"
+
+deep-extend@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+ integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
+define-lazy-prop@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
+ integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
+
+delegates@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+ integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
+
+depd@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
+ integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
+destroy@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
+ integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
+
+detect-libc@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
+ integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
+
+dir-glob@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
+ integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
+ dependencies:
+ path-type "^4.0.0"
+
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+ integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
+
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+encodeurl@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+ integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
+
+end-of-stream@^1.1.0, end-of-stream@^1.4.1:
+ version "1.4.4"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
+ integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
+ dependencies:
+ once "^1.4.0"
+
+escalade@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+ integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
+escape-html@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+ integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
+
+etag@~1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
+ integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
+
+expand-template@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
+ integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
+
+express@^4.18.1:
+ version "4.18.1"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf"
+ integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==
+ dependencies:
+ accepts "~1.3.8"
+ array-flatten "1.1.1"
+ body-parser "1.20.0"
+ content-disposition "0.5.4"
+ content-type "~1.0.4"
+ cookie "0.5.0"
+ cookie-signature "1.0.6"
+ debug "2.6.9"
+ depd "2.0.0"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ finalhandler "1.2.0"
+ fresh "0.5.2"
+ http-errors "2.0.0"
+ merge-descriptors "1.0.1"
+ methods "~1.1.2"
+ on-finished "2.4.1"
+ parseurl "~1.3.3"
+ path-to-regexp "0.1.7"
+ proxy-addr "~2.0.7"
+ qs "6.10.3"
+ range-parser "~1.2.1"
+ safe-buffer "5.2.1"
+ send "0.18.0"
+ serve-static "1.15.0"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ type-is "~1.6.18"
+ utils-merge "1.0.1"
+ vary "~1.1.2"
+
+fast-glob@^3.2.9:
+ version "3.2.11"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
+ integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.2"
+ merge2 "^1.3.0"
+ micromatch "^4.0.4"
+
+fastq@^1.6.0:
+ version "1.13.0"
+ resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
+ integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==
+ dependencies:
+ reusify "^1.0.4"
+
+fill-range@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+ integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+finalhandler@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32"
+ integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==
+ dependencies:
+ debug "2.6.9"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ on-finished "2.4.1"
+ parseurl "~1.3.3"
+ statuses "2.0.1"
+ unpipe "~1.0.0"
+
+follow-redirects@^1.14.4:
+ version "1.15.1"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5"
+ integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
+
+forwarded@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
+ integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
+
+fresh@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+ integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
+
+from2@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
+ integrity sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==
+ dependencies:
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+
+fs-constants@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
+ integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
+
+fs-extra@^9.1.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
+ integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
+ dependencies:
+ at-least-node "^1.0.0"
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+gauge@~2.7.3:
+ version "2.7.4"
+ resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
+ integrity sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==
+ dependencies:
+ aproba "^1.0.3"
+ console-control-strings "^1.0.0"
+ has-unicode "^2.0.0"
+ object-assign "^4.1.0"
+ signal-exit "^3.0.0"
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+ wide-align "^1.1.0"
+
+get-caller-file@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
+get-intrinsic@^1.0.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598"
+ integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==
+ dependencies:
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-symbols "^1.0.3"
+
+github-from-package@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
+ integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
+
+glob-parent@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+globby@^11.1.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
+ integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
+ dependencies:
+ array-union "^2.1.0"
+ dir-glob "^3.0.1"
+ fast-glob "^3.2.9"
+ ignore "^5.2.0"
+ merge2 "^1.4.1"
+ slash "^3.0.0"
+
+graceful-fs@^4.1.6, graceful-fs@^4.2.0:
+ version "4.2.10"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
+ integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+has-symbols@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+ integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
+has-unicode@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+ integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==
+
+has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+ dependencies:
+ function-bind "^1.1.1"
+
+http-errors@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
+ integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
+ dependencies:
+ depd "2.0.0"
+ inherits "2.0.4"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ toidentifier "1.0.1"
+
+https-proxy-agent@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
+ integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
+ dependencies:
+ agent-base "6"
+ debug "4"
+
+iconv-lite@0.4.24:
+ version "0.4.24"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
+ integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3"
+
+ieee754@^1.1.13:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
+ integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
+
+ignore@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
+ integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
+
+inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+ini@~1.3.0:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
+ integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
+
+into-stream@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-6.0.0.tgz#4bfc1244c0128224e18b8870e85b2de8e66c6702"
+ integrity sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==
+ dependencies:
+ from2 "^2.3.0"
+ p-is-promise "^3.0.0"
+
+ipaddr.js@1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
+ integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
+
+is-core-module@2.9.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
+ integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
+ dependencies:
+ has "^1.0.3"
+
+is-core-module@^2.9.0:
+ version "2.10.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed"
+ integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==
+ dependencies:
+ has "^1.0.3"
+
+is-docker@^2.0.0, is-docker@^2.1.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
+ integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
+
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-fullwidth-code-point@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
+ integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==
+ dependencies:
+ number-is-nan "^1.0.0"
+
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
+is-glob@^4.0.1:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+is-wsl@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
+ integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
+ dependencies:
+ is-docker "^2.0.0"
+
+isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+ integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
+
+jsesc@^2.5.1:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
+ integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
+
+jsonfile@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
+ integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
+ dependencies:
+ universalify "^2.0.0"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+lru-cache@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+ integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+ dependencies:
+ yallist "^4.0.0"
+
+media-typer@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+ integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
+
+merge-descriptors@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+ integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
+
+merge2@^1.3.0, merge2@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
+ integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+
+methods@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+ integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
+
+micromatch@^4.0.4:
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
+ integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
+ dependencies:
+ braces "^3.0.2"
+ picomatch "^2.3.1"
+
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@~2.1.24, mime-types@~2.1.34:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+mime@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+ integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+
+mimic-response@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
+ integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
+
+minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6:
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
+ integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
+
+mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
+ integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
+
+ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+ms@2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+multistream@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/multistream/-/multistream-4.1.0.tgz#7bf00dfd119556fbc153cff3de4c6d477909f5a8"
+ integrity sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==
+ dependencies:
+ once "^1.4.0"
+ readable-stream "^3.6.0"
+
+napi-build-utils@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
+ integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
+
+negotiator@0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
+ integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
+
+node-abi@^2.21.0:
+ version "2.30.1"
+ resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.30.1.tgz#c437d4b1fe0e285aaf290d45b45d4d7afedac4cf"
+ integrity sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==
+ dependencies:
+ semver "^5.4.1"
+
+node-fetch@^2.6.6:
+ version "2.6.7"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
+ integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
+ dependencies:
+ whatwg-url "^5.0.0"
+
+npmlog@^4.0.1:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
+ integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
+ dependencies:
+ are-we-there-yet "~1.1.2"
+ console-control-strings "~1.1.0"
+ gauge "~2.7.3"
+ set-blocking "~2.0.0"
+
+number-is-nan@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
+ integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==
+
+object-assign@^4.1.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
+
+object-inspect@^1.9.0:
+ version "1.12.2"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea"
+ integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==
+
+on-finished@2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
+ integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
+ dependencies:
+ ee-first "1.1.1"
+
+once@^1.3.1, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+ dependencies:
+ wrappy "1"
+
+open@^8.4.0:
+ version "8.4.0"
+ resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8"
+ integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==
+ dependencies:
+ define-lazy-prop "^2.0.0"
+ is-docker "^2.1.1"
+ is-wsl "^2.2.0"
+
+p-is-promise@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-3.0.0.tgz#58e78c7dfe2e163cf2a04ff869e7c1dba64a5971"
+ integrity sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==
+
+parseurl@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
+ integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
+
+path-parse@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+ integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+path-to-regexp@0.1.7:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
+ integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
+
+path-type@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
+ integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+
+picomatch@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+pkg-fetch@3.4.2:
+ version "3.4.2"
+ resolved "https://registry.yarnpkg.com/pkg-fetch/-/pkg-fetch-3.4.2.tgz#6f68ebc54842b73f8c0808959a9df3739dcb28b7"
+ integrity sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA==
+ dependencies:
+ chalk "^4.1.2"
+ fs-extra "^9.1.0"
+ https-proxy-agent "^5.0.0"
+ node-fetch "^2.6.6"
+ progress "^2.0.3"
+ semver "^7.3.5"
+ tar-fs "^2.1.1"
+ yargs "^16.2.0"
+
+pkg@^5.8.0:
+ version "5.8.0"
+ resolved "https://registry.yarnpkg.com/pkg/-/pkg-5.8.0.tgz#a77644aeff0b94a1656d7f76558837f7c754a4c0"
+ integrity sha512-8h9PUDYFi+LOMLbIyGRdP21g08mAtHidSpofSrf8LWhxUWGHymaRzcopEGiynB5EhQmZUKM6PQ9kCImV2TpdjQ==
+ dependencies:
+ "@babel/generator" "7.18.2"
+ "@babel/parser" "7.18.4"
+ "@babel/types" "7.18.4"
+ chalk "^4.1.2"
+ fs-extra "^9.1.0"
+ globby "^11.1.0"
+ into-stream "^6.0.0"
+ is-core-module "2.9.0"
+ minimist "^1.2.6"
+ multistream "^4.1.0"
+ pkg-fetch "3.4.2"
+ prebuild-install "6.1.4"
+ resolve "^1.22.0"
+ stream-meter "^1.0.4"
+
+prebuild-install@6.1.4:
+ version "6.1.4"
+ resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.1.4.tgz#ae3c0142ad611d58570b89af4986088a4937e00f"
+ integrity sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==
+ dependencies:
+ detect-libc "^1.0.3"
+ expand-template "^2.0.3"
+ github-from-package "0.0.0"
+ minimist "^1.2.3"
+ mkdirp-classic "^0.5.3"
+ napi-build-utils "^1.0.1"
+ node-abi "^2.21.0"
+ npmlog "^4.0.1"
+ pump "^3.0.0"
+ rc "^1.2.7"
+ simple-get "^3.0.3"
+ tar-fs "^2.0.0"
+ tunnel-agent "^0.6.0"
+
+process-nextick-args@~2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+ integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
+progress@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
+ integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
+
+proxy-addr@~2.0.7:
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
+ integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
+ dependencies:
+ forwarded "0.2.0"
+ ipaddr.js "1.9.1"
+
+pump@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
+ integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+qs@6.10.3:
+ version "6.10.3"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
+ integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
+ dependencies:
+ side-channel "^1.0.4"
+
+queue-microtask@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
+ integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+
+range-parser@~1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+ integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+
+raw-body@2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
+ integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
+ dependencies:
+ bytes "3.1.2"
+ http-errors "2.0.0"
+ iconv-lite "0.4.24"
+ unpipe "1.0.0"
+
+rc@^1.2.7:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
+ integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
+ dependencies:
+ deep-extend "^0.6.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~2.0.1"
+
+readable-stream@^2.0.0, readable-stream@^2.0.6, readable-stream@^2.1.4:
+ version "2.3.7"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
+ integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
+
+readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
+ integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+ integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
+
+resolve@^1.22.0:
+ version "1.22.1"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
+ integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
+ dependencies:
+ is-core-module "^2.9.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+reusify@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
+ integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+
+run-parallel@^1.1.9:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
+ integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
+ dependencies:
+ queue-microtask "^1.2.2"
+
+safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+"safer-buffer@>= 2.1.2 < 3":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+semver@^5.4.1:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+ integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
+semver@^7.3.5:
+ version "7.3.7"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
+ integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
+ dependencies:
+ lru-cache "^6.0.0"
+
+send@0.18.0:
+ version "0.18.0"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
+ integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
+ dependencies:
+ debug "2.6.9"
+ depd "2.0.0"
+ destroy "1.2.0"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ fresh "0.5.2"
+ http-errors "2.0.0"
+ mime "1.6.0"
+ ms "2.1.3"
+ on-finished "2.4.1"
+ range-parser "~1.2.1"
+ statuses "2.0.1"
+
+serve-static@1.15.0:
+ version "1.15.0"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
+ integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
+ dependencies:
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ parseurl "~1.3.3"
+ send "0.18.0"
+
+set-blocking@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+ integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
+
+setprototypeof@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
+ integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
+
+side-channel@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
+ integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
+ dependencies:
+ call-bind "^1.0.0"
+ get-intrinsic "^1.0.2"
+ object-inspect "^1.9.0"
+
+signal-exit@^3.0.0:
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
+ integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
+
+simple-concat@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
+ integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
+
+simple-get@^3.0.3:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55"
+ integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==
+ dependencies:
+ decompress-response "^4.2.0"
+ once "^1.3.1"
+ simple-concat "^1.0.0"
+
+slash@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
+ integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
+
+statuses@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
+ integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+
+stream-meter@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/stream-meter/-/stream-meter-1.0.4.tgz#52af95aa5ea760a2491716704dbff90f73afdd1d"
+ integrity sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ==
+ dependencies:
+ readable-stream "^2.1.4"
+
+string-width@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
+ integrity sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==
+ dependencies:
+ code-point-at "^1.0.0"
+ is-fullwidth-code-point "^1.0.0"
+ strip-ansi "^3.0.0"
+
+"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string_decoder@^1.1.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
+ integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
+ dependencies:
+ safe-buffer "~5.2.0"
+
+string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+ integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+ dependencies:
+ safe-buffer "~5.1.0"
+
+strip-ansi@^3.0.0, strip-ansi@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
+ integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==
+ dependencies:
+ ansi-regex "^2.0.0"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+ integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
+
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+tar-fs@^2.0.0, tar-fs@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
+ integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
+ dependencies:
+ chownr "^1.1.1"
+ mkdirp-classic "^0.5.2"
+ pump "^3.0.0"
+ tar-stream "^2.1.4"
+
+tar-stream@^2.1.4:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
+ integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
+ dependencies:
+ bl "^4.0.3"
+ end-of-stream "^1.4.1"
+ fs-constants "^1.0.0"
+ inherits "^2.0.3"
+ readable-stream "^3.1.1"
+
+to-fast-properties@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
+ integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
+
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
+toidentifier@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
+ integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+
+tr46@~0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+ integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+
+tunnel-agent@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
+ integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==
+ dependencies:
+ safe-buffer "^5.0.1"
+
+type-is@~1.6.18:
+ version "1.6.18"
+ resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
+ integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
+ dependencies:
+ media-typer "0.3.0"
+ mime-types "~2.1.24"
+
+universalify@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
+ integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
+
+unpipe@1.0.0, unpipe@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+ integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
+
+util-deprecate@^1.0.1, util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+ integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+
+utils-merge@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+ integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
+
+vary@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+ integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
+
+webidl-conversions@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+ integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
+
+whatwg-url@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+ integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
+ dependencies:
+ tr46 "~0.0.3"
+ webidl-conversions "^3.0.0"
+
+wide-align@^1.1.0:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
+ integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
+ dependencies:
+ string-width "^1.0.2 || 2 || 3 || 4"
+
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+y18n@^5.0.5:
+ version "5.0.8"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
+ integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
+yallist@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+ integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
+yargs-parser@^20.2.2:
+ version "20.2.9"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
+ integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
+
+yargs@^16.2.0:
+ version "16.2.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
+ integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
+ dependencies:
+ cliui "^7.0.2"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.0"
+ y18n "^5.0.5"
+ yargs-parser "^20.2.2"
diff --git a/packages/webgal/.eslintignore b/packages/webgal/.eslintignore
new file mode 100644
index 000000000..0bdc31f75
--- /dev/null
+++ b/packages/webgal/.eslintignore
@@ -0,0 +1,10 @@
+*.md
+*.css
+*.scss
+*.otf
+*.svg
+*.ttf
+*.ico
+*.mp3
+*.wav
+*.txt
diff --git a/packages/webgal/.eslintrc.js b/packages/webgal/.eslintrc.js
new file mode 100644
index 000000000..ba9e148db
--- /dev/null
+++ b/packages/webgal/.eslintrc.js
@@ -0,0 +1,32 @@
+module.exports = {
+ extends: ['alloy', 'alloy/react', 'alloy/typescript', 'plugin:prettier/recommended'],
+ env: {
+ // 你的环境变量(包含多个预定义的全局变量)
+ //
+ // browser: true,
+ // node: true,
+ // mocha: true,
+ // jest: true,
+ // jquery: true
+ },
+ globals: {
+ // 你的全局变量(设置为 false 表示它不允许被重新赋值)
+ //
+ // myGlobal: false
+ },
+ rules: {
+ // 自定义你的规则
+ // 最大圈复杂度
+ complexity: ['error', 30],
+ 'linebreak-style': ['error', 'unix'],
+ semi: 2,
+ // indent: ['error', 2],
+ 'semi-style': ['error', 'last'],
+ 'react/jsx-no-useless-fragment': [
+ 'error',
+ {
+ allowExpressions: true,
+ },
+ ],
+ },
+};
diff --git a/packages/webgal/.prettierignore b/packages/webgal/.prettierignore
new file mode 100644
index 000000000..75191a9ce
--- /dev/null
+++ b/packages/webgal/.prettierignore
@@ -0,0 +1,5 @@
+*.md
+*.css
+*.scss
+*.otf
+*.svg
diff --git a/packages/webgal/.prettierrc.js b/packages/webgal/.prettierrc.js
new file mode 100644
index 000000000..ae7b006b1
--- /dev/null
+++ b/packages/webgal/.prettierrc.js
@@ -0,0 +1,42 @@
+// .prettierrc.js
+module.exports = {
+ // 一行最多 120 字符
+ printWidth: 120,
+ // 使用 n 个空格缩进
+ tabWidth: 2,
+ // 不使用缩进符,而使用空格
+ useTabs: false,
+ // 行尾需要有分号
+ semi: true,
+ // 使用单引号
+ singleQuote: true,
+ // 对象的 key 仅在必要时用引号
+ quoteProps: 'as-needed',
+ // jsx 不使用单引号,而使用双引号
+ jsxSingleQuote: false,
+ // 末尾需要有逗号
+ trailingComma: 'all',
+ // 大括号内的首尾需要空格
+ bracketSpacing: true,
+ // jsx 标签的反尖括号需要换行
+ bracketSameLine: false,
+ // 箭头函数,只有一个参数的时候,也需要括号
+ arrowParens: 'always',
+ // 每个文件格式化的范围是文件的全部内容
+ rangeStart: 0,
+ rangeEnd: Infinity,
+ // 不需要写文件开头的 @prettier
+ requirePragma: false,
+ // 不需要自动在文件开头插入 @prettier
+ insertPragma: false,
+ // 使用默认的折行标准
+ proseWrap: 'preserve',
+ // 根据显示样式决定 html 要不要折行
+ htmlWhitespaceSensitivity: 'css',
+ // vue 文件中的 script 和 style 内不用缩进
+ vueIndentScriptAndStyle: false,
+ // 换行符使用 lf
+ endOfLine: 'lf',
+ // 格式化内嵌代码
+ embeddedLanguageFormatting: 'auto',
+}
diff --git a/packages/webgal/index.html b/packages/webgal/index.html
new file mode 100644
index 000000000..c3ffb4d19
--- /dev/null
+++ b/packages/webgal/index.html
@@ -0,0 +1,288 @@
+
+
+
+
+
+
+
+
+ WebGAL
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PRESS THE SCREEN TO START
+
+
+
+
+ Powered by
WebGAL Framework
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/webgal/package.json b/packages/webgal/package.json
new file mode 100644
index 000000000..52f5b5a1d
--- /dev/null
+++ b/packages/webgal/package.json
@@ -0,0 +1,57 @@
+{
+ "name": "webgal",
+ "private": true,
+ "version": "4.5.9",
+ "scripts": {
+ "dev": "vite --host --port 3000",
+ "build": "cross-env NODE_ENV=production tsc && vite build --base=./",
+ "preview": "vite preview",
+ "lint": "eslint src/** --fix"
+ },
+ "dependencies": {
+ "@emotion/css": "^11.11.2",
+ "@icon-park/react": "^1.4.2",
+ "@reduxjs/toolkit": "^1.8.1",
+ "angular-expressions": "^1.1.5",
+ "axios": "^0.28.0",
+ "cloudlogjs": "^1.0.9",
+ "i18next": "^22.4.15",
+ "localforage": "^1.10.0",
+ "lodash": "^4.17.21",
+ "mitt": "^3.0.0",
+ "modern-css-reset": "^1.4.0",
+ "pixi-filters": "^4.2.0",
+ "pixi-live2d-display-webgal": "^0.5.8",
+ "pixi-spine": "^3.1.2",
+ "pixi.js": "^6.3.0",
+ "popmotion": "^11.0.5",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2",
+ "react-i18next": "^12.2.2",
+ "react-redux": "^8.0.1",
+ "sass": "^1.49.9",
+ "uuid": "^9.0.0",
+ "vite-plugin-package-version": "^1.0.2"
+ },
+ "devDependencies": {
+ "@types/lodash": "^4.14.180",
+ "@types/node": "^17.0.23",
+ "@types/react": "^17.0.33",
+ "@types/react-dom": "^17.0.10",
+ "@types/uuid": "^8.3.4",
+ "@typescript-eslint/eslint-plugin": "^5.18.0",
+ "@typescript-eslint/parser": "^5.18.0",
+ "@vitejs/plugin-react": "^4.0.4",
+ "cross-env": "^7.0.3",
+ "eslint": "^8.13.0",
+ "eslint-config-alloy": "^4.5.1",
+ "eslint-config-prettier": "^8.5.0",
+ "eslint-plugin-prettier": "^4.2.1",
+ "eslint-plugin-react": "^7.29.4",
+ "lint-staged": "^12.3.7",
+ "prettier": "^2.6.2",
+ "rollup-plugin-visualizer": "^5.6.0",
+ "typescript": "^4.5.4",
+ "vite": "^4.5.5"
+ }
+}
diff --git a/packages/webgal/public/game/animation/animationTable.json b/packages/webgal/public/game/animation/animationTable.json
new file mode 100644
index 000000000..f4ed953b6
--- /dev/null
+++ b/packages/webgal/public/game/animation/animationTable.json
@@ -0,0 +1,19 @@
+[
+ "enter-from-left",
+ "enter-from-bottom",
+ "enter-from-right",
+ "shake",
+ "move-front-and-back",
+ "enter",
+ "exit",
+ "blur",
+ "oldFilm",
+ "dotFilm",
+ "reflectionFilm",
+ "glitchFilm",
+ "rgbFilm",
+ "godrayFilm",
+ "removeFilm",
+ "shockwaveIn",
+ "shockwaveOut"
+]
diff --git a/packages/webgal/public/game/animation/blur.json b/packages/webgal/public/game/animation/blur.json
new file mode 100644
index 000000000..82a7f2da8
--- /dev/null
+++ b/packages/webgal/public/game/animation/blur.json
@@ -0,0 +1,10 @@
+[
+ {
+ "blur": 0,
+ "duration": 0
+ },
+ {
+ "blur": 5,
+ "duration": 300
+ }
+]
diff --git a/packages/webgal/public/game/animation/dotFilm.json b/packages/webgal/public/game/animation/dotFilm.json
new file mode 100644
index 000000000..b68183d20
--- /dev/null
+++ b/packages/webgal/public/game/animation/dotFilm.json
@@ -0,0 +1,6 @@
+[
+ {
+ "dotFilm": 1,
+ "duration": 0
+ }
+]
diff --git a/packages/webgal/public/game/animation/enter-from-bottom.json b/packages/webgal/public/game/animation/enter-from-bottom.json
new file mode 100644
index 000000000..4ac5b0163
--- /dev/null
+++ b/packages/webgal/public/game/animation/enter-from-bottom.json
@@ -0,0 +1,20 @@
+[
+ {
+ "alpha": 0,
+ "position": {
+ "x": 0,
+ "y": 50
+ },
+ "blur": 5,
+ "duration": 0
+ },
+ {
+ "alpha": 1,
+ "position": {
+ "x": 0,
+ "y": 0
+ },
+ "blur": 0,
+ "duration": 500
+ }
+]
diff --git a/packages/webgal/public/game/animation/enter-from-left.json b/packages/webgal/public/game/animation/enter-from-left.json
new file mode 100644
index 000000000..974c02d87
--- /dev/null
+++ b/packages/webgal/public/game/animation/enter-from-left.json
@@ -0,0 +1,30 @@
+[
+ {
+ "alpha": 0,
+ "scale": {
+ "x": 1,
+ "y": 1
+ },
+ "position": {
+ "x": -50,
+ "y": 0
+ },
+ "rotation": 0,
+ "blur": 5,
+ "duration": 0
+ },
+ {
+ "alpha": 1,
+ "scale": {
+ "x": 1,
+ "y": 1
+ },
+ "position": {
+ "x": 0,
+ "y": 0
+ },
+ "rotation": 0,
+ "blur": 0,
+ "duration": 500
+ }
+]
diff --git a/packages/webgal/public/game/animation/enter-from-right.json b/packages/webgal/public/game/animation/enter-from-right.json
new file mode 100644
index 000000000..df705a481
--- /dev/null
+++ b/packages/webgal/public/game/animation/enter-from-right.json
@@ -0,0 +1,20 @@
+[
+ {
+ "alpha": 0,
+ "position": {
+ "x": 50,
+ "y": 0
+ },
+ "blur": 5,
+ "duration": 0
+ },
+ {
+ "alpha": 1,
+ "position": {
+ "x": 0,
+ "y": 0
+ },
+ "blur": 0,
+ "duration": 500
+ }
+]
diff --git a/packages/webgal/public/game/animation/enter.json b/packages/webgal/public/game/animation/enter.json
new file mode 100644
index 000000000..70eaae283
--- /dev/null
+++ b/packages/webgal/public/game/animation/enter.json
@@ -0,0 +1,10 @@
+[
+ {
+ "alpha": 0,
+ "duration": 0
+ },
+ {
+ "alpha": 1,
+ "duration": 300
+ }
+]
diff --git a/packages/webgal/public/game/animation/exit.json b/packages/webgal/public/game/animation/exit.json
new file mode 100644
index 000000000..3f468cf7a
--- /dev/null
+++ b/packages/webgal/public/game/animation/exit.json
@@ -0,0 +1,10 @@
+[
+ {
+ "alpha": 1,
+ "duration": 0
+ },
+ {
+ "alpha": 0,
+ "duration": 300
+ }
+]
diff --git a/packages/webgal/public/game/animation/glitchFilm.json b/packages/webgal/public/game/animation/glitchFilm.json
new file mode 100644
index 000000000..5060afab8
--- /dev/null
+++ b/packages/webgal/public/game/animation/glitchFilm.json
@@ -0,0 +1,6 @@
+[
+ {
+ "glitchFilm": 1,
+ "duration": 0
+ }
+]
\ No newline at end of file
diff --git a/packages/webgal/public/game/animation/godrayFilm.json b/packages/webgal/public/game/animation/godrayFilm.json
new file mode 100644
index 000000000..1dd1ad321
--- /dev/null
+++ b/packages/webgal/public/game/animation/godrayFilm.json
@@ -0,0 +1,6 @@
+[
+ {
+ "godrayFilm": 1,
+ "duration": 0
+ }
+]
diff --git a/packages/webgal/public/game/animation/move-front-and-back.json b/packages/webgal/public/game/animation/move-front-and-back.json
new file mode 100644
index 000000000..59b8ea390
--- /dev/null
+++ b/packages/webgal/public/game/animation/move-front-and-back.json
@@ -0,0 +1,23 @@
+[
+ {
+ "scale": {
+ "x": 1,
+ "y": 1
+ },
+ "duration": 0
+ },
+ {
+ "scale": {
+ "x": 1.15,
+ "y": 1.15
+ },
+ "duration": 500
+ },
+ {
+ "scale": {
+ "x": 1,
+ "y": 1
+ },
+ "duration": 500
+ }
+]
diff --git a/packages/webgal/public/game/animation/oldFilm.json b/packages/webgal/public/game/animation/oldFilm.json
new file mode 100644
index 000000000..25c871fef
--- /dev/null
+++ b/packages/webgal/public/game/animation/oldFilm.json
@@ -0,0 +1,6 @@
+[
+ {
+ "oldFilm": 1,
+ "duration": 0
+ }
+]
diff --git a/packages/webgal/public/game/animation/reflectionFilm.json b/packages/webgal/public/game/animation/reflectionFilm.json
new file mode 100644
index 000000000..4989708b3
--- /dev/null
+++ b/packages/webgal/public/game/animation/reflectionFilm.json
@@ -0,0 +1,6 @@
+[
+ {
+ "reflectionFilm": 1,
+ "duration": 0
+ }
+]
diff --git a/packages/webgal/public/game/animation/removeFilm.json b/packages/webgal/public/game/animation/removeFilm.json
new file mode 100644
index 000000000..4e82ca50d
--- /dev/null
+++ b/packages/webgal/public/game/animation/removeFilm.json
@@ -0,0 +1,11 @@
+[
+ {
+ "oldFilm": 0,
+ "dotFilm": 0,
+ "reflectionFilm": 0,
+ "glitchFilm": 0,
+ "rgbFilm": 0,
+ "godrayFilm": 0,
+ "duration": 0
+ }
+]
diff --git a/packages/webgal/public/game/animation/rgbFilm.json b/packages/webgal/public/game/animation/rgbFilm.json
new file mode 100644
index 000000000..dd7fb9b04
--- /dev/null
+++ b/packages/webgal/public/game/animation/rgbFilm.json
@@ -0,0 +1,6 @@
+[
+ {
+ "rgbFilm": 1,
+ "duration": 0
+ }
+]
diff --git a/packages/webgal/public/game/animation/shake.json b/packages/webgal/public/game/animation/shake.json
new file mode 100644
index 000000000..47b5c8fb8
--- /dev/null
+++ b/packages/webgal/public/game/animation/shake.json
@@ -0,0 +1,30 @@
+[
+ {
+ "position": {
+ "x": 0,
+ "y": 0
+ },
+ "duration": 0
+ },
+ {
+ "position": {
+ "x": -100,
+ "y": 0
+ },
+ "duration": 250
+ },
+ {
+ "position": {
+ "x": 100,
+ "y": 0
+ },
+ "duration": 500
+ },
+ {
+ "position": {
+ "x": 0,
+ "y": 0
+ },
+ "duration": 250
+ }
+]
diff --git a/packages/webgal/public/game/animation/shockwaveIn.json b/packages/webgal/public/game/animation/shockwaveIn.json
new file mode 100644
index 000000000..41afc947b
--- /dev/null
+++ b/packages/webgal/public/game/animation/shockwaveIn.json
@@ -0,0 +1,14 @@
+[
+ {
+ "shockwaveFilter": 0,
+ "alpha": 1,
+ "radiusAlphaFilter": 0,
+ "duration": 0
+ },
+ {
+ "shockwaveFilter": 3.05,
+ "alpha": 1,
+ "radiusAlphaFilter": 1.05,
+ "duration": 2000
+ }
+]
diff --git a/packages/webgal/public/game/animation/shockwaveOut.json b/packages/webgal/public/game/animation/shockwaveOut.json
new file mode 100644
index 000000000..56719bdc5
--- /dev/null
+++ b/packages/webgal/public/game/animation/shockwaveOut.json
@@ -0,0 +1,12 @@
+[
+ {
+ "shockwaveFilter": 0,
+ "alpha": 1,
+ "duration": 0
+ },
+ {
+ "shockwaveFilter": 3,
+ "alpha": 1,
+ "duration": 2000
+ }
+]
diff --git a/packages/webgal/public/game/background/WebGAL_New_Enter_Image.png b/packages/webgal/public/game/background/WebGAL_New_Enter_Image.png
new file mode 100644
index 000000000..e83234b74
Binary files /dev/null and b/packages/webgal/public/game/background/WebGAL_New_Enter_Image.png differ
diff --git a/packages/webgal/public/game/background/WebGalEnter.png b/packages/webgal/public/game/background/WebGalEnter.png
new file mode 100644
index 000000000..50e07f661
Binary files /dev/null and b/packages/webgal/public/game/background/WebGalEnter.png differ
diff --git a/packages/webgal/public/game/background/bg.png b/packages/webgal/public/game/background/bg.png
new file mode 100644
index 000000000..127ca4d9f
Binary files /dev/null and b/packages/webgal/public/game/background/bg.png differ
diff --git a/packages/webgal/public/game/bgm/s_Title.mp3 b/packages/webgal/public/game/bgm/s_Title.mp3
new file mode 100644
index 000000000..b12645e94
Binary files /dev/null and b/packages/webgal/public/game/bgm/s_Title.mp3 differ
diff --git a/packages/webgal/public/game/config.txt b/packages/webgal/public/game/config.txt
new file mode 100644
index 000000000..3bc42af83
--- /dev/null
+++ b/packages/webgal/public/game/config.txt
@@ -0,0 +1,5 @@
+Game_name:欢迎使用WebGAL!;
+Game_key:0f87dstRg;
+Title_img:WebGAL_New_Enter_Image.png;
+Title_bgm:s_Title.mp3;
+Game_Logo:WebGalEnter.png;
diff --git a/packages/webgal/public/game/figure/1/closed_eyes.png b/packages/webgal/public/game/figure/1/closed_eyes.png
new file mode 100644
index 000000000..9900056c6
Binary files /dev/null and b/packages/webgal/public/game/figure/1/closed_eyes.png differ
diff --git a/packages/webgal/public/game/figure/1/closed_mouth.png b/packages/webgal/public/game/figure/1/closed_mouth.png
new file mode 100644
index 000000000..53892b017
Binary files /dev/null and b/packages/webgal/public/game/figure/1/closed_mouth.png differ
diff --git a/packages/webgal/public/game/figure/1/halfopen_mouth.png b/packages/webgal/public/game/figure/1/halfopen_mouth.png
new file mode 100644
index 000000000..c6a9427ec
Binary files /dev/null and b/packages/webgal/public/game/figure/1/halfopen_mouth.png differ
diff --git a/packages/webgal/public/game/figure/1/open_eyes.png b/packages/webgal/public/game/figure/1/open_eyes.png
new file mode 100644
index 000000000..53892b017
Binary files /dev/null and b/packages/webgal/public/game/figure/1/open_eyes.png differ
diff --git a/packages/webgal/public/game/figure/1/open_mouth.png b/packages/webgal/public/game/figure/1/open_mouth.png
new file mode 100644
index 000000000..9d6e1ed01
Binary files /dev/null and b/packages/webgal/public/game/figure/1/open_mouth.png differ
diff --git a/packages/webgal/public/game/figure/2/closed_eyes.png b/packages/webgal/public/game/figure/2/closed_eyes.png
new file mode 100644
index 000000000..a151f2c14
Binary files /dev/null and b/packages/webgal/public/game/figure/2/closed_eyes.png differ
diff --git a/packages/webgal/public/game/figure/2/closed_mouth.png b/packages/webgal/public/game/figure/2/closed_mouth.png
new file mode 100644
index 000000000..8e828d044
Binary files /dev/null and b/packages/webgal/public/game/figure/2/closed_mouth.png differ
diff --git a/packages/webgal/public/game/figure/2/halfopen_mouth.png b/packages/webgal/public/game/figure/2/halfopen_mouth.png
new file mode 100644
index 000000000..15cceaf66
Binary files /dev/null and b/packages/webgal/public/game/figure/2/halfopen_mouth.png differ
diff --git a/packages/webgal/public/game/figure/2/open_eyes.png b/packages/webgal/public/game/figure/2/open_eyes.png
new file mode 100644
index 000000000..8e828d044
Binary files /dev/null and b/packages/webgal/public/game/figure/2/open_eyes.png differ
diff --git a/packages/webgal/public/game/figure/2/open_mouth.png b/packages/webgal/public/game/figure/2/open_mouth.png
new file mode 100644
index 000000000..6c74fb119
Binary files /dev/null and b/packages/webgal/public/game/figure/2/open_mouth.png differ
diff --git a/packages/webgal/public/game/figure/3/closed_eyes.png b/packages/webgal/public/game/figure/3/closed_eyes.png
new file mode 100644
index 000000000..e32445b54
Binary files /dev/null and b/packages/webgal/public/game/figure/3/closed_eyes.png differ
diff --git a/packages/webgal/public/game/figure/3/closed_mouth.png b/packages/webgal/public/game/figure/3/closed_mouth.png
new file mode 100644
index 000000000..7eb1de513
Binary files /dev/null and b/packages/webgal/public/game/figure/3/closed_mouth.png differ
diff --git a/packages/webgal/public/game/figure/3/halfopen_mouth.png b/packages/webgal/public/game/figure/3/halfopen_mouth.png
new file mode 100644
index 000000000..5b845144b
Binary files /dev/null and b/packages/webgal/public/game/figure/3/halfopen_mouth.png differ
diff --git a/packages/webgal/public/game/figure/3/open_eyes.png b/packages/webgal/public/game/figure/3/open_eyes.png
new file mode 100644
index 000000000..7eb1de513
Binary files /dev/null and b/packages/webgal/public/game/figure/3/open_eyes.png differ
diff --git a/packages/webgal/public/game/figure/3/open_mouth.png b/packages/webgal/public/game/figure/3/open_mouth.png
new file mode 100644
index 000000000..f4df6005d
Binary files /dev/null and b/packages/webgal/public/game/figure/3/open_mouth.png differ
diff --git a/packages/webgal/public/game/figure/miniavatar.png b/packages/webgal/public/game/figure/miniavatar.png
new file mode 100644
index 000000000..fc5d484e4
Binary files /dev/null and b/packages/webgal/public/game/figure/miniavatar.png differ
diff --git a/packages/webgal/public/game/figure/stand.png b/packages/webgal/public/game/figure/stand.png
new file mode 100644
index 000000000..eca86cc2a
Binary files /dev/null and b/packages/webgal/public/game/figure/stand.png differ
diff --git a/packages/webgal/public/game/figure/stand2.png b/packages/webgal/public/game/figure/stand2.png
new file mode 100644
index 000000000..b7c9d4bee
Binary files /dev/null and b/packages/webgal/public/game/figure/stand2.png differ
diff --git a/packages/webgal/public/game/scene/demo_animation.txt b/packages/webgal/public/game/scene/demo_animation.txt
new file mode 100644
index 000000000..9cd74655a
--- /dev/null
+++ b/packages/webgal/public/game/scene/demo_animation.txt
@@ -0,0 +1,19 @@
+bgm:s_Title.mp3 -volume=80 -enter=3000;
+unlockBgm:s_Title.mp3 -name=ようこそ;
+intro:■初めに| このデモゲームで使用している画像素材は、AI生成画像です;
+intro:■クレジット| 「VOICEVOX:小夜/SAYO」;
+intro:まばたき、口パクのアニメーションテストです;
+changeBg:bg.png;
+changeFigure:1/open_mouth.png -left -next -id=test1 -animationFlag=on -eyesOpen=1/open_eyes.png -eyesClose=1/closed_eyes.png -mouthOpen=1/open_mouth.png -mouthHalfOpen=1/halfopen_mouth.png -mouthClose=1/closed_mouth.png;
+WebGAL:こんにちは -001_小夜SAYO(ノーマル)_こんにちは。.wav -fontSize=default -animationFlag=on -figureId=test1;
+WebGALは、かつてないまったく新しいビジュアルノベルエンジンです。 -003_小夜SAYO(ノーマル)_ウェブギャルは、か….wav -fontSize=default -animationFlag=on -figureId=test1;
+changeFigure:2/open_mouth.png -next -id=test2 -animationFlag=on -eyesOpen=2/open_eyes.png -eyesClose=2/closed_eyes.png -mouthOpen=2/open_mouth.png -mouthHalfOpen=2/halfopen_mouth.png -mouthClose=2/closed_mouth.png;
+WebGAL2:WebGALへようこそ -002_小夜SAYO(ノーマル)_ウェブギャルへよう….wav -fontSize=large -right -animationFlag=on;
+Web技術を使用して開発されたエンジンであるため、ウェブページで優れたパフォーマンスを発揮します。 -004_小夜SAYO(ノーマル)_Web技術を使用し….wav -fontSize=default -right -animationFlag=on -figureId=test2;
+changeFigure:3/open_mouth.png -right -next -id=test3 -animationFlag=on -eyesOpen=3/open_eyes.png -eyesClose=3/closed_eyes.png -mouthOpen=3/open_mouth.png -mouthHalfOpen=3/halfopen_mouth.png -mouthClose=3/closed_mouth.png;
+WebGAL3:この機能のおかげで、ウェブサイト上に公開すると、プレイヤーはリンクを押すだけで -005_小夜SAYO(ノーマル)_この機能のおかげで….wav -fontSize=default -id -figureId=test3;
+いつでも、どこでも、ウェブサイト上でゲームをプレイすることができます! -006_小夜SAYO(ノーマル)_いつでも、どこでも….wav -fontSize=default -id -figureId=test3;
+changeFigure: -id=test1;
+changeFigure: -id=test2;
+changeFigure: -id=test3;
+choose:简体中文:demo_zh_cn.txt|日本語:demo_ja.txt|English:demo_en.txt;
diff --git a/packages/webgal/public/game/scene/demo_changeConfig.txt b/packages/webgal/public/game/scene/demo_changeConfig.txt
new file mode 100644
index 000000000..8666897a8
--- /dev/null
+++ b/packages/webgal/public/game/scene/demo_changeConfig.txt
@@ -0,0 +1,15 @@
+webgal:我会修改标题 -hold;
+setVar:Game_name=WebGal_Change -global;
+webgal:游戏标题已经被我修改为{Game_name} -hold;
+webgal:我会修改游戏背景 -hold;
+setVar:Title_img=bg.png -global;
+webgal:游戏背景已经被我修改为{Title_img} -hold;
+webgal:获取Debug值:{Debug} -hold;
+setVar:GameNum=15 -global;
+webgal:获取并修改GameNum的值:{GameNum} -hold;
+setVar:GameNum_Double=GameNum_Double + 5;
+webgal:获取GameNum_Double的值,但我不会修改它原本的值:{GameNum_Double} -hold;
+webgal:获取bgm声音:{$stage.bgm.volume} -hold;
+setVar:bgm_calc= $stage.bgm.volume * 2 ;
+webgal:获取bgm声音的两倍是{bgm_calc} -hold;
+end;
diff --git a/packages/webgal/public/game/scene/demo_en.txt b/packages/webgal/public/game/scene/demo_en.txt
new file mode 100644
index 000000000..55c8918a8
--- /dev/null
+++ b/packages/webgal/public/game/scene/demo_en.txt
@@ -0,0 +1,52 @@
+bgm:s_Title.mp3 -volume=80 -enter=3000;
+unlockBgm:s_Title.mp3 -name=welcome;
+intro:*Getting started| The image materials used in this demo game are AI-generated images;
+intro:*Credit| Created By ondoku3.com;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良い夜;
+changeFigure:stand.png -left -enter=enter-from-left -next;
+miniAvatar:miniavatar.png;
+WebGAL:Hello -e001_Hello.mp3;
+Welcome to WebGAL! -e002_Welcome_to_WebGAL.mp3;
+WebGAL is a completely new web visual engine never seen before. -e003_WebGAL_is_a_completely_new_web.mp3;
+changeFigure:stand2.png -right -next;
+It is an engine developed using web technology, so it performs well on web pages. -e004_It_is_an_engine_developedusing_web.mp3;
+Thanks to this feature, once published on your website's platform,|players can simply click a link to play your game on your website anytime, anywhere! -e005_Thanks_to_this_ feature_once.mp3;
+setAnimation:move-front-and-back -target=fig-left -next;
+Very attractive, don't you think? -e006_Very attractive.mp3;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+In addition, WebGAL allows you to add animations and special effects to create high quality games. -e007_In_addition_ WebGAL_allows you.mp3;
+pixiInit;
+pixiPerform:snow;
+For example, let's make it snow as a special effect. -e008_For_example_let's_make.mp3;
+As you can see, WebGAL has attractive features. |Try it out and experience it. -e009_As_you_can see_WebGAL.mp3;
+That's all for the feature introduction. -e010_That's_all_for_the_feature introduction.mp3;
+Next,I will introduce the history and trivia of WebGAL. -e011_Next_I_will introduce_the_ history.mp3;
+pixiInit;
+choose:WebGAL History:choose1|WebGAL Trivia:choose2;
+;選択1
+label:choose1;
+WebGAL was developed to make it easier for more people to create their own visual novel games. -e012_WebGAL _was_developed to_make_it_ easier.mp3;
+Initially, WebGAL had very few features, supporting only standing and background image display, audio playback, and choices. -e013_Initially_ WebGAL_had_very_few_features.mp3;
+After a long development period, it has now been reborn as an engine with many excellent functions. -e014_After_a_long _development period.mp3;
+Additionally, the release of the WebGAL editor makes creating and outputting games simpler and easier. -e015_Additionally,_the release_of_the WebGAL_editor.mp3;
+jumpLabel:end;
+;選択2
+label:choose2;
+The WebGAL project has reached 1000 stars on GitHub in just under a year! -e016_The_WebGAL project_has reached_1000.mp3;
+The development process of WebGAL is a process of learning development while developing. -e017_The_development process_of WebGAL_is_a process.mp3;
+WebGAL's scripting language was designed from the ground up to simplify production difficulty! -e018_WebGAL's _scripting language_was designed_from the_ground.mp3;
+;エンド
+label:end;
+;changeFigure:none -left -next;
+;changeFigure:stand.png -next;
+The WebGAL project supports many keyboard shortcuts and the ability to go back to scenes to more closely match the functionality of a visual novel game engine for the desktop. -e019_The_WebGAL_project_supports_many_keyboard shortcuts.mp3;
+Try pressing the "Backlog" button on the menu or scrolling the mouse wheel up! -e020_Try_pressing the_Backlog button.mp3;
+Features like quick save, quick load, auto mode and skip mode are also supported. -e021_Features_like_quick_save_quick_load.mp3;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+For developers who are developing games for the first time, we provide beautiful general-purpose UI and rich preset functions and animations. -e022_For developers_who_ are_developing_ games.mp3;
+So you can start making games quickly. -e023_So_you_can_ start_making_ games_quickly.mp3;
+We hope that your work will be exhibited at WebGAL. -e024_We_hope_ that_your_work.mp3;
+Thank you for your interest in the WebGAL project! -e025_Thank_you_ for_your_interest_in_the_WebGAL_project!.mp3;
+end;
diff --git a/packages/webgal/public/game/scene/demo_escape.txt b/packages/webgal/public/game/scene/demo_escape.txt
new file mode 100644
index 000000000..f0f4cd023
--- /dev/null
+++ b/packages/webgal/public/game/scene/demo_escape.txt
@@ -0,0 +1,3 @@
+intro:我会显示出来:\:\,\.\;不信你看看 -hold;
+WebGal:我会显示出来:\:\,\.\;不信你看看;
+choose:我会显示出来:\:\,\.\;不信你看看;
diff --git a/packages/webgal/public/game/scene/demo_ja.txt b/packages/webgal/public/game/scene/demo_ja.txt
new file mode 100644
index 000000000..6cf5aad05
--- /dev/null
+++ b/packages/webgal/public/game/scene/demo_ja.txt
@@ -0,0 +1,64 @@
+bgm:s_Title.mp3 -volume=80 -enter=3000;
+unlockBgm:s_Title.mp3 -name=ようこそ;
+intro:■初めに| このデモゲームで使用している画像素材は、AI生成画像です;
+intro:■クレジット| 「VOICEVOX:小夜/SAYO」;
+changeBg:bg.png -next;
+unlockCg:bg.png -name=良い夜; // CGのロックを解いて、名前をつけます。
+changeFigure:stand.png -left -enter=enter-from-left -next;
+miniAvatar:miniavatar.png;
+WebGAL:こんにちは。 -001_小夜SAYO(ノーマル)_こんにちは。.wav;
+WebGALへようこそ! -002_小夜SAYO(ノーマル)_ウェブギャルへよう….wav;
+WebGALは、かつてないまったく新しいビジュアルノベルエンジンです。 -003_小夜SAYO(ノーマル)_ウェブギャルは、か….wav;
+changeFigure:stand2.png -right -next;
+Web技術を使用して開発されたエンジンであるため、ウェブページで優れたパフォーマンスを発揮します。 -004_小夜SAYO(ノーマル)_Web技術を使用し….wav;
+この機能のおかげで、ウェブサイト上に公開すると、プレイヤーはリンクを押すだけで、 -005_小夜SAYO(ノーマル)_この機能のおかげで….wav;
+いつでも、どこでも、ウェブサイト上でゲームをプレイすることができます! -006_小夜SAYO(ノーマル)_いつでも、どこでも….wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+とっても、魅力的ではありませんか? -007_小夜SAYO(ノーマル)_とっても、魅力的で….wav;
+changeFigure:none -right -next;
+setAnimation:l2c -target=fig-left -next;
+さらに、WebGALでは、アニメーションや特殊効果を追加することができ、クオリティの高いゲームを作成することができます。 -008_小夜SAYO(ノーマル)_さらに、ウェブギャ….wav;
+pixiInit;
+pixiPerform:snow;
+例えば、特殊効果として、雪を降らせてみます。 -009_小夜SAYO(ノーマル)_例えば、特殊効果と….wav;
+こんな感じに、WebGALには、魅力的な機能があります。| ぜひ、使ってみて体験してみてください。 -010_小夜SAYO(ノーマル)_こんな感じに、ウェ….wav;
+機能の紹介は以上です。 -011_小夜SAYO(ノーマル)_機能の紹介は以上で….wav;
+次に、WebGALの歴史と豆知識について紹介します。 -012_小夜SAYO(ノーマル)_次に、ウェブギャル….wav;
+興味があれば、引き続きお聞きください。 -013_小夜SAYO(ノーマル)_興味があれば、引き….wav;
+pixiInit;
+choose:WebGAL 歴史:choose1|WebGAL 豆知識:choose2;
+
+;選択1
+label:choose1;
+WebGALは、より多くの人が自分のビジュアルノベルゲームを簡単に作成できるようにと、開発されました。 -014_小夜SAYO(ノーマル)_ウェブギャルは、よ….wav;
+当初のWebGALには、機能がほとんどなく、立ち絵と背景画像の表示、音声再生、選択肢の選択機能のみをサポートしていました。 -015_小夜SAYO(ノーマル)_当初のウェブギャル….wav;
+長きにわたる開発期間を経て、今では多くの優れた機能があるエンジンとして、生まれ変わりました。 -016_小夜SAYO(ノーマル)_長きにわたる開発期….wav;
+さらに、WebGALエディターのリリースにより、ゲームの作成と出力がよりシンプルかつ容易になりました。 -017_小夜SAYO(ノーマル)_さらに、ウェブギャ….wav;
+jumpLabel:end;
+
+;選択2
+label:choose2;
+WebGALプロジェクトは、わずか1年足らずでGitHubで1000スターを獲得しました! -019_小夜SAYO(ノーマル)_ウェブギャルプロジ….wav;
+WebGALの開発過程は、開発しながら開発を学ぶプロセスです。 -020_小夜SAYO(ノーマル)_ウェブギャルの開発….wav;
+そのため、彼女は3回のリファクタリングを経験し、毎回ほぼゼロからやり直す形となりました。 -021_小夜SAYO(ノーマル)_そのため、彼女は3….wav;
+WebGALのスクリプト言語は、制作の難易度を簡素化するためにゼロから設計されました! -022_小夜SAYO(ノーマル)_ウェブギャルのスク….wav;
+;
+;l2d
+;label:l2d;
+;changeFigure:hiyori/hiyori_pro_t11.model3.json -left -motion=Idle -next;
+;WebGALプロジェクトでは、現在Live2Dの実験的なサポートも始まっています!これからは正式な機能として成長する可能性もあります! -023_小夜SAYO(ノーマル)_ウェブギャルプロジ….wav
+;
+;エンド
+label:end;
+;changeFigure:none -left -next;
+;changeFigure:stand.png -next;
+WebGALプロジェクトは、デスクトップ向けのビジュアルノベルゲームエンジンの機能により近づくため、多くのショートカットキーとシーンの戻る機能をサポートしています。 -024_小夜SAYO(ノーマル)_ウェブギャルプロジ….wav;
+メニューの「LOG」ボタンを押すか、マウスホイールを上にスクロールしてみてください! -025_小夜SAYO(ノーマル)_メニューのログボタ….wav;
+豊富な設定、クイックセーブ、クイックロード、自動モード、スキップモードなどの機能もサポートされています。 -026_小夜SAYO(ノーマル)_豊富な設定、クイッ….wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+初めてゲームを開発する開発者のために、美しい汎用UIと豊富なプリセット機能やアニメーションを提供しています。 -028_小夜SAYO(ノーマル)_初めてゲームを開発….wav
+ですから、心配せずに素早くゲーム制作を始めることができます。 -029_小夜SAYO(ノーマル)_ですから、心配せず….wav;
+WebGALプロジェクトチームは、あなたの作品がWebGALで展示されることを期待しています! -030_小夜SAYO(ノーマル)_ウェブギャルプロジ….wav
+リンクを1つ用意するだけで、無数のユーザーがあなたの作品をすぐに楽しめるようになります。 -031_小夜SAYO(ノーマル)_リンクを1つ用意す….wav;
+WebGALプロジェクトへのご注目、ありがとうございます! -032_小夜SAYO(ノーマル)_ウェブギャルプロジ….wav;
+end;
diff --git a/packages/webgal/public/game/scene/demo_var.txt b/packages/webgal/public/game/scene/demo_var.txt
new file mode 100644
index 000000000..9b31b80f5
--- /dev/null
+++ b/packages/webgal/public/game/scene/demo_var.txt
@@ -0,0 +1,11 @@
+setVar:bg=bg.png;
+changeBg:{bg} -next;
+setVar:speaker=WebGAL;
+{speaker}:Background now is {bg}.;
+{speaker}:\{bg\} is not use interpolation.;
+setVar:a=3;
+setVar:bg=WebGalEnter.png -when=a>2;
+changeBg:{bg} -next;
+setVar:func=variable interpolation;
+{speaker}:Bg changed! Welcome to {speaker} {func}!;
+end;
diff --git a/packages/webgal/public/game/scene/demo_zh_cn.txt b/packages/webgal/public/game/scene/demo_zh_cn.txt
new file mode 100644
index 000000000..c6230e31b
--- /dev/null
+++ b/packages/webgal/public/game/scene/demo_zh_cn.txt
@@ -0,0 +1,74 @@
+bgm:s_Title.mp3 -volume=80 -enter=3000;
+unlockBgm:s_Title.mp3 -name=雲を追いかけて;
+intro:你好|欢迎来到 {egine} 的世界;
+changeBg:WebGalEnter.png -next;
+setTransition: -target=bg-main -exit=shockwaveOut;
+:你好|欢迎来到 {egine} 的世界;
+changeBg:bg.png -next;
+setTransition: -target=bg-main -enter=shockwaveIn -next;
+unlockCg:bg.png -name=良い夜; // 解锁CG并赋予名称
+changeFigure:stand.png -left -enter=enter-from-left -next;
+miniAvatar:miniavatar.png;
+{heroine}:欢迎使用 {egine}!这是一款全新的网页端视觉小说引擎。 -v1.wav;
+changeFigure:stand2.png -right -next;
+{egine} 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav;
+由于这个特性,如果你将 {egine} 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav;
+setAnimation:move-front-and-back -target=fig-left -continue;
+听起来是不是非常吸引人? -v4.wav;
+changeFigure:none -right -next;
+setTransform:{"position": {"x": 500,"y": 0}} -target=fig-left -next;
+{egine} 引擎也具有动画系统和特效系统,使用 {egine} 开发的游戏可以拥有很好的表现效果。 -v5.wav;
+pixiInit;
+pixiPerform:snow;
+比如,这个下起小雪的特效。 -v6.wav;
+除此以外,分支选择的功能也必不可少。 -v7.wav;
+pixiInit;
+WebGAL:接下来介绍一些新版本功能!
+WebGAL:比如这个[注](zhù)[音](yīn)功能,可以为游戏带来更好的体验!
+WebGAL:我们也支持了[文本拓展语法](style=color:#B5495B\;),可以为[文](wen)[本](ben)带来[富文本支持](style-alltext=font-style:italic\; style=color:#66327C\;)、交互等特性。
+WebGAL:新版本添加了特性:获取用户输入,你要尝试一下吗?
+choose:尝试一下:userInput|算了吧:toNextPart;
+
+label:userInput;
+getUserInput:name -title=如何称呼你 -buttonText=确认;
+WebGAL:很高兴遇见你,{name}!
+jumpLabel:toNextPart;
+
+label:toNextPart;
+你可以通过以下两个分支了解 {egine} 的更多故事。 -v8.wav;
+choose:{egine} 发展历程:choose1|{egine} 冷知识:choose2;
+
+;分支1
+label:choose1;
+{egine} 的开发初衷是能够让更多人可以更方便地制作属于自己的视觉小说。 -v9.wav;
+在一开始,{egine} 只具备很少的功能,仅仅能支持立绘和背景的显示、语音播放和分支选择。 -v10.wav;
+setAnimation:move-front-and-back -target=fig-left -next;
+在经历一年的发展后,现在已经是支持了很多优秀的表现效果的引擎了! -v11.wav;
+除此以外,{egine} 编辑器的加入也使得制作和导出一个游戏更加方便快捷。 -v12.wav;
+jumpLabel:end;
+
+;分支2
+label:choose2;
+这个演示游戏使用的素材是 AI 生成的。 -v13.wav;
+{egine} 项目只用了一年不到就在 GitHub 获得了 1000 星标! -v14.wav;
+{egine} 的开发历程,是一个在开发中学习开发的过程。因此她经历了3次重构,并且每次几乎都是推倒重来式的。 -v15.wav;
+{egine} 的脚本语言是为了简化制作难度而全新设计的! -v16.wav;
+;
+;l2d
+;label:l2d;
+;changeFigure:hiyori/hiyori_pro_t11.model3.json -left -motion=Idle -next;
+;{egine} 项目现在也开始尝试实验性支持 live2D!今后可能会成为一个正式功能!
+;
+;结束分支
+label:end;
+;changeFigure:none -left -next;
+;changeFigure:stand.png -next;
+为了更接近桌面端视觉小说引擎的能力,我们支持很多快捷键以及可跳转的剧情回溯。 -v17.wav;
+按下菜单中的“回想”按钮或者向上滚动鼠标滚轮试试吧! -v18.wav;
+快速存读挡、丰富的选项、自动播放以及快进等功能,也是应有尽有。 -v19.wav;
+setTempAnimation:[{"position": {"x": 500,"y": 0},"duration": 0},{"position": {"x": 400,"y": 0},"duration": 250},{"position": {"x": 600,"y": 0},"duration": 500},{"position": {"x": 500,"y": 0},"duration": 250}] -target=fig-left -next;
+当然,这不算什么,因为大多数成熟的引擎都有这些功能。但是在 Web 端,这却是很少见的。 -v20.wav;
+我们为可能初次开发游戏的开发者提供了美观的通用 UI 和丰富的预设功能与动画。所以你可以没有顾虑地快速开始制作游戏。 -v21.wav;
+{egine} 项目组期待你的作品能够在 {egine} 上呈现!只需要一个链接就可以让万千用户立刻开始享受你的作品。 -v22.wav;
+感谢你关注 {egine} 项目! -v23.wav;
+end;
diff --git a/packages/webgal/public/game/scene/function_test.txt b/packages/webgal/public/game/scene/function_test.txt
new file mode 100644
index 000000000..8514bd9d0
--- /dev/null
+++ b/packages/webgal/public/game/scene/function_test.txt
@@ -0,0 +1 @@
+choose:Lip Sync Animation Test:demo_animation.txt | Variable interpolation test:demo_var.txt | Change Config:demo_changeConfig.txt;
diff --git a/packages/webgal/public/game/scene/start.txt b/packages/webgal/public/game/scene/start.txt
new file mode 100644
index 000000000..1e072c7c3
--- /dev/null
+++ b/packages/webgal/public/game/scene/start.txt
@@ -0,0 +1,3 @@
+setVar:heroine=WebGAL;
+setVar:egine=WebGAL;
+choose:简体中文:demo_zh_cn.txt|日本語:demo_ja.txt|English:demo_en.txt|Test:function_test.txt;
diff --git a/packages/webgal/public/game/template/Stage/Choose/choose.scss b/packages/webgal/public/game/template/Stage/Choose/choose.scss
new file mode 100644
index 000000000..4040078bc
--- /dev/null
+++ b/packages/webgal/public/game/template/Stage/Choose/choose.scss
@@ -0,0 +1,54 @@
+.Choose_Main {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-flow: column;
+ justify-content: center;
+ align-items: center;
+ z-index: 13;
+ background: rgba(0, 0, 0, 0.05);
+}
+
+.Choose_item {
+ font-family: "WebgalUI", serif;
+ cursor: pointer;
+ min-width: 50%;
+ padding: 0.25em 1em 0.25em 1em;
+ font-size: 265%;
+ color: #8E354A;
+ text-align: center;
+ border-radius: 4px;
+ border: 3px solid rgba(0, 0, 0, 0);
+ box-shadow: 0 0 25px rgba(0, 0, 0, 0.25);
+ background: rgba(255, 255, 255, 0.65);
+ margin: 0.25em 0 0.25em 0;
+ transition: background-color 0.5s, border 0.5s, font-weight 0.5s, box-shadow 0.5s;
+
+ &:hover {
+ background: rgba(255, 255, 255, 0.9);
+ box-shadow: 0 0 25px rgba(0, 0, 0, 0.35);
+ border: 3px solid #8E354A;
+ }
+}
+
+.Choose_item_disabled {
+ font-family: "WebgalUI", serif;
+ cursor: not-allowed;
+ min-width: 50%;
+ padding: 0.25em 1em 0.25em 1em;
+ font-size: 265%;
+ color: rgba(142, 53, 74, 0.5);
+ text-align: center;
+ border-radius: 4px;
+ border: 3px solid rgba(0, 0, 0, 0);
+ box-shadow: 0 0 25px rgba(0, 0, 0, 0.25);
+ background: rgba(255, 255, 255, 0.5);
+ margin: 0.25em 0 0.25em 0;
+ transition: background-color 0.5s, border 0.5s, font-weight 0.5s, box-shadow 0.5s;
+}
+
+.Choose_item_outer {
+ color: #000;
+ min-width: 50%;
+}
diff --git a/packages/webgal/public/game/template/Stage/TextBox/textbox.scss b/packages/webgal/public/game/template/Stage/TextBox/textbox.scss
new file mode 100644
index 000000000..23120e12d
--- /dev/null
+++ b/packages/webgal/public/game/template/Stage/TextBox/textbox.scss
@@ -0,0 +1,155 @@
+.TextBox_main {
+ z-index: 3;
+ position: absolute;
+ right: 25px;
+ min-height: 330px;
+ max-height: 330px;
+ background-blend-mode: darken;
+ border-radius: 165px 20px 20px 165px;
+ bottom: 20px;
+ left: 275px;
+ font-weight: bold;
+ color: white;
+ padding: 1em 50px 70px 200px;
+ box-sizing: border-box;
+ display: flex;
+ flex-flow: column;
+ align-items: flex-start;
+ letter-spacing: 0.2em;
+ transition: left 0.33s;
+}
+
+.TextBox_main_miniavatarOff {
+ left: 25px;
+}
+
+.TextBox_Background {
+ z-index: 2;
+ background: linear-gradient(rgba(245, 247, 250, 1) 0%, rgba(189, 198, 222, 1) 100%);
+}
+
+@keyframes showSoftly {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+
+.TextBox_textElement_start {
+ position: relative;
+ animation: TextDelayShow 1000ms ease-out forwards;
+ opacity: 0;
+}
+
+.outer {
+ position: absolute;
+ white-space: nowrap;
+ left: 0;
+ top: 0;
+ background-image: linear-gradient(#0B346E 0%, #141423 100%);
+ background-clip: text;
+ -webkit-background-clip: text;
+ color: transparent;
+ z-index: 2;
+}
+
+.inner {
+ white-space: nowrap;
+ position: absolute;
+ left: 0;
+ top: 0;
+ -webkit-text-stroke: 0.1em rgba(255, 255, 255, 1);
+ z-index: 1;
+}
+
+.zhanwei {
+ color: transparent;
+ white-space: nowrap;
+}
+
+
+.TextBox_textElement_Settled {
+ position: relative;
+ opacity: 1;
+}
+
+
+.TextBox_showName {
+ font-size: 85%;
+ padding: 0 2em 0 2em;
+ position: absolute;
+ left: 150px;
+ top: -68px;
+ height: 80px;
+ line-height: 68px;
+ border-radius: 40px;
+ z-index: 3;
+ border: 4px solid rgba(255, 255, 255, 0);
+}
+
+.TextBox_ShowName_Background {
+ z-index: 2;
+ background: rgba(11, 52, 110, 1);
+ border: 4px solid rgba(255, 255, 255, 0.75);
+ box-shadow: 3px 3px 10px rgba(100, 100, 100, 0.5);
+}
+
+@keyframes TextDelayShow {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+
+.miniAvatarContainer {
+ position: absolute;
+ height: 450px;
+ width: 450px;
+ bottom: 0;
+ left: -250px;
+ border-radius: 100% 0 0 100%;
+ overflow: hidden;
+}
+
+.miniAvatarImg {
+ max-height: 100%;
+ max-width: 100%;
+ position: absolute;
+ bottom: 0;
+ filter: drop-shadow(15px 0 3px rgba(0, 0, 0, 0.5));
+}
+
+.nameContainer {
+ position: absolute;
+ left: 2em;
+ top: -3.5em;
+}
+
+.outerName {
+ position: absolute;
+ left: 0;
+ top: 0;
+ background: linear-gradient(150deg, rgb(255, 255, 255) 0%, rgb(255, 255, 255) 35%, rgb(165, 212, 228) 100%);
+ background-clip: text;
+ -webkit-background-clip: text;
+ color: transparent;
+ z-index: 2;
+}
+
+.innerName {
+ position: absolute;
+ left: 0;
+ top: 0;
+ z-index: 1;
+}
+
+.text {
+ line-height: 1.9em;
+ overflow: hidden;
+}
diff --git a/packages/webgal/public/game/template/UI/Title/title.scss b/packages/webgal/public/game/template/UI/Title/title.scss
new file mode 100644
index 000000000..48073c96b
--- /dev/null
+++ b/packages/webgal/public/game/template/UI/Title/title.scss
@@ -0,0 +1,58 @@
+.Title_main {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ z-index: 13;
+}
+
+.Title_buttonList {
+ font-family: "思源宋体", serif;
+ display: flex;
+ position: absolute;
+ left: 0;
+ min-width: 25%;
+ height: 100%;
+ justify-content: center;
+ align-items: flex-start;
+ flex-flow: column;
+ transition: background 0.75s;
+ padding-left: 120px;
+}
+
+.Title_button {
+ font-weight: bold;
+ text-align: center;
+ flex: 0 1 auto;
+ cursor: pointer;
+ padding: 1em 2em 1em 2em;
+ margin: 20px 0;
+ transition: all 0.33s;
+ background: rgba(255, 255, 255, 0.15);
+ backdrop-filter: blur(5px);
+ border-radius: 4px;
+ transform: skewX(-10deg);
+ background: linear-gradient(to right, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.1));
+
+ &:hover {
+ text-shadow: 0 0 10px rgba(255, 255, 255, 1);
+ padding: 1em 6em 1em 3em;
+ }
+}
+
+.Title_button_text {
+ color: transparent;
+ background: linear-gradient(135deg, #fdfbfb 0%, #dcddde 100%);
+ -webkit-background-clip: text;
+ padding: 0 0.5em 0 0.5em;
+ font-size: 200%;
+ font-family: WebgalUI, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+ letter-spacing: 0.15em;
+}
+
+.Title_backup_background {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ z-index: 13;
+ background: linear-gradient(135deg, #fdfbfb 0%, #dcddde 100%);
+}
diff --git a/packages/webgal/public/game/template/template.json b/packages/webgal/public/game/template/template.json
new file mode 100644
index 000000000..3194a3688
--- /dev/null
+++ b/packages/webgal/public/game/template/template.json
@@ -0,0 +1,4 @@
+{
+ "name":"Default Template",
+ "webgal-version":"4.5.9"
+}
diff --git a/packages/webgal/public/game/tex/cherryBlossoms.png b/packages/webgal/public/game/tex/cherryBlossoms.png
new file mode 100644
index 000000000..28b4bc013
Binary files /dev/null and b/packages/webgal/public/game/tex/cherryBlossoms.png differ
diff --git a/packages/webgal/public/game/tex/raindrop.png b/packages/webgal/public/game/tex/raindrop.png
new file mode 100644
index 000000000..4e1b8cd12
Binary files /dev/null and b/packages/webgal/public/game/tex/raindrop.png differ
diff --git a/packages/webgal/public/game/tex/snowFlake_min.png b/packages/webgal/public/game/tex/snowFlake_min.png
new file mode 100644
index 000000000..181856280
Binary files /dev/null and b/packages/webgal/public/game/tex/snowFlake_min.png differ
diff --git a/packages/webgal/public/game/userStyleSheet.css b/packages/webgal/public/game/userStyleSheet.css
new file mode 100644
index 000000000..e69de29bb
diff --git "a/packages/webgal/public/game/vocal/001_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\223\343\202\223\343\201\253\343\201\241\343\201\257\343\200\202.wav" "b/packages/webgal/public/game/vocal/001_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\223\343\202\223\343\201\253\343\201\241\343\201\257\343\200\202.wav"
new file mode 100644
index 000000000..a08d8d5e4
Binary files /dev/null and "b/packages/webgal/public/game/vocal/001_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\223\343\202\223\343\201\253\343\201\241\343\201\257\343\200\202.wav" differ
diff --git "a/packages/webgal/public/game/vocal/002_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\270\343\202\210\343\201\206\342\200\246.wav" "b/packages/webgal/public/game/vocal/002_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\270\343\202\210\343\201\206\342\200\246.wav"
new file mode 100644
index 000000000..176bf13df
Binary files /dev/null and "b/packages/webgal/public/game/vocal/002_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\270\343\202\210\343\201\206\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/003_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\257\343\200\201\343\201\213\342\200\246.wav" "b/packages/webgal/public/game/vocal/003_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\257\343\200\201\343\201\213\342\200\246.wav"
new file mode 100644
index 000000000..48d85ce7b
Binary files /dev/null and "b/packages/webgal/public/game/vocal/003_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\257\343\200\201\343\201\213\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/004_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_Web\346\212\200\350\241\223\343\202\222\344\275\277\347\224\250\343\201\227\342\200\246.wav" "b/packages/webgal/public/game/vocal/004_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_Web\346\212\200\350\241\223\343\202\222\344\275\277\347\224\250\343\201\227\342\200\246.wav"
new file mode 100644
index 000000000..2237bfd78
Binary files /dev/null and "b/packages/webgal/public/game/vocal/004_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_Web\346\212\200\350\241\223\343\202\222\344\275\277\347\224\250\343\201\227\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/005_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\223\343\201\256\346\251\237\350\203\275\343\201\256\343\201\212\343\201\213\343\201\222\343\201\247\342\200\246.wav" "b/packages/webgal/public/game/vocal/005_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\223\343\201\256\346\251\237\350\203\275\343\201\256\343\201\212\343\201\213\343\201\222\343\201\247\342\200\246.wav"
new file mode 100644
index 000000000..2d83864d0
Binary files /dev/null and "b/packages/webgal/public/game/vocal/005_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\223\343\201\256\346\251\237\350\203\275\343\201\256\343\201\212\343\201\213\343\201\222\343\201\247\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/006_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\204\343\201\244\343\201\247\343\202\202\343\200\201\343\201\251\343\201\223\343\201\247\343\202\202\342\200\246.wav" "b/packages/webgal/public/game/vocal/006_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\204\343\201\244\343\201\247\343\202\202\343\200\201\343\201\251\343\201\223\343\201\247\343\202\202\342\200\246.wav"
new file mode 100644
index 000000000..91bedcf59
Binary files /dev/null and "b/packages/webgal/public/game/vocal/006_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\204\343\201\244\343\201\247\343\202\202\343\200\201\343\201\251\343\201\223\343\201\247\343\202\202\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/007_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\250\343\201\243\343\201\246\343\202\202\343\200\201\351\255\205\345\212\233\347\232\204\343\201\247\342\200\246.wav" "b/packages/webgal/public/game/vocal/007_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\250\343\201\243\343\201\246\343\202\202\343\200\201\351\255\205\345\212\233\347\232\204\343\201\247\342\200\246.wav"
new file mode 100644
index 000000000..96e203ca6
Binary files /dev/null and "b/packages/webgal/public/game/vocal/007_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\250\343\201\243\343\201\246\343\202\202\343\200\201\351\255\205\345\212\233\347\232\204\343\201\247\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/008_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\225\343\202\211\343\201\253\343\200\201\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\342\200\246.wav" "b/packages/webgal/public/game/vocal/008_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\225\343\202\211\343\201\253\343\200\201\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\342\200\246.wav"
new file mode 100644
index 000000000..b088ccb31
Binary files /dev/null and "b/packages/webgal/public/game/vocal/008_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\225\343\202\211\343\201\253\343\200\201\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/009_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\344\276\213\343\201\210\343\201\260\343\200\201\347\211\271\346\256\212\345\212\271\346\236\234\343\201\250\342\200\246.wav" "b/packages/webgal/public/game/vocal/009_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\344\276\213\343\201\210\343\201\260\343\200\201\347\211\271\346\256\212\345\212\271\346\236\234\343\201\250\342\200\246.wav"
new file mode 100644
index 000000000..9ba368ea0
Binary files /dev/null and "b/packages/webgal/public/game/vocal/009_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\344\276\213\343\201\210\343\201\260\343\200\201\347\211\271\346\256\212\345\212\271\346\236\234\343\201\250\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/010_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\223\343\202\223\343\201\252\346\204\237\343\201\230\343\201\253\343\200\201\343\202\246\343\202\247\342\200\246.wav" "b/packages/webgal/public/game/vocal/010_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\223\343\202\223\343\201\252\346\204\237\343\201\230\343\201\253\343\200\201\343\202\246\343\202\247\342\200\246.wav"
new file mode 100644
index 000000000..c988c6185
Binary files /dev/null and "b/packages/webgal/public/game/vocal/010_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\223\343\202\223\343\201\252\346\204\237\343\201\230\343\201\253\343\200\201\343\202\246\343\202\247\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/011_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\346\251\237\350\203\275\343\201\256\347\264\271\344\273\213\343\201\257\344\273\245\344\270\212\343\201\247\342\200\246.wav" "b/packages/webgal/public/game/vocal/011_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\346\251\237\350\203\275\343\201\256\347\264\271\344\273\213\343\201\257\344\273\245\344\270\212\343\201\247\342\200\246.wav"
new file mode 100644
index 000000000..fe424d398
Binary files /dev/null and "b/packages/webgal/public/game/vocal/011_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\346\251\237\350\203\275\343\201\256\347\264\271\344\273\213\343\201\257\344\273\245\344\270\212\343\201\247\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/012_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\346\254\241\343\201\253\343\200\201\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\342\200\246.wav" "b/packages/webgal/public/game/vocal/012_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\346\254\241\343\201\253\343\200\201\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\342\200\246.wav"
new file mode 100644
index 000000000..6242cebea
Binary files /dev/null and "b/packages/webgal/public/game/vocal/012_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\346\254\241\343\201\253\343\200\201\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/013_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\350\210\210\345\221\263\343\201\214\343\201\202\343\202\214\343\201\260\343\200\201\345\274\225\343\201\215\342\200\246.wav" "b/packages/webgal/public/game/vocal/013_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\350\210\210\345\221\263\343\201\214\343\201\202\343\202\214\343\201\260\343\200\201\345\274\225\343\201\215\342\200\246.wav"
new file mode 100644
index 000000000..2914606cf
Binary files /dev/null and "b/packages/webgal/public/game/vocal/013_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\350\210\210\345\221\263\343\201\214\343\201\202\343\202\214\343\201\260\343\200\201\345\274\225\343\201\215\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/014_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\257\343\200\201\343\202\210\342\200\246.wav" "b/packages/webgal/public/game/vocal/014_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\257\343\200\201\343\202\210\342\200\246.wav"
new file mode 100644
index 000000000..76693e53e
Binary files /dev/null and "b/packages/webgal/public/game/vocal/014_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\257\343\200\201\343\202\210\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/015_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\345\275\223\345\210\235\343\201\256\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\342\200\246.wav" "b/packages/webgal/public/game/vocal/015_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\345\275\223\345\210\235\343\201\256\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\342\200\246.wav"
new file mode 100644
index 000000000..a6f0ecef2
Binary files /dev/null and "b/packages/webgal/public/game/vocal/015_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\345\275\223\345\210\235\343\201\256\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/016_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\351\225\267\343\201\215\343\201\253\343\202\217\343\201\237\343\202\213\351\226\213\347\231\272\346\234\237\342\200\246.wav" "b/packages/webgal/public/game/vocal/016_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\351\225\267\343\201\215\343\201\253\343\202\217\343\201\237\343\202\213\351\226\213\347\231\272\346\234\237\342\200\246.wav"
new file mode 100644
index 000000000..5664b1537
Binary files /dev/null and "b/packages/webgal/public/game/vocal/016_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\351\225\267\343\201\215\343\201\253\343\202\217\343\201\237\343\202\213\351\226\213\347\231\272\346\234\237\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/017_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\225\343\202\211\343\201\253\343\200\201\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\342\200\246.wav" "b/packages/webgal/public/game/vocal/017_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\225\343\202\211\343\201\253\343\200\201\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\342\200\246.wav"
new file mode 100644
index 000000000..a7bb23cb0
Binary files /dev/null and "b/packages/webgal/public/game/vocal/017_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\225\343\202\211\343\201\253\343\200\201\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/018_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\223\343\201\256\343\203\207\343\203\242\343\202\262\343\203\274\343\203\240\343\201\247\344\275\277\342\200\246.wav" "b/packages/webgal/public/game/vocal/018_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\223\343\201\256\343\203\207\343\203\242\343\202\262\343\203\274\343\203\240\343\201\247\344\275\277\342\200\246.wav"
new file mode 100644
index 000000000..bea68b70f
Binary files /dev/null and "b/packages/webgal/public/game/vocal/018_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\223\343\201\256\343\203\207\343\203\242\343\202\262\343\203\274\343\203\240\343\201\247\344\275\277\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/019_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav" "b/packages/webgal/public/game/vocal/019_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav"
new file mode 100644
index 000000000..3ef6703a9
Binary files /dev/null and "b/packages/webgal/public/game/vocal/019_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/020_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\256\351\226\213\347\231\272\342\200\246.wav" "b/packages/webgal/public/game/vocal/020_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\256\351\226\213\347\231\272\342\200\246.wav"
new file mode 100644
index 000000000..78fd5fadf
Binary files /dev/null and "b/packages/webgal/public/game/vocal/020_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\256\351\226\213\347\231\272\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/021_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\235\343\201\256\343\201\237\343\202\201\343\200\201\345\275\274\345\245\263\343\201\2573\342\200\246.wav" "b/packages/webgal/public/game/vocal/021_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\235\343\201\256\343\201\237\343\202\201\343\200\201\345\275\274\345\245\263\343\201\2573\342\200\246.wav"
new file mode 100644
index 000000000..d461d9f08
Binary files /dev/null and "b/packages/webgal/public/game/vocal/021_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\235\343\201\256\343\201\237\343\202\201\343\200\201\345\275\274\345\245\263\343\201\2573\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/022_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\256\343\202\271\343\202\257\342\200\246.wav" "b/packages/webgal/public/game/vocal/022_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\256\343\202\271\343\202\257\342\200\246.wav"
new file mode 100644
index 000000000..4bd6a77f3
Binary files /dev/null and "b/packages/webgal/public/game/vocal/022_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\201\256\343\202\271\343\202\257\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/023_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav" "b/packages/webgal/public/game/vocal/023_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav"
new file mode 100644
index 000000000..db603e9fc
Binary files /dev/null and "b/packages/webgal/public/game/vocal/023_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/024_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav" "b/packages/webgal/public/game/vocal/024_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav"
new file mode 100644
index 000000000..ced32290a
Binary files /dev/null and "b/packages/webgal/public/game/vocal/024_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/025_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\203\241\343\203\213\343\203\245\343\203\274\343\201\256\343\203\255\343\202\260\343\203\234\343\202\277\342\200\246.wav" "b/packages/webgal/public/game/vocal/025_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\203\241\343\203\213\343\203\245\343\203\274\343\201\256\343\203\255\343\202\260\343\203\234\343\202\277\342\200\246.wav"
new file mode 100644
index 000000000..bbc7e66a5
Binary files /dev/null and "b/packages/webgal/public/game/vocal/025_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\203\241\343\203\213\343\203\245\343\203\274\343\201\256\343\203\255\343\202\260\343\203\234\343\202\277\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/026_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\350\261\212\345\257\214\343\201\252\350\250\255\345\256\232\343\200\201\343\202\257\343\202\244\343\203\203\342\200\246.wav" "b/packages/webgal/public/game/vocal/026_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\350\261\212\345\257\214\343\201\252\350\250\255\345\256\232\343\200\201\343\202\257\343\202\244\343\203\203\342\200\246.wav"
new file mode 100644
index 000000000..1021c800b
Binary files /dev/null and "b/packages/webgal/public/game/vocal/026_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\350\261\212\345\257\214\343\201\252\350\250\255\345\256\232\343\200\201\343\202\257\343\202\244\343\203\203\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/027_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\202\343\201\241\343\202\215\343\202\223\343\200\201\343\201\223\343\202\214\343\202\211\343\201\256\342\200\246.wav" "b/packages/webgal/public/game/vocal/027_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\202\343\201\241\343\202\215\343\202\223\343\200\201\343\201\223\343\202\214\343\202\211\343\201\256\342\200\246.wav"
new file mode 100644
index 000000000..14fbb4f80
Binary files /dev/null and "b/packages/webgal/public/game/vocal/027_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\202\343\201\241\343\202\215\343\202\223\343\200\201\343\201\223\343\202\214\343\202\211\343\201\256\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/028_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\345\210\235\343\202\201\343\201\246\343\202\262\343\203\274\343\203\240\343\202\222\351\226\213\347\231\272\342\200\246.wav" "b/packages/webgal/public/game/vocal/028_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\345\210\235\343\202\201\343\201\246\343\202\262\343\203\274\343\203\240\343\202\222\351\226\213\347\231\272\342\200\246.wav"
new file mode 100644
index 000000000..4aab87f89
Binary files /dev/null and "b/packages/webgal/public/game/vocal/028_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\345\210\235\343\202\201\343\201\246\343\202\262\343\203\274\343\203\240\343\202\222\351\226\213\347\231\272\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/029_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\247\343\201\231\343\201\213\343\202\211\343\200\201\345\277\203\351\205\215\343\201\233\343\201\232\342\200\246.wav" "b/packages/webgal/public/game/vocal/029_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\247\343\201\231\343\201\213\343\202\211\343\200\201\345\277\203\351\205\215\343\201\233\343\201\232\342\200\246.wav"
new file mode 100644
index 000000000..f97ca4d97
Binary files /dev/null and "b/packages/webgal/public/game/vocal/029_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\201\247\343\201\231\343\201\213\343\202\211\343\200\201\345\277\203\351\205\215\343\201\233\343\201\232\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/030_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav" "b/packages/webgal/public/game/vocal/030_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav"
new file mode 100644
index 000000000..f21c3de6b
Binary files /dev/null and "b/packages/webgal/public/game/vocal/030_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/031_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\203\252\343\203\263\343\202\257\343\202\2221\343\201\244\347\224\250\346\204\217\343\201\231\342\200\246.wav" "b/packages/webgal/public/game/vocal/031_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\203\252\343\203\263\343\202\257\343\202\2221\343\201\244\347\224\250\346\204\217\343\201\231\342\200\246.wav"
new file mode 100644
index 000000000..f73b0cbff
Binary files /dev/null and "b/packages/webgal/public/game/vocal/031_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\203\252\343\203\263\343\202\257\343\202\2221\343\201\244\347\224\250\346\204\217\343\201\231\342\200\246.wav" differ
diff --git "a/packages/webgal/public/game/vocal/032_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav" "b/packages/webgal/public/game/vocal/032_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav"
new file mode 100644
index 000000000..4993293b6
Binary files /dev/null and "b/packages/webgal/public/game/vocal/032_\345\260\217\345\244\234SAYO\357\274\210\343\203\216\343\203\274\343\203\236\343\203\253\357\274\211_\343\202\246\343\202\247\343\203\226\343\202\256\343\203\243\343\203\253\343\203\227\343\203\255\343\202\270\342\200\246.wav" differ
diff --git a/packages/webgal/public/game/vocal/e001_Hello.mp3 b/packages/webgal/public/game/vocal/e001_Hello.mp3
new file mode 100644
index 000000000..7f2853032
Binary files /dev/null and b/packages/webgal/public/game/vocal/e001_Hello.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e002_Welcome_to_WebGAL.mp3 b/packages/webgal/public/game/vocal/e002_Welcome_to_WebGAL.mp3
new file mode 100644
index 000000000..12faadc62
Binary files /dev/null and b/packages/webgal/public/game/vocal/e002_Welcome_to_WebGAL.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e003_WebGAL_is_a_completely_new_web.mp3 b/packages/webgal/public/game/vocal/e003_WebGAL_is_a_completely_new_web.mp3
new file mode 100644
index 000000000..eba6c1824
Binary files /dev/null and b/packages/webgal/public/game/vocal/e003_WebGAL_is_a_completely_new_web.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e004_It_is_an_engine_developedusing_web.mp3 b/packages/webgal/public/game/vocal/e004_It_is_an_engine_developedusing_web.mp3
new file mode 100644
index 000000000..1690f360d
Binary files /dev/null and b/packages/webgal/public/game/vocal/e004_It_is_an_engine_developedusing_web.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e005_Thanks_to_this_ feature_once.mp3 b/packages/webgal/public/game/vocal/e005_Thanks_to_this_ feature_once.mp3
new file mode 100644
index 000000000..dd9e9c4c0
Binary files /dev/null and b/packages/webgal/public/game/vocal/e005_Thanks_to_this_ feature_once.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e006_Very attractive.mp3 b/packages/webgal/public/game/vocal/e006_Very attractive.mp3
new file mode 100644
index 000000000..91a952686
Binary files /dev/null and b/packages/webgal/public/game/vocal/e006_Very attractive.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e007_In_addition_ WebGAL_allows you.mp3 b/packages/webgal/public/game/vocal/e007_In_addition_ WebGAL_allows you.mp3
new file mode 100644
index 000000000..49a5cdf6a
Binary files /dev/null and b/packages/webgal/public/game/vocal/e007_In_addition_ WebGAL_allows you.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e008_For_example_let's_make.mp3 b/packages/webgal/public/game/vocal/e008_For_example_let's_make.mp3
new file mode 100644
index 000000000..e38d26be2
Binary files /dev/null and b/packages/webgal/public/game/vocal/e008_For_example_let's_make.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e009_As_you_can see_WebGAL.mp3 b/packages/webgal/public/game/vocal/e009_As_you_can see_WebGAL.mp3
new file mode 100644
index 000000000..bbfe2e1a2
Binary files /dev/null and b/packages/webgal/public/game/vocal/e009_As_you_can see_WebGAL.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e010_That's_all_for_the_feature introduction.mp3 b/packages/webgal/public/game/vocal/e010_That's_all_for_the_feature introduction.mp3
new file mode 100644
index 000000000..ba767c71b
Binary files /dev/null and b/packages/webgal/public/game/vocal/e010_That's_all_for_the_feature introduction.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e011_Next_I_will introduce_the_ history.mp3 b/packages/webgal/public/game/vocal/e011_Next_I_will introduce_the_ history.mp3
new file mode 100644
index 000000000..c2f1284a8
Binary files /dev/null and b/packages/webgal/public/game/vocal/e011_Next_I_will introduce_the_ history.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e012_WebGAL _was_developed to_make_it_ easier.mp3 b/packages/webgal/public/game/vocal/e012_WebGAL _was_developed to_make_it_ easier.mp3
new file mode 100644
index 000000000..9142ca323
Binary files /dev/null and b/packages/webgal/public/game/vocal/e012_WebGAL _was_developed to_make_it_ easier.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e013_Initially_ WebGAL_had_very_few_features.mp3 b/packages/webgal/public/game/vocal/e013_Initially_ WebGAL_had_very_few_features.mp3
new file mode 100644
index 000000000..8e3feb981
Binary files /dev/null and b/packages/webgal/public/game/vocal/e013_Initially_ WebGAL_had_very_few_features.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e014_After_a_long _development period.mp3 b/packages/webgal/public/game/vocal/e014_After_a_long _development period.mp3
new file mode 100644
index 000000000..0d660e1cd
Binary files /dev/null and b/packages/webgal/public/game/vocal/e014_After_a_long _development period.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e015_Additionally,_the release_of_the WebGAL_editor.mp3 b/packages/webgal/public/game/vocal/e015_Additionally,_the release_of_the WebGAL_editor.mp3
new file mode 100644
index 000000000..f0f9370b7
Binary files /dev/null and b/packages/webgal/public/game/vocal/e015_Additionally,_the release_of_the WebGAL_editor.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e016_The_WebGAL project_has reached_1000.mp3 b/packages/webgal/public/game/vocal/e016_The_WebGAL project_has reached_1000.mp3
new file mode 100644
index 000000000..6ad0cd6c2
Binary files /dev/null and b/packages/webgal/public/game/vocal/e016_The_WebGAL project_has reached_1000.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e017_The_development process_of WebGAL_is_a process.mp3 b/packages/webgal/public/game/vocal/e017_The_development process_of WebGAL_is_a process.mp3
new file mode 100644
index 000000000..feb8f9db6
Binary files /dev/null and b/packages/webgal/public/game/vocal/e017_The_development process_of WebGAL_is_a process.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e018_WebGAL's _scripting language_was designed_from the_ground.mp3 b/packages/webgal/public/game/vocal/e018_WebGAL's _scripting language_was designed_from the_ground.mp3
new file mode 100644
index 000000000..738ab9524
Binary files /dev/null and b/packages/webgal/public/game/vocal/e018_WebGAL's _scripting language_was designed_from the_ground.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e019_The_WebGAL_project_supports_many_keyboard shortcuts.mp3 b/packages/webgal/public/game/vocal/e019_The_WebGAL_project_supports_many_keyboard shortcuts.mp3
new file mode 100644
index 000000000..d68478ab5
Binary files /dev/null and b/packages/webgal/public/game/vocal/e019_The_WebGAL_project_supports_many_keyboard shortcuts.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e020_Try_pressing the_Backlog button.mp3 b/packages/webgal/public/game/vocal/e020_Try_pressing the_Backlog button.mp3
new file mode 100644
index 000000000..c166554f0
Binary files /dev/null and b/packages/webgal/public/game/vocal/e020_Try_pressing the_Backlog button.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e021_Features_like_quick_save_quick_load.mp3 b/packages/webgal/public/game/vocal/e021_Features_like_quick_save_quick_load.mp3
new file mode 100644
index 000000000..82a0d1d44
Binary files /dev/null and b/packages/webgal/public/game/vocal/e021_Features_like_quick_save_quick_load.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e022_For developers_who_ are_developing_ games.mp3 b/packages/webgal/public/game/vocal/e022_For developers_who_ are_developing_ games.mp3
new file mode 100644
index 000000000..c9afdf926
Binary files /dev/null and b/packages/webgal/public/game/vocal/e022_For developers_who_ are_developing_ games.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e023_So_you_can_ start_making_ games_quickly.mp3 b/packages/webgal/public/game/vocal/e023_So_you_can_ start_making_ games_quickly.mp3
new file mode 100644
index 000000000..262385754
Binary files /dev/null and b/packages/webgal/public/game/vocal/e023_So_you_can_ start_making_ games_quickly.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e024_We_hope_ that_your_work.mp3 b/packages/webgal/public/game/vocal/e024_We_hope_ that_your_work.mp3
new file mode 100644
index 000000000..36633e5a4
Binary files /dev/null and b/packages/webgal/public/game/vocal/e024_We_hope_ that_your_work.mp3 differ
diff --git a/packages/webgal/public/game/vocal/e025_Thank_you_ for_your_interest_in_the_WebGAL_project!.mp3 b/packages/webgal/public/game/vocal/e025_Thank_you_ for_your_interest_in_the_WebGAL_project!.mp3
new file mode 100644
index 000000000..ba9a98268
Binary files /dev/null and b/packages/webgal/public/game/vocal/e025_Thank_you_ for_your_interest_in_the_WebGAL_project!.mp3 differ
diff --git a/packages/webgal/public/game/vocal/v1.wav b/packages/webgal/public/game/vocal/v1.wav
new file mode 100644
index 000000000..6af4cd575
Binary files /dev/null and b/packages/webgal/public/game/vocal/v1.wav differ
diff --git a/packages/webgal/public/game/vocal/v10.wav b/packages/webgal/public/game/vocal/v10.wav
new file mode 100644
index 000000000..0c4508d28
Binary files /dev/null and b/packages/webgal/public/game/vocal/v10.wav differ
diff --git a/packages/webgal/public/game/vocal/v11.wav b/packages/webgal/public/game/vocal/v11.wav
new file mode 100644
index 000000000..9971cd7e3
Binary files /dev/null and b/packages/webgal/public/game/vocal/v11.wav differ
diff --git a/packages/webgal/public/game/vocal/v12.wav b/packages/webgal/public/game/vocal/v12.wav
new file mode 100644
index 000000000..bdf912a99
Binary files /dev/null and b/packages/webgal/public/game/vocal/v12.wav differ
diff --git a/packages/webgal/public/game/vocal/v13.wav b/packages/webgal/public/game/vocal/v13.wav
new file mode 100644
index 000000000..bb260dcdc
Binary files /dev/null and b/packages/webgal/public/game/vocal/v13.wav differ
diff --git a/packages/webgal/public/game/vocal/v14.wav b/packages/webgal/public/game/vocal/v14.wav
new file mode 100644
index 000000000..cdda2a9bf
Binary files /dev/null and b/packages/webgal/public/game/vocal/v14.wav differ
diff --git a/packages/webgal/public/game/vocal/v15.wav b/packages/webgal/public/game/vocal/v15.wav
new file mode 100644
index 000000000..8da026ebe
Binary files /dev/null and b/packages/webgal/public/game/vocal/v15.wav differ
diff --git a/packages/webgal/public/game/vocal/v16.wav b/packages/webgal/public/game/vocal/v16.wav
new file mode 100644
index 000000000..39a4b0826
Binary files /dev/null and b/packages/webgal/public/game/vocal/v16.wav differ
diff --git a/packages/webgal/public/game/vocal/v17.wav b/packages/webgal/public/game/vocal/v17.wav
new file mode 100644
index 000000000..3b9917930
Binary files /dev/null and b/packages/webgal/public/game/vocal/v17.wav differ
diff --git a/packages/webgal/public/game/vocal/v18.wav b/packages/webgal/public/game/vocal/v18.wav
new file mode 100644
index 000000000..3f5831e27
Binary files /dev/null and b/packages/webgal/public/game/vocal/v18.wav differ
diff --git a/packages/webgal/public/game/vocal/v19.wav b/packages/webgal/public/game/vocal/v19.wav
new file mode 100644
index 000000000..af0fe7745
Binary files /dev/null and b/packages/webgal/public/game/vocal/v19.wav differ
diff --git a/packages/webgal/public/game/vocal/v2.wav b/packages/webgal/public/game/vocal/v2.wav
new file mode 100644
index 000000000..57dc1e9db
Binary files /dev/null and b/packages/webgal/public/game/vocal/v2.wav differ
diff --git a/packages/webgal/public/game/vocal/v20.wav b/packages/webgal/public/game/vocal/v20.wav
new file mode 100644
index 000000000..757e80734
Binary files /dev/null and b/packages/webgal/public/game/vocal/v20.wav differ
diff --git a/packages/webgal/public/game/vocal/v21.wav b/packages/webgal/public/game/vocal/v21.wav
new file mode 100644
index 000000000..040500b2c
Binary files /dev/null and b/packages/webgal/public/game/vocal/v21.wav differ
diff --git a/packages/webgal/public/game/vocal/v22.wav b/packages/webgal/public/game/vocal/v22.wav
new file mode 100644
index 000000000..908c988f4
Binary files /dev/null and b/packages/webgal/public/game/vocal/v22.wav differ
diff --git a/packages/webgal/public/game/vocal/v23.wav b/packages/webgal/public/game/vocal/v23.wav
new file mode 100644
index 000000000..a17c7615b
Binary files /dev/null and b/packages/webgal/public/game/vocal/v23.wav differ
diff --git a/packages/webgal/public/game/vocal/v3.wav b/packages/webgal/public/game/vocal/v3.wav
new file mode 100644
index 000000000..740f2b886
Binary files /dev/null and b/packages/webgal/public/game/vocal/v3.wav differ
diff --git a/packages/webgal/public/game/vocal/v4.wav b/packages/webgal/public/game/vocal/v4.wav
new file mode 100644
index 000000000..32598179e
Binary files /dev/null and b/packages/webgal/public/game/vocal/v4.wav differ
diff --git a/packages/webgal/public/game/vocal/v5.wav b/packages/webgal/public/game/vocal/v5.wav
new file mode 100644
index 000000000..ad3eaaa79
Binary files /dev/null and b/packages/webgal/public/game/vocal/v5.wav differ
diff --git a/packages/webgal/public/game/vocal/v6.wav b/packages/webgal/public/game/vocal/v6.wav
new file mode 100644
index 000000000..a86f2fd24
Binary files /dev/null and b/packages/webgal/public/game/vocal/v6.wav differ
diff --git a/packages/webgal/public/game/vocal/v7.wav b/packages/webgal/public/game/vocal/v7.wav
new file mode 100644
index 000000000..d67959cff
Binary files /dev/null and b/packages/webgal/public/game/vocal/v7.wav differ
diff --git a/packages/webgal/public/game/vocal/v8.wav b/packages/webgal/public/game/vocal/v8.wav
new file mode 100644
index 000000000..92dfda9cb
Binary files /dev/null and b/packages/webgal/public/game/vocal/v8.wav differ
diff --git a/packages/webgal/public/game/vocal/v9.wav b/packages/webgal/public/game/vocal/v9.wav
new file mode 100644
index 000000000..aa7f9e777
Binary files /dev/null and b/packages/webgal/public/game/vocal/v9.wav differ
diff --git a/packages/webgal/public/icons/apple-touch-icon.png b/packages/webgal/public/icons/apple-touch-icon.png
new file mode 100644
index 000000000..75a1281d9
Binary files /dev/null and b/packages/webgal/public/icons/apple-touch-icon.png differ
diff --git a/packages/webgal/public/icons/favicon.ico b/packages/webgal/public/icons/favicon.ico
new file mode 100644
index 000000000..2f4226607
Binary files /dev/null and b/packages/webgal/public/icons/favicon.ico differ
diff --git a/packages/webgal/public/icons/icon-192-maskable.png b/packages/webgal/public/icons/icon-192-maskable.png
new file mode 100644
index 000000000..55710da09
Binary files /dev/null and b/packages/webgal/public/icons/icon-192-maskable.png differ
diff --git a/packages/webgal/public/icons/icon-192.png b/packages/webgal/public/icons/icon-192.png
new file mode 100644
index 000000000..2f1940862
Binary files /dev/null and b/packages/webgal/public/icons/icon-192.png differ
diff --git a/packages/webgal/public/icons/icon-512-maskable.png b/packages/webgal/public/icons/icon-512-maskable.png
new file mode 100644
index 000000000..35271a107
Binary files /dev/null and b/packages/webgal/public/icons/icon-512-maskable.png differ
diff --git a/packages/webgal/public/icons/icon-512.png b/packages/webgal/public/icons/icon-512.png
new file mode 100644
index 000000000..32a1e0985
Binary files /dev/null and b/packages/webgal/public/icons/icon-512.png differ
diff --git a/packages/webgal/public/manifest.json b/packages/webgal/public/manifest.json
new file mode 100644
index 000000000..7526b7e66
--- /dev/null
+++ b/packages/webgal/public/manifest.json
@@ -0,0 +1,33 @@
+{
+ "name": "WebGAL",
+ "short_name": "WebGAL",
+ "start_url": ".",
+ "display": "fullscreen",
+ "description": "WebGAL DEMO",
+ "dir": "auto",
+ "orientation": "landscape",
+ "icons": [
+ {
+ "src": "./icons/icon-192.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "./icons/icon-512.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ },
+ {
+ "src": "./icons/icon-192-maskable.png",
+ "type": "image/png",
+ "sizes": "192x192",
+ "purpose": "maskable"
+ },
+ {
+ "src": "./icons/icon-512-maskable.png",
+ "type": "image/png",
+ "sizes": "512x512",
+ "purpose": "maskable"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/webgal/public/webgal-serviceworker.js b/packages/webgal/public/webgal-serviceworker.js
new file mode 100644
index 000000000..c3eeee301
--- /dev/null
+++ b/packages/webgal/public/webgal-serviceworker.js
@@ -0,0 +1,63 @@
+self.addEventListener('install', (ev) => {
+ // console.log('[service worker] installing');
+ ev.waitUntil(self.skipWaiting());
+});
+
+// fetch事件是每次页面请求资源时触发的
+self.addEventListener('fetch', function (event) {
+ const url = event.request.url;
+ const isReturnCache = !!(url.match('/assets/') && !url.match('game'));
+ if (isReturnCache) {
+ // console.log('%cCACHED: ' + url, 'color: #005CAF; padding: 2px;');
+ }
+ if (!isReturnCache) {
+ event.respondWith(fetch(event.request));
+ } else {
+ event.respondWith(
+ // 检查在缓存中是否有匹配的资源
+ caches.match(event.request).then(function (response) {
+ // 如果缓存中有匹配的资源,则返回缓存资源
+ if (response) {
+ return response;
+ }
+ // 如果没有匹配的资源,则尝试从网络请求
+ // 同时将获取的资源存入缓存
+ return fetch(event.request)
+ .then(function (networkResponse) {
+ console.log('%cCACHED: ' + url, 'color: #005CAF; padding: 2px;');
+ if (networkResponse.status === 206 && event.request.headers.has('range')) {
+ // 如果是部分响应且请求带有Range头,则创建新的请求,将完整响应返回给客户端
+ // eslint-disable-next-line max-nested-callbacks
+ return fetch(event.request.url).then(function (fullNetworkResponse) {
+ const headers = {};
+ for (let entry of fullNetworkResponse.headers.entries()) {
+ headers[entry[0]] = entry[1];
+ }
+ const fullResponse = new Response(fullNetworkResponse.body, {
+ status: fullNetworkResponse.status,
+ statusText: fullNetworkResponse.statusText,
+ headers: headers,
+ });
+ const clonedResponse = fullResponse.clone();
+ // eslint-disable-next-line max-nested-callbacks
+ caches.open('my-cache').then(function (cache) {
+ cache.put(event.request, clonedResponse);
+ });
+ return fullResponse;
+ });
+ }
+ const clonedResponse = networkResponse.clone();
+ // eslint-disable-next-line max-nested-callbacks
+ caches.open('my-cache').then(function (cache) {
+ cache.put(event.request, clonedResponse);
+ });
+ return networkResponse;
+ })
+ .catch(function (error) {
+ console.error('Fetching failed:', error);
+ throw error;
+ });
+ }),
+ );
+ }
+});
diff --git a/packages/webgal/src/App.tsx b/packages/webgal/src/App.tsx
new file mode 100644
index 000000000..2ed11e21d
--- /dev/null
+++ b/packages/webgal/src/App.tsx
@@ -0,0 +1,43 @@
+import Title from '@/UI/Title/Title';
+import Logo from '@/UI/Logo/Logo';
+import { useEffect } from 'react';
+import { initializeScript } from './Core/initializeScript';
+import Menu from '@/UI/Menu/Menu';
+import { Stage } from '@/Stage/Stage';
+import { BottomControlPanel } from '@/UI/BottomControlPanel/BottomControlPanel';
+import { Backlog } from '@/UI/Backlog/Backlog';
+import { Extra } from '@/UI/Extra/Extra';
+import { BottomControlPanelFilm } from '@/UI/BottomControlPanel/BottomControlPanelFilm';
+import GlobalDialog from '@/UI/GlobalDialog/GlobalDialog';
+import DevPanel from '@/UI/DevPanel/DevPanel';
+import Translation from '@/UI/Translation/Translation';
+import { PanicOverlay } from '@/UI/PanicOverlay/PanicOverlay';
+import { useFullScreen } from './hooks/useFullScreen';
+
+function App() {
+ useEffect(() => {
+ initializeScript();
+ }, []);
+
+ useFullScreen();
+
+ // Provider用于对各组件提供状态
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/packages/webgal/src/Core/Modules/animationFunctions.ts b/packages/webgal/src/Core/Modules/animationFunctions.ts
new file mode 100644
index 000000000..e1cc14658
--- /dev/null
+++ b/packages/webgal/src/Core/Modules/animationFunctions.ts
@@ -0,0 +1,93 @@
+import { generateUniversalSoftInAnimationObj } from '@/Core/controller/stage/pixi/animations/universalSoftIn';
+import { logger } from '@/Core/util/logger';
+import { generateUniversalSoftOffAnimationObj } from '@/Core/controller/stage/pixi/animations/universalSoftOff';
+import { webgalStore } from '@/store/store';
+import cloneDeep from 'lodash/cloneDeep';
+import { baseTransform } from '@/store/stageInterface';
+import { generateTimelineObj } from '@/Core/controller/stage/pixi/animations/timeline';
+import { WebGAL } from '@/Core/WebGAL';
+
+export function getAnimationObject(animationName: string, target: string, duration: number) {
+ const effect = WebGAL.animationManager.getAnimations().find((ani) => ani.name === animationName);
+ if (effect) {
+ const mappedEffects = effect.effects.map((effect) => {
+ const targetSetEffect = webgalStore.getState().stage.effects.find((e) => e.target === target);
+ const newEffect = cloneDeep({ ...(targetSetEffect?.transform ?? baseTransform), duration: 0 });
+ Object.assign(newEffect, effect);
+ newEffect.duration = effect.duration;
+ return newEffect;
+ });
+ logger.debug('装载自定义动画', mappedEffects);
+ return generateTimelineObj(mappedEffects, target, duration);
+ }
+ return null;
+}
+
+export function getAnimateDuration(animationName: string) {
+ const effect = WebGAL.animationManager.getAnimations().find((ani) => ani.name === animationName);
+ if (effect) {
+ let duration = 0;
+ effect.effects.forEach((e) => {
+ duration += e.duration;
+ });
+ return duration;
+ }
+ return 0;
+}
+
+// eslint-disable-next-line max-params
+export function getEnterExitAnimation(
+ target: string,
+ type: 'enter' | 'exit',
+ isBg = false,
+ realTarget?: string, // 用于立绘和背景移除时,以当前时间打上特殊标记
+): {
+ duration: number;
+ animation: {
+ setStartState: () => void;
+ tickerFunc: (delta: number) => void;
+ setEndState: () => void;
+ } | null;
+} {
+ if (type === 'enter') {
+ let duration = 500;
+ if (isBg) {
+ duration = 1500;
+ }
+ // 走默认动画
+ let animation: {
+ setStartState: () => void;
+ tickerFunc: (delta: number) => void;
+ setEndState: () => void;
+ } | null = generateUniversalSoftInAnimationObj(realTarget ?? target, duration);
+ const animarionName = WebGAL.animationManager.nextEnterAnimationName.get(target);
+ if (animarionName) {
+ logger.debug('取代默认进入动画', target);
+ animation = getAnimationObject(animarionName, realTarget ?? target, getAnimateDuration(animarionName));
+ duration = getAnimateDuration(animarionName);
+ // 用后重置
+ WebGAL.animationManager.nextEnterAnimationName.delete(target);
+ }
+ return { duration, animation };
+ } else {
+ let duration = 750;
+ if (isBg) {
+ duration = 1500;
+ }
+ // 走默认动画
+ let animation: {
+ setStartState: () => void;
+ tickerFunc: (delta: number) => void;
+ setEndState: () => void;
+ } | null = generateUniversalSoftOffAnimationObj(realTarget ?? target, duration);
+ const animarionName = WebGAL.animationManager.nextExitAnimationName.get(target);
+ if (animarionName) {
+ logger.debug('取代默认退出动画', target);
+ animation = getAnimationObject(animarionName, realTarget ?? target, getAnimateDuration(animarionName));
+ duration = getAnimateDuration(animarionName);
+ // 用后重置
+ WebGAL.animationManager.nextExitAnimationName.delete(target);
+ }
+ return { duration, animation };
+ }
+}
diff --git a/packages/webgal/src/Core/Modules/animations.ts b/packages/webgal/src/Core/Modules/animations.ts
new file mode 100644
index 000000000..7b07650fc
--- /dev/null
+++ b/packages/webgal/src/Core/Modules/animations.ts
@@ -0,0 +1,19 @@
+import { ITransform } from '@/store/stageInterface';
+
+export interface IUserAnimation {
+ name: string;
+ effects: Array;
+}
+
+export class AnimationManager {
+ public nextEnterAnimationName: Map = new Map();
+ public nextExitAnimationName: Map = new Map();
+ private animations: Array = [];
+
+ public addAnimation(animation: IUserAnimation) {
+ this.animations.push(animation);
+ }
+ public getAnimations() {
+ return this.animations;
+ }
+}
diff --git a/packages/webgal/src/Core/Modules/backlog.ts b/packages/webgal/src/Core/Modules/backlog.ts
new file mode 100644
index 000000000..8d9c9e8c8
--- /dev/null
+++ b/packages/webgal/src/Core/Modules/backlog.ts
@@ -0,0 +1,69 @@
+/**
+ * 当前的backlog
+ */
+import { IEffect, IStageState } from '@/store/stageInterface';
+import { webgalStore } from '@/store/store';
+import { ISaveScene } from '@/store/userDataInterface';
+import cloneDeep from 'lodash/cloneDeep';
+
+import { SYSTEM_CONFIG } from '@/config';
+import { SceneManager } from '@/Core/Modules/scene';
+
+export interface IBacklogItem {
+ currentStageState: IStageState;
+ saveScene: ISaveScene;
+}
+
+export class BacklogManager {
+ public isSaveBacklogNext = false;
+ private backlog: Array = [];
+
+ private readonly sceneManager: SceneManager;
+
+ public constructor(sceneManager: SceneManager) {
+ this.sceneManager = sceneManager;
+ }
+
+ public getBacklog() {
+ return this.backlog;
+ }
+
+ public editLastBacklogItemEffect(effects: IEffect[]) {
+ this.backlog[this.backlog.length - 1].currentStageState.effects = effects;
+ }
+
+ public makeBacklogEmpty() {
+ this.backlog.splice(0, this.backlog.length); // 清空backlog
+ }
+ public insertBacklogItem(item: IBacklogItem) {
+ this.backlog.push(item);
+ }
+ public saveCurrentStateToBacklog() {
+ // 存一下 Backlog
+ const currentStageState = webgalStore.getState().stage;
+ const stageStateToBacklog = cloneDeep(currentStageState);
+ stageStateToBacklog.PerformList.forEach((ele) => {
+ ele.script.args.forEach((argelement) => {
+ if (argelement.key === 'concat') {
+ argelement.value = false;
+ ele.script.content = stageStateToBacklog.showText;
+ }
+ });
+ });
+ const backlogElement: IBacklogItem = {
+ currentStageState: stageStateToBacklog,
+ saveScene: {
+ currentSentenceId: this.sceneManager.sceneData.currentSentenceId, // 当前语句ID
+ sceneStack: cloneDeep(this.sceneManager.sceneData.sceneStack), // 场景栈
+ sceneName: this.sceneManager.sceneData.currentScene.sceneName, // 场景名称
+ sceneUrl: this.sceneManager.sceneData.currentScene.sceneUrl, // 场景url
+ },
+ };
+ this.getBacklog().push(backlogElement);
+
+ // 清除超出长度的部分
+ while (this.getBacklog().length > SYSTEM_CONFIG.backlog_size) {
+ this.getBacklog().shift();
+ }
+ }
+}
diff --git a/packages/webgal/src/Core/Modules/events.ts b/packages/webgal/src/Core/Modules/events.ts
new file mode 100644
index 000000000..af8b08eae
--- /dev/null
+++ b/packages/webgal/src/Core/Modules/events.ts
@@ -0,0 +1,32 @@
+import mitt from 'mitt';
+
+interface IWebgalEvent {
+ on: (callback: (message?: T) => void, id?: string) => void;
+ off: (callback: (message?: T) => void, id?: string) => void;
+ emit: (message?: T, id?: string) => void;
+}
+
+export class Events {
+ public textSettle = formEvent('text-settle');
+ public userInteractNext = formEvent('__NEXT');
+ public fullscreenDbClick = formEvent('fullscreen-dbclick');
+ public styleUpdate = formEvent('style-update');
+}
+
+const eventBus = mitt();
+
+function formEvent(eventName: string): IWebgalEvent {
+ return {
+ on: (callback, id?) => {
+ // @ts-ignore
+ eventBus.on(`${eventName}-${id ?? ''}`, callback);
+ },
+ emit: (message?, id?) => {
+ eventBus.emit(`${eventName}-${id ?? ''}`, message);
+ },
+ off: (callback, id?) => {
+ // @ts-ignore
+ eventBus.off(`${eventName}-${id ?? ''}`, callback);
+ },
+ };
+}
diff --git a/packages/webgal/src/Core/Modules/gamePlay.ts b/packages/webgal/src/Core/Modules/gamePlay.ts
new file mode 100644
index 000000000..69d1c0fe9
--- /dev/null
+++ b/packages/webgal/src/Core/Modules/gamePlay.ts
@@ -0,0 +1,29 @@
+import PixiStage from '@/Core/controller/stage/pixi/PixiController';
+import { PerformController } from '@/Core/Modules/perform/performController';
+
+/**
+ * 游戏运行时变量
+ */
+export class Gameplay {
+ public isAuto = false;
+ public isFast = false;
+ public autoInterval: ReturnType | null = null;
+ public fastInterval: ReturnType | null = null;
+ public autoTimeout: ReturnType | null = null;
+ public pixiStage: PixiStage | null = null;
+ public performController = new PerformController();
+ public resetGamePlay() {
+ this.performController.timeoutList = [];
+ this.isAuto = false;
+ this.isFast = false;
+ const autoInterval = this.autoInterval;
+ if (autoInterval !== null) clearInterval(autoInterval);
+ this.autoInterval = null;
+ const fastInterval = this.fastInterval;
+ if (fastInterval !== null) clearInterval(fastInterval);
+ this.fastInterval = null;
+ const autoTimeout = this.autoTimeout;
+ if (autoTimeout !== null) clearInterval(autoTimeout);
+ this.autoTimeout = null;
+ }
+}
diff --git a/packages/webgal/src/Core/Modules/perform/performController.ts b/packages/webgal/src/Core/Modules/perform/performController.ts
new file mode 100644
index 000000000..6386425f0
--- /dev/null
+++ b/packages/webgal/src/Core/Modules/perform/performController.ts
@@ -0,0 +1,133 @@
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { webgalStore } from '@/store/store';
+import cloneDeep from 'lodash/cloneDeep';
+import { resetStageState, stageActions } from '@/store/stageReducer';
+import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
+import { IRunPerform } from '@/store/stageInterface';
+import { WEBGAL_NONE } from '@/Core/constants';
+
+/**
+ * 获取随机演出名称
+ */
+export const getRandomPerformName = (): string => {
+ return Math.random().toString().substring(0, 10);
+};
+
+export class PerformController {
+ public performList: Array = [];
+ public timeoutList: Array> = [];
+
+ public arrangeNewPerform(perform: IPerform, script: ISentence, syncPerformState = true) {
+ // 检查演出列表内是否有相同的演出,如果有,一定是出了什么问题
+ const dupPerformIndex = this.performList.findIndex((p) => p.performName === perform.performName);
+ if (dupPerformIndex > -1) {
+ // 结束并删除全部重复演出
+ for (let i = 0; i < this.performList.length; i++) {
+ const e = this.performList[i];
+ if (e.performName === perform.performName) {
+ e.stopFunction();
+ clearTimeout(e.stopTimeout as unknown as number);
+ this.performList.splice(i, 1);
+ i--;
+ }
+ }
+ }
+
+ // 语句不执行演出
+ if (perform.performName === WEBGAL_NONE) {
+ return;
+ }
+ // 同步演出状态
+ if (syncPerformState) {
+ const performToAdd = { id: perform.performName, isHoldOn: perform.isHoldOn, script: script };
+ webgalStore.dispatch(stageActions.addPerform(performToAdd));
+ }
+
+ // 时间到后自动清理演出
+ perform.stopTimeout = setTimeout(() => {
+ // perform.stopFunction();
+ // perform.isOver = true;
+ if (!perform.isHoldOn) {
+ // 如果不是保持演出,清除
+ this.unmountPerform(perform.performName);
+ }
+ }, perform.duration);
+
+ if (script.args.find((e) => e.key === 'continue' && e.value === true)) perform.goNextWhenOver = true;
+
+ this.performList.push(perform);
+ }
+
+ public unmountPerform(name: string, force = false) {
+ if (!force) {
+ for (let i = 0; i < this.performList.length; i++) {
+ const e = this.performList[i];
+ if (!e.isHoldOn && e.performName === name) {
+ e.stopFunction();
+ clearTimeout(e.stopTimeout as unknown as number);
+ /**
+ * 在演出列表里删除演出对象的操作必须在调用 goNextWhenOver 之前
+ * 因为 goNextWhenOver 会调用 nextSentence,而 nextSentence 会清除目前未结束的演出
+ * 那么 nextSentence 函数就会删除这个演出,但是此时,在这个上下文,i 已经被确定了
+ * 所以 goNextWhenOver 后的代码会多删东西,解决方法就是在调用 goNextWhenOver 前先删掉这个演出对象
+ * 此问题对所有 goNextWhenOver 属性为真的演出都有影响,但只有 2 个演出有此问题
+ */
+ this.performList.splice(i, 1);
+ i--;
+ if (e.goNextWhenOver) {
+ // nextSentence();
+ this.goNextWhenOver();
+ }
+ }
+ }
+ } else {
+ for (let i = 0; i < this.performList.length; i++) {
+ const e = this.performList[i];
+ if (e.performName === name) {
+ e.stopFunction();
+ clearTimeout(e.stopTimeout as unknown as number);
+ if (e.goNextWhenOver) {
+ // nextSentence();
+ this.goNextWhenOver();
+ }
+ this.performList.splice(i, 1);
+ i--;
+ /**
+ * 从状态表里清除演出
+ */
+ this.erasePerformFromState(name);
+ }
+ }
+ }
+ }
+
+ public erasePerformFromState(name: string) {
+ webgalStore.dispatch(stageActions.removePerformByName(name));
+ }
+
+ public removeAllPerform() {
+ for (const e of this.performList) {
+ e.stopFunction();
+ }
+ this.performList = [];
+ for (const e of this.timeoutList) {
+ clearTimeout(e);
+ }
+ }
+
+ private goNextWhenOver() {
+ let isBlockingAuto = false;
+ this.performList?.forEach((e) => {
+ if (e.blockingAuto())
+ // 阻塞且没有结束的演出
+ isBlockingAuto = true;
+ });
+ if (isBlockingAuto) {
+ // 有阻塞,提前结束
+ setTimeout(this.goNextWhenOver, 100);
+ } else {
+ nextSentence();
+ }
+ }
+}
diff --git a/packages/webgal/src/Core/Modules/perform/performInterface.ts b/packages/webgal/src/Core/Modules/perform/performInterface.ts
new file mode 100644
index 000000000..8e00b2f90
--- /dev/null
+++ b/packages/webgal/src/Core/Modules/perform/performInterface.ts
@@ -0,0 +1,44 @@
+/**
+ * 描述演出的接口,主要用于控制演出,而不是执行(在演出开始时被调用演出的执行器返回)
+ * @interface IPerform
+ */
+export interface IPerform {
+ // 演出名称,用于在后面手动清除演出,如果没有标识,则代表不是保持演出,给予一个随机字符串
+ performName: string;
+ // 持续时间,单位为ms,持续时间到后自动回收演出
+ duration: number;
+ // 演出是不是一个保持类型的演出
+ isHoldOn: boolean;
+ // 卸载演出的函数
+ stopFunction: () => void;
+ // 演出是否阻塞游戏流程继续(一个函数,返回 boolean类型的结果,判断要不要阻塞)
+ blockingNext: () => boolean;
+ // 演出是否阻塞自动模式(一个函数,返回 boolean类型的结果,判断要不要阻塞)
+ blockingAuto: () => boolean;
+ // 自动回收使用的 Timeout
+ stopTimeout: undefined | ReturnType;
+ // 演出结束后转到下一句
+ goNextWhenOver?: boolean;
+ // 对于延迟触发的演出,使用 Promise
+ arrangePerformPromise?: Promise;
+ // 跳过由 nextSentence 函数引发的演出回收
+ skipNextCollect?: boolean;
+}
+
+// next之后,可以被打断的演出会被打断,不能被打断的演出会继续,阻塞next的演出会阻止next被响应。
+// 被打断或执行完毕的演出会移出演出列表
+// 只有所有演出都完成,或者仅存在不阻塞auto的演出,才允许auto
+
+/**
+ * 初始化的演出
+ */
+export const initPerform: IPerform = {
+ performName: '',
+ duration: 100,
+ // isOver: false,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined,
+};
diff --git a/packages/webgal/src/Core/Modules/scene.ts b/packages/webgal/src/Core/Modules/scene.ts
new file mode 100644
index 000000000..1f72069f1
--- /dev/null
+++ b/packages/webgal/src/Core/Modules/scene.ts
@@ -0,0 +1,37 @@
+import { ISceneData } from '@/Core/controller/scene/sceneInterface';
+import cloneDeep from 'lodash/cloneDeep';
+
+export interface ISceneEntry {
+ sceneName: string; // 场景名称
+ sceneUrl: string; // 场景url
+ continueLine: number; // 继续原场景的行号
+}
+
+/**
+ * 初始化场景数据
+ */
+export const initSceneData = {
+ currentSentenceId: 0, // 当前语句ID
+ sceneStack: [],
+ // 初始场景,没有数据
+ currentScene: {
+ sceneName: '', // 场景名称
+ sceneUrl: '', // 场景url
+ sentenceList: [], // 语句列表
+ assetsList: [], // 资源列表
+ subSceneList: [], // 子场景列表
+ },
+};
+
+export class SceneManager {
+ public settledScenes: Array = [];
+ public settledAssets: Array = [];
+ public sceneData: ISceneData = cloneDeep(initSceneData);
+ public lockSceneWrite = false;
+
+ public resetScene() {
+ this.sceneData.currentSentenceId = 0;
+ this.sceneData.sceneStack = [];
+ this.sceneData.currentScene = cloneDeep(initSceneData.currentScene);
+ }
+}
diff --git a/packages/webgal/src/Core/WebGAL.ts b/packages/webgal/src/Core/WebGAL.ts
new file mode 100644
index 000000000..0874d828e
--- /dev/null
+++ b/packages/webgal/src/Core/WebGAL.ts
@@ -0,0 +1,7 @@
+import { WebgalCore } from '@/Core/webgalCore';
+
+export const WebGAL = new WebgalCore();
+
+// 调试,不调试给去掉
+// @ts-ignore
+// window.WebGAL = WebGAL;
diff --git a/packages/webgal/src/Core/constants.ts b/packages/webgal/src/Core/constants.ts
new file mode 100644
index 000000000..6f7dd2a05
--- /dev/null
+++ b/packages/webgal/src/Core/constants.ts
@@ -0,0 +1,8 @@
+export const STAGE_KEYS = {
+ BGMAIN: 'bg-main',
+ FIG_C: 'fig-center',
+ FIG_L: 'fig-left',
+ FIG_R: 'fig-right',
+};
+
+export const WEBGAL_NONE = 'none';
diff --git a/packages/webgal/src/Core/controller/customUI/scss2cssinjsParser.ts b/packages/webgal/src/Core/controller/customUI/scss2cssinjsParser.ts
new file mode 100644
index 000000000..b8c14fef4
--- /dev/null
+++ b/packages/webgal/src/Core/controller/customUI/scss2cssinjsParser.ts
@@ -0,0 +1,6 @@
+import { IWebGALStyleObj } from 'webgal-parser/build/types/styleParser';
+import { WebgalParser } from '@/Core/parser/sceneParser';
+
+export function scss2cssinjsParser(scssString: string): IWebGALStyleObj {
+ return WebgalParser.parseScssToWebgalStyleObj(scssString);
+}
diff --git a/packages/webgal/src/Core/controller/gamePlay/autoPlay.ts b/packages/webgal/src/Core/controller/gamePlay/autoPlay.ts
new file mode 100644
index 000000000..0abe68278
--- /dev/null
+++ b/packages/webgal/src/Core/controller/gamePlay/autoPlay.ts
@@ -0,0 +1,77 @@
+// import {logger} from '../../util/logger';
+import styles from '@/UI/BottomControlPanel/bottomControlPanel.module.scss';
+import { webgalStore } from '@/store/store';
+import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 设置 autoplay 按钮的激活与否
+ * @param on
+ */
+const setButton = (on: boolean) => {
+ const autoIcon = document.getElementById('Button_ControlPanel_auto');
+ if (autoIcon) {
+ if (on) {
+ autoIcon.className = styles.button_on;
+ } else autoIcon.className = styles.singleButton;
+ }
+};
+
+/**
+ * 停止自动播放
+ */
+export const stopAuto = () => {
+ WebGAL.gameplay.isAuto = false;
+ setButton(false);
+ if (WebGAL.gameplay.autoInterval !== null) {
+ clearInterval(WebGAL.gameplay.autoInterval);
+ WebGAL.gameplay.autoInterval = null;
+ }
+ if (WebGAL.gameplay.autoTimeout !== null) {
+ clearTimeout(WebGAL.gameplay.autoTimeout);
+ WebGAL.gameplay.autoTimeout = null;
+ }
+};
+
+/**
+ * 切换自动播放状态
+ */
+export const switchAuto = () => {
+ // 现在正在自动播放
+ if (WebGAL.gameplay.isAuto) {
+ stopAuto();
+ } else {
+ // 当前不在自动播放
+ WebGAL.gameplay.isAuto = true;
+ setButton(true);
+ WebGAL.gameplay.autoInterval = setInterval(autoPlay, 100);
+ }
+};
+
+export const autoNextSentence = () => {
+ nextSentence();
+ WebGAL.gameplay.autoTimeout = null;
+};
+
+/**
+ * 自动播放的执行函数
+ */
+const autoPlay = () => {
+ const delay = webgalStore.getState().userData.optionData.autoSpeed;
+ const autoPlayDelay = 750 - 250 * delay;
+ let isBlockingAuto = false;
+ WebGAL.gameplay.performController.performList.forEach((e) => {
+ if (e.blockingAuto())
+ // 阻塞且没有结束的演出
+ isBlockingAuto = true;
+ });
+ if (isBlockingAuto) {
+ // 有阻塞,提前结束
+ return;
+ }
+ // nextSentence();
+ if (WebGAL.gameplay.autoTimeout === null) {
+ WebGAL.gameplay.autoTimeout = setTimeout(autoNextSentence, autoPlayDelay);
+ }
+};
diff --git a/packages/webgal/src/Core/controller/gamePlay/backToTitle.ts b/packages/webgal/src/Core/controller/gamePlay/backToTitle.ts
new file mode 100644
index 000000000..5d0c000bc
--- /dev/null
+++ b/packages/webgal/src/Core/controller/gamePlay/backToTitle.ts
@@ -0,0 +1,22 @@
+import { webgalStore } from '@/store/store';
+import { setStage } from '@/store/stageReducer';
+import { setVisibility } from '@/store/GUIReducer';
+import { stopAllPerform } from '@/Core/controller/gamePlay/stopAllPerform';
+import { stopAuto } from '@/Core/controller/gamePlay/autoPlay';
+import { stopFast } from '@/Core/controller/gamePlay/fastSkip';
+import { setEbg } from '@/Core/gameScripts/changeBg/setEbg';
+
+export const backToTitle = () => {
+ const dispatch = webgalStore.dispatch;
+ stopAllPerform();
+ stopAuto();
+ stopFast();
+ // 清除语音
+ dispatch(setStage({ key: 'playVocal', value: '' }));
+ // 重新打开标题界面
+ dispatch(setVisibility({ component: 'showTitle', visibility: true }));
+ /**
+ * 重设为标题背景
+ */
+ setEbg(webgalStore.getState().GUI.titleBg);
+};
diff --git a/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts b/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts
new file mode 100644
index 000000000..755e5d500
--- /dev/null
+++ b/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts
@@ -0,0 +1,75 @@
+// 切换自动播放状态
+import { stopAuto } from './autoPlay';
+import styles from '@/UI/BottomControlPanel/bottomControlPanel.module.scss';
+import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
+
+import { WebGAL } from '@/Core/WebGAL';
+import { SYSTEM_CONFIG } from '@/config';
+
+/**
+ * 设置 fast 按钮的激活与否
+ * @param on
+ */
+const setButton = (on: boolean) => {
+ const autoIcon = document.getElementById('Button_ControlPanel_fast');
+ if (autoIcon) {
+ if (on) {
+ autoIcon.className = styles.button_on;
+ } else autoIcon.className = styles.singleButton;
+ }
+};
+
+/**
+ * 停止快进模式
+ */
+export const stopFast = () => {
+ if (!isFast()) {
+ return;
+ }
+ WebGAL.gameplay.isFast = false;
+ setButton(false);
+ if (WebGAL.gameplay.fastInterval !== null) {
+ clearInterval(WebGAL.gameplay.fastInterval);
+ WebGAL.gameplay.fastInterval = null;
+ }
+};
+
+/**
+ * 开启快进
+ */
+export const startFast = () => {
+ if (isFast()) {
+ return;
+ }
+ WebGAL.gameplay.isFast = true;
+ setButton(true);
+ WebGAL.gameplay.fastInterval = setInterval(() => {
+ nextSentence();
+ }, SYSTEM_CONFIG.fast_timeout);
+};
+
+// 判断是否是快进模式
+export const isFast = function () {
+ return WebGAL.gameplay.isFast;
+};
+
+/**
+ * 停止快进模式与自动播放
+ */
+export const stopAll = () => {
+ stopFast();
+ stopAuto();
+};
+
+/**
+ * 切换快进模式
+ */
+export const switchFast = () => {
+ // 现在正在快进
+ if (WebGAL.gameplay.isFast) {
+ stopFast();
+ } else {
+ // 当前不在快进
+ startFast();
+ }
+};
diff --git a/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts b/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts
new file mode 100644
index 000000000..7cb4a9f78
--- /dev/null
+++ b/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts
@@ -0,0 +1,87 @@
+import { scriptExecutor } from './scriptExecutor';
+import { logger } from '../../util/logger';
+import { webgalStore } from '@/store/store';
+import { resetStageState } from '@/store/stageReducer';
+import cloneDeep from 'lodash/cloneDeep';
+import { IBacklogItem } from '@/Core/Modules/backlog';
+
+import { SYSTEM_CONFIG } from '@/config';
+import { WebGAL } from '@/Core/WebGAL';
+import { IRunPerform } from '@/store/stageInterface';
+
+/**
+ * 进行下一句
+ */
+export const nextSentence = () => {
+ /**
+ * 发送 “发生点击下一句” 事件。
+ */
+ WebGAL.events.userInteractNext.emit();
+
+ // 如果当前显示标题,那么不进行下一句
+ const GUIState = webgalStore.getState().GUI;
+ if (GUIState.showTitle) {
+ return;
+ }
+
+ // 第一步,检查是否存在 blockNext 的演出
+ let isBlockingNext = false;
+ WebGAL.gameplay.performController.performList.forEach((e) => {
+ if (e.blockingNext())
+ // 阻塞且没有结束的演出
+ isBlockingNext = true;
+ });
+ if (isBlockingNext) {
+ // 有阻塞,提前结束
+ logger.warn('next 被阻塞!');
+ return;
+ }
+
+ // 检查是否处于演出完成状态,不是则结束所有普通演出(保持演出不算做普通演出)
+ let allSettled = true;
+ WebGAL.gameplay.performController.performList.forEach((e) => {
+ if (!e.isHoldOn && !e.skipNextCollect) allSettled = false;
+ });
+ if (allSettled) {
+ // 所有普通演出已经结束
+ // if (WebGAL.backlogManager.isSaveBacklogNext) {
+ // WebGAL.backlogManager.isSaveBacklogNext = false;
+ // }
+ // 清除状态表的演出序列(因为这时候已经准备进行下一句了)
+ const stageState = webgalStore.getState().stage;
+ const newStageState = cloneDeep(stageState);
+ for (let i = 0; i < newStageState.PerformList.length; i++) {
+ const e: IRunPerform = newStageState.PerformList[i];
+ if (!e.isHoldOn) {
+ newStageState.PerformList.splice(i, 1);
+ i--;
+ }
+ }
+ webgalStore.dispatch(resetStageState(newStageState));
+ scriptExecutor();
+ return;
+ }
+
+ // 不处于 allSettled 状态,清除所有普通演出,强制进入settled。
+ logger.debug('提前结束被触发,现在清除普通演出');
+ let isGoNext = false;
+ for (let i = 0; i < WebGAL.gameplay.performController.performList.length; i++) {
+ const e = WebGAL.gameplay.performController.performList[i];
+ if (!e.isHoldOn) {
+ if (e.goNextWhenOver) {
+ isGoNext = true;
+ } // 先检查是不是要跳过收集
+ if (!e.skipNextCollect) {
+ // 由于提前结束使用的不是 unmountPerform 标准 API,所以不会触发两次 nextSentence
+ e.stopFunction();
+ clearTimeout(e.stopTimeout as unknown as number);
+ WebGAL.gameplay.performController.performList.splice(i, 1);
+ i--;
+ }
+ }
+ }
+ if (isGoNext) {
+ // 由于不使用 unmountPerform 标准 API,这里需要手动收集一下 isGoNext
+ nextSentence();
+ }
+};
diff --git a/packages/webgal/src/Core/controller/gamePlay/runScript.ts b/packages/webgal/src/Core/controller/gamePlay/runScript.ts
new file mode 100644
index 000000000..e76b76174
--- /dev/null
+++ b/packages/webgal/src/Core/controller/gamePlay/runScript.ts
@@ -0,0 +1,25 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { initPerform, IPerform } from '@/Core/Modules/perform/performInterface';
+
+import { WebGAL } from '@/Core/WebGAL';
+import { scriptRegistry, SCRIPT_TAG_MAP, ScriptFunction } from '@/Core/parser/sceneParser';
+
+/**
+ * 语句调用器,真正执行语句的调用,并自动将演出在指定时间卸载
+ * @param script 调用的语句
+ */
+export const runScript = (script: ISentence) => {
+ let perform: IPerform = initPerform;
+ const funcToRun: ScriptFunction = scriptRegistry[script.command]?.scriptFunction ?? SCRIPT_TAG_MAP.say.scriptFunction; // 默认是say
+
+ // 调用脚本对应的函数
+ perform = funcToRun(script);
+
+ if (perform.arrangePerformPromise) {
+ perform.arrangePerformPromise.then((resolovedPerform) =>
+ WebGAL.gameplay.performController.arrangeNewPerform(resolovedPerform, script),
+ );
+ } else {
+ WebGAL.gameplay.performController.arrangeNewPerform(perform, script);
+ }
+};
diff --git a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts
new file mode 100644
index 000000000..3370b99a5
--- /dev/null
+++ b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts
@@ -0,0 +1,157 @@
+import { commandType, ISentence } from '@/Core/controller/scene/sceneInterface';
+import { runScript } from './runScript';
+import { logger } from '../../util/logger';
+import { IStageState } from '@/store/stageInterface';
+import { restoreScene } from '../scene/restoreScene';
+import { webgalStore } from '@/store/store';
+import { getValueFromStateElseKey } from '@/Core/gameScripts/setVar';
+import { strIf } from '@/Core/controller/gamePlay/strIf';
+import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
+import cloneDeep from 'lodash/cloneDeep';
+import { ISceneEntry } from '@/Core/Modules/scene';
+import { IBacklogItem } from '@/Core/Modules/backlog';
+import { SYSTEM_CONFIG } from '@/config';
+import { WebGAL } from '@/Core/WebGAL';
+
+export const whenChecker = (whenValue: string | undefined): boolean => {
+ if (whenValue === undefined) {
+ return true;
+ }
+ // 先把变量解析出来
+ const valExpArr = whenValue.split(/([+\-*\/()>=|<=|==|&&|\|\||!=)/g);
+ const valExp = valExpArr
+ .map((e) => {
+ if (e.match(/[a-zA-Z]/)) {
+ if (e.match(/true/) || e.match(/false/)) {
+ return e;
+ }
+ return getValueFromStateElseKey(e, true);
+ } else return e;
+ })
+ .reduce((pre, curr) => pre + curr, '');
+ return !!strIf(valExp);
+};
+
+/**
+ * 语句执行器
+ * 执行语句,同步场景状态,并根据情况立即执行下一句或者加入backlog
+ */
+export const scriptExecutor = () => {
+ // 超过总语句数量,则从场景栈拿出一个需要继续的场景,然后继续流程。若场景栈清空,则停止流程
+ if (
+ WebGAL.sceneManager.sceneData.currentSentenceId >
+ WebGAL.sceneManager.sceneData.currentScene.sentenceList.length - 1
+ ) {
+ if (WebGAL.sceneManager.sceneData.sceneStack.length !== 0) {
+ const sceneToRestore: ISceneEntry | undefined = WebGAL.sceneManager.sceneData.sceneStack.pop();
+ if (sceneToRestore !== undefined) {
+ restoreScene(sceneToRestore);
+ }
+ }
+ return;
+ }
+ const currentScript: ISentence =
+ WebGAL.sceneManager.sceneData.currentScene.sentenceList[WebGAL.sceneManager.sceneData.currentSentenceId];
+
+ const interpolationOneItem = (content: string): string => {
+ let retContent = content;
+ const contentExp = retContent.match(/(? {
+ const contentVarValue = getValueFromStateElseKey(e.replace(/(? {
+ currentScript.content = interpolationOneItem(currentScript.content);
+
+ currentScript.args.forEach((arg) => {
+ if (arg.value && typeof arg.value === 'string') {
+ arg.value = interpolationOneItem(arg.value);
+ }
+ });
+ };
+
+ variableInterpolation();
+
+ // 判断这个脚本要不要执行
+ let runThis = true;
+ let isHasWhenArg = false;
+ let whenValue = '';
+ currentScript.args.forEach((e) => {
+ if (e.key === 'when') {
+ isHasWhenArg = true;
+ whenValue = e.value.toString();
+ }
+ });
+ // 如果语句有 when
+ if (isHasWhenArg) {
+ runThis = whenChecker(whenValue);
+ }
+
+ // 执行语句
+ if (!runThis) {
+ logger.warn('不满足条件,跳过本句!');
+ WebGAL.sceneManager.sceneData.currentSentenceId++;
+ nextSentence();
+ return;
+ }
+ runScript(currentScript);
+ let isNext = false; // 是否要进行下一句
+ currentScript.args.forEach((e) => {
+ // 判断是否有下一句的参数
+ if (e.key === 'next' && e.value) {
+ isNext = true;
+ }
+ });
+
+ let isSaveBacklog = currentScript.command === commandType.say; // 是否在本句保存backlog(一般遇到对话保存)
+ // 检查当前对话是否有 notend 参数
+ currentScript.args.forEach((e) => {
+ if (e.key === 'notend' && e.value === true) {
+ isSaveBacklog = false;
+ }
+ });
+ let currentStageState: IStageState;
+
+ // 执行至指定 sentenceID
+ // if (runToSentence >= 0 && runtime_currentSceneData.currentSentenceId < runToSentence) {
+ // runtime_currentSceneData.currentSentenceId++;
+ // scriptExecutor(runToSentence);
+ // return;
+ // }
+
+ // 执行“下一句”
+ if (isNext) {
+ WebGAL.sceneManager.sceneData.currentSentenceId++;
+ scriptExecutor();
+ return;
+ }
+
+ /**
+ * 为了让 backlog 拿到连续执行了多条语句后正确的数据,放到下一个宏任务中执行(我也不知道为什么这样能正常,有能力的可以研究一下
+ */
+ setTimeout(() => {
+ // 同步当前舞台数据
+ currentStageState = webgalStore.getState().stage;
+ const allState = {
+ currentStageState: currentStageState,
+ globalGameVar: webgalStore.getState().userData.globalGameVar,
+ };
+ logger.debug('本条语句执行结果', allState);
+ // 保存 backlog
+ if (isSaveBacklog) {
+ // WebGAL.backlogManager.isSaveBacklogNext = true;
+ WebGAL.backlogManager.saveCurrentStateToBacklog();
+ }
+ }, 0);
+ WebGAL.sceneManager.sceneData.currentSentenceId++;
+};
diff --git a/packages/webgal/src/Core/controller/gamePlay/startContinueGame.ts b/packages/webgal/src/Core/controller/gamePlay/startContinueGame.ts
new file mode 100644
index 000000000..e3998acd5
--- /dev/null
+++ b/packages/webgal/src/Core/controller/gamePlay/startContinueGame.ts
@@ -0,0 +1,52 @@
+import { assetSetter, fileType } from '../../util/gameAssetsAccess/assetSetter';
+import { sceneFetcher } from '../scene/sceneFetcher';
+import { sceneParser } from '../../parser/sceneParser';
+import { resetStage } from '@/Core/controller/stage/resetStage';
+import { webgalStore } from '@/store/store';
+import { setVisibility } from '@/store/GUIReducer';
+import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
+import { setEbg } from '@/Core/gameScripts/changeBg/setEbg';
+import { restorePerform } from '@/Core/controller/storage/jumpFromBacklog';
+
+import { hasFastSaveRecord, loadFastSaveGame } from '@/Core/controller/storage/fastSaveLoad';
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 从头开始游戏
+ */
+export const startGame = () => {
+ resetStage(true);
+
+ // 重新获取初始场景
+ const sceneUrl: string = assetSetter('start.txt', fileType.scene);
+ // 场景写入到运行时
+ sceneFetcher(sceneUrl).then((rawScene) => {
+ WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, 'start.txt', sceneUrl);
+ // 开始第一条语句
+ nextSentence();
+ });
+ webgalStore.dispatch(setVisibility({ component: 'showTitle', visibility: false }));
+};
+
+export async function continueGame() {
+ /**
+ * 重设模糊背景
+ */
+ setEbg(webgalStore.getState().stage.bgName);
+ // 当且仅当游戏未开始时使用快速存档
+ // 当游戏开始后 使用原来的逻辑
+ if ((await hasFastSaveRecord()) && WebGAL.sceneManager.sceneData.currentSentenceId === 0) {
+ // 恢复记录
+ await loadFastSaveGame();
+ return;
+ }
+ if (
+ WebGAL.sceneManager.sceneData.currentSentenceId === 0 &&
+ WebGAL.sceneManager.sceneData.currentScene.sceneName === 'start.txt'
+ ) {
+ // 如果游戏没有开始,开始游戏
+ nextSentence();
+ } else {
+ restorePerform();
+ }
+}
diff --git a/packages/webgal/src/Core/controller/gamePlay/stopAllPerform.ts b/packages/webgal/src/Core/controller/gamePlay/stopAllPerform.ts
new file mode 100644
index 000000000..fd3746d81
--- /dev/null
+++ b/packages/webgal/src/Core/controller/gamePlay/stopAllPerform.ts
@@ -0,0 +1,14 @@
+import { logger } from '@/Core/util/logger';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+export const stopAllPerform = () => {
+ logger.warn('清除所有演出');
+ for (let i = 0; i < WebGAL.gameplay.performController.performList.length; i++) {
+ const e = WebGAL.gameplay.performController.performList[i];
+ e.stopFunction();
+ clearTimeout(e.stopTimeout as unknown as number);
+ WebGAL.gameplay.performController.performList.splice(i, 1);
+ i--;
+ }
+};
diff --git a/packages/webgal/src/Core/controller/gamePlay/strIf.ts b/packages/webgal/src/Core/controller/gamePlay/strIf.ts
new file mode 100644
index 000000000..82f7d87f1
--- /dev/null
+++ b/packages/webgal/src/Core/controller/gamePlay/strIf.ts
@@ -0,0 +1,10 @@
+import { compile } from 'angular-expressions';
+
+export function strIf(s: string) {
+ try {
+ const res = compile(s);
+ return res();
+ } catch {
+ return false;
+ }
+}
diff --git a/packages/webgal/src/Core/controller/scene/callScene.ts b/packages/webgal/src/Core/controller/scene/callScene.ts
new file mode 100644
index 000000000..ead6e3bd1
--- /dev/null
+++ b/packages/webgal/src/Core/controller/scene/callScene.ts
@@ -0,0 +1,44 @@
+import { sceneFetcher } from './sceneFetcher';
+import { sceneParser } from '../../parser/sceneParser';
+import { logger } from '../../util/logger';
+import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
+import uniqWith from 'lodash/uniqWith';
+import { scenePrefetcher } from '@/Core/util/prefetcher/scenePrefetcher';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 调用场景
+ * @param sceneUrl 场景路径
+ * @param sceneName 场景名称
+ */
+export const callScene = (sceneUrl: string, sceneName: string) => {
+ if (WebGAL.sceneManager.lockSceneWrite) {
+ return;
+ }
+ WebGAL.sceneManager.lockSceneWrite = true;
+ // 先将本场景压入场景栈
+ WebGAL.sceneManager.sceneData.sceneStack.push({
+ sceneName: WebGAL.sceneManager.sceneData.currentScene.sceneName,
+ sceneUrl: WebGAL.sceneManager.sceneData.currentScene.sceneUrl,
+ continueLine: WebGAL.sceneManager.sceneData.currentSentenceId,
+ });
+ // 场景写入到运行时
+ sceneFetcher(sceneUrl)
+ .then((rawScene) => {
+ WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, sceneName, sceneUrl);
+ WebGAL.sceneManager.sceneData.currentSentenceId = 0;
+ // 开始场景的预加载
+ const subSceneList = WebGAL.sceneManager.sceneData.currentScene.subSceneList;
+ WebGAL.sceneManager.settledScenes.push(sceneUrl); // 放入已加载场景列表,避免递归加载相同场景
+ const subSceneListUniq = uniqWith(subSceneList); // 去重
+ scenePrefetcher(subSceneListUniq);
+ logger.debug('现在调用场景,调用结果:', WebGAL.sceneManager.sceneData);
+ WebGAL.sceneManager.lockSceneWrite = false;
+ nextSentence();
+ })
+ .catch((e) => {
+ logger.error('场景调用错误', e);
+ WebGAL.sceneManager.lockSceneWrite = false;
+ });
+};
diff --git a/packages/webgal/src/Core/controller/scene/changeScene.ts b/packages/webgal/src/Core/controller/scene/changeScene.ts
new file mode 100644
index 000000000..67df0879a
--- /dev/null
+++ b/packages/webgal/src/Core/controller/scene/changeScene.ts
@@ -0,0 +1,38 @@
+import { sceneFetcher } from './sceneFetcher';
+import { sceneParser } from '../../parser/sceneParser';
+import { logger } from '../../util/logger';
+import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
+import uniqWith from 'lodash/uniqWith';
+import { scenePrefetcher } from '@/Core/util/prefetcher/scenePrefetcher';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 切换场景
+ * @param sceneUrl 场景路径
+ * @param sceneName 场景名称
+ */
+export const changeScene = (sceneUrl: string, sceneName: string) => {
+ if (WebGAL.sceneManager.lockSceneWrite) {
+ return;
+ }
+ WebGAL.sceneManager.lockSceneWrite = true;
+ // 场景写入到运行时
+ sceneFetcher(sceneUrl)
+ .then((rawScene) => {
+ WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, sceneName, sceneUrl);
+ WebGAL.sceneManager.sceneData.currentSentenceId = 0;
+ // 开始场景的预加载
+ const subSceneList = WebGAL.sceneManager.sceneData.currentScene.subSceneList;
+ WebGAL.sceneManager.settledScenes.push(sceneUrl); // 放入已加载场景列表,避免递归加载相同场景
+ const subSceneListUniq = uniqWith(subSceneList); // 去重
+ scenePrefetcher(subSceneListUniq);
+ logger.debug('现在切换场景,切换后的结果:', WebGAL.sceneManager.sceneData);
+ WebGAL.sceneManager.lockSceneWrite = false;
+ nextSentence();
+ })
+ .catch((e) => {
+ logger.error('场景调用错误', e);
+ WebGAL.sceneManager.lockSceneWrite = false;
+ });
+};
diff --git a/packages/webgal/src/Core/controller/scene/restoreScene.ts b/packages/webgal/src/Core/controller/scene/restoreScene.ts
new file mode 100644
index 000000000..1486c272c
--- /dev/null
+++ b/packages/webgal/src/Core/controller/scene/restoreScene.ts
@@ -0,0 +1,31 @@
+import { sceneFetcher } from './sceneFetcher';
+import { sceneParser } from '../../parser/sceneParser';
+import { logger } from '../../util/logger';
+import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
+import { ISceneEntry } from '@/Core/Modules/scene';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 恢复场景
+ * @param entry 场景入口
+ */
+export const restoreScene = (entry: ISceneEntry) => {
+ if (WebGAL.sceneManager.lockSceneWrite) {
+ return;
+ }
+ WebGAL.sceneManager.lockSceneWrite = true;
+ // 场景写入到运行时
+ sceneFetcher(entry.sceneUrl)
+ .then((rawScene) => {
+ WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, entry.sceneName, entry.sceneUrl);
+ WebGAL.sceneManager.sceneData.currentSentenceId = entry.continueLine + 1; // 重设场景
+ logger.debug('现在恢复场景,恢复后场景:', WebGAL.sceneManager.sceneData.currentScene);
+ WebGAL.sceneManager.lockSceneWrite = false;
+ nextSentence();
+ })
+ .catch((e) => {
+ logger.error('场景调用错误', e);
+ WebGAL.sceneManager.lockSceneWrite = false;
+ });
+};
diff --git a/packages/webgal/src/Core/controller/scene/sceneFetcher.ts b/packages/webgal/src/Core/controller/scene/sceneFetcher.ts
new file mode 100644
index 000000000..c66c04cf1
--- /dev/null
+++ b/packages/webgal/src/Core/controller/scene/sceneFetcher.ts
@@ -0,0 +1,19 @@
+import axios from 'axios';
+
+/**
+ * 原始场景文件获取函数
+ * @param sceneUrl 场景文件路径
+ */
+export const sceneFetcher = (sceneUrl: string) => {
+ return new Promise((resolve, reject) => {
+ axios
+ .get(sceneUrl)
+ .then((response) => {
+ const rawScene: string = response.data.toString();
+ resolve(rawScene);
+ })
+ .catch((e) => {
+ reject(e);
+ });
+ });
+};
diff --git a/packages/webgal/src/Core/controller/scene/sceneInterface.ts b/packages/webgal/src/Core/controller/scene/sceneInterface.ts
new file mode 100644
index 000000000..a4c556dbf
--- /dev/null
+++ b/packages/webgal/src/Core/controller/scene/sceneInterface.ts
@@ -0,0 +1,105 @@
+/**
+ * 语句类型
+ */
+import { fileType } from '@/Core/util/gameAssetsAccess/assetSetter';
+import { ISceneEntry } from '@/Core/Modules/scene';
+
+export enum commandType {
+ say, // 对话
+ changeBg, // 更改背景
+ changeFigure, // 更改立绘
+ bgm, // 更改背景音乐
+ video, // 播放视频
+ pixi, // pixi演出
+ pixiInit, // pixi初始化
+ intro, // 黑屏文字演示
+ miniAvatar, // 小头像
+ changeScene, // 切换场景
+ choose, // 分支选择
+ end, // 结束游戏
+ setComplexAnimation, // 动画演出
+ setFilter, // 设置效果
+ label, // 标签
+ jumpLabel, // 跳转标签
+ chooseLabel, // 选择标签
+ setVar, // 设置变量
+ if, // 条件跳转
+ callScene, // 调用场景
+ showVars,
+ unlockCg,
+ unlockBgm,
+ filmMode,
+ setTextbox,
+ setAnimation,
+ playEffect,
+ setTempAnimation,
+ comment,
+ setTransform,
+ setTransition,
+ getUserInput,
+ applyStyle,
+}
+
+/**
+ * 单个参数接口
+ * @interface arg
+ */
+export interface arg {
+ key: string; // 参数键
+ value: string | boolean | number; // 参数值
+}
+
+/**
+ * 资源接口
+ * @interface IAsset
+ */
+export interface IAsset {
+ name: string; // 资源名称
+ type: fileType; // 资源类型
+ url: string; // 资源url
+ lineNumber: number; // 触发资源语句的行号
+}
+
+/**
+ * 单条语句接口
+ * @interface ISentence
+ */
+export interface ISentence {
+ command: commandType; // 语句类型
+ commandRaw: string; // 命令的原始内容,方便调试
+ content: string; // 语句内容
+ args: Array; // 参数列表
+ sentenceAssets: Array; // 语句携带的资源列表
+ subScene: Array; // 语句包含子场景列表
+}
+
+/**
+ * 场景接口
+ * @interface IScene
+ */
+export interface IScene {
+ sceneName: string; // 场景名称
+ sceneUrl: string; // 场景url
+ sentenceList: Array; // 语句列表
+ assetsList: Array; // 资源列表
+ subSceneList: Array; // 子场景的url列表
+}
+
+/**
+ * 当前的场景数据
+ * @interface ISceneData
+ */
+export interface ISceneData {
+ currentSentenceId: number; // 当前语句ID
+ sceneStack: Array; // 场景栈
+ currentScene: IScene; // 当前场景数据
+}
+
+/**
+ * 处理后的命令接口
+ * @interface parsedCommand
+ */
+export interface parsedCommand {
+ type: commandType;
+ additionalArgs: Array;
+}
diff --git a/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts b/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts
new file mode 100644
index 000000000..ddd8aac00
--- /dev/null
+++ b/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts
@@ -0,0 +1,1053 @@
+import * as PIXI from 'pixi.js';
+import { v4 as uuid } from 'uuid';
+import { webgalStore } from '@/store/store';
+import { setStage, stageActions } from '@/store/stageReducer';
+import cloneDeep from 'lodash/cloneDeep';
+import { IEffect, IFigureAssociatedAnimation, IFigureMetadata } from '@/store/stageInterface';
+import { logger } from '@/Core/util/logger';
+import { isIOS } from '@/Core/initializeScript';
+import { WebGALPixiContainer } from '@/Core/controller/stage/pixi/WebGALPixiContainer';
+import { WebGAL } from '@/Core/WebGAL';
+import 'pixi-spine'; // Do this once at the very start of your code. This registers the loader!
+import { Spine } from 'pixi-spine';
+import { SCREEN_CONSTANTS } from '@/Core/util/constants';
+// import { figureCash } from '@/Core/gameScripts/vocal/conentsCash'; // 如果要使用 Live2D,取消这里的注释
+// import { Live2DModel, SoundManager } from 'pixi-live2d-display-webgal'; // 如果要使用 Live2D,取消这里的注释
+
+export interface IAnimationObject {
+ setStartState: Function;
+ setEndState: Function;
+ tickerFunc: PIXI.TickerCallback;
+ getEndFilterEffect?: Function;
+}
+
+interface IStageAnimationObject {
+ // 唯一标识
+ uuid: string;
+ // 一般与作用目标有关
+ key: string;
+ targetKey?: string;
+ type: 'common' | 'preset';
+ animationObject: IAnimationObject;
+}
+
+export interface IStageObject {
+ // 唯一标识
+ uuid: string;
+ // 一般与作用目标有关
+ key: string;
+ pixiContainer: WebGALPixiContainer;
+ // 相关的源 url
+ sourceUrl: string;
+ sourceExt: string;
+ sourceType: 'img' | 'live2d' | 'spine' | 'gif' | 'video';
+}
+
+export interface ILive2DRecord {
+ target: string;
+ motion: string;
+ expression: string;
+}
+
+// export interface IRegisterTickerOpr {
+// tickerGeneratorFn: (targetKey: string, duration: number) => PIXI.TickerCallback;
+// key: string;
+// target: string;
+// duration: number;
+// }
+
+// @ts-ignore
+window.PIXI = PIXI;
+
+export default class PixiStage {
+ /**
+ * 当前的 PIXI App
+ */
+ public currentApp: PIXI.Application | null = null;
+ public readonly effectsContainer: PIXI.Container;
+ public frameDuration = 16.67;
+ public notUpdateBacklogEffects = false;
+ private readonly figureContainer: PIXI.Container;
+ private figureObjects: Array = [];
+ private readonly backgroundContainer: PIXI.Container;
+ private backgroundObjects: Array = [];
+
+ // 注册到 Ticker 上的函数
+ private stageAnimations: Array = [];
+ private assetLoader = new PIXI.Loader();
+ private loadQueue: { url: string; callback: () => void; name?: string }[] = [];
+ private live2dFigureRecorder: Array = [];
+
+ // 锁定变换对象(对象可能正在执行动画,不能应用变换)
+ private lockTransformTarget: Array = [];
+ private stageWidth = SCREEN_CONSTANTS.width;
+ private stageHeight = SCREEN_CONSTANTS.height;
+ /**
+ * 暂时没用上,以后可能用
+ * @private
+ */
+ private MAX_TEX_COUNT = 10;
+
+ public constructor() {
+ const app = new PIXI.Application({
+ backgroundAlpha: 0,
+ preserveDrawingBuffer: true,
+ });
+ // @ts-ignore
+
+ window.PIXIapp = this; // @ts-ignore
+ window.__PIXI_APP__ = app;
+ // 清空原节点
+ const pixiContainer = document.getElementById('pixiContianer');
+ if (pixiContainer) {
+ pixiContainer.innerHTML = '';
+ pixiContainer.appendChild(app.view);
+ }
+
+ // 设置样式
+ app.renderer.view.style.position = 'absolute';
+ app.renderer.view.style.display = 'block';
+ app.renderer.view.id = 'pixiCanvas';
+ // @ts-ignore
+ app.renderer.autoResize = true;
+ const appRoot = document.getElementById('root');
+ if (appRoot) {
+ app.renderer.resize(appRoot.clientWidth, appRoot.clientHeight);
+ }
+ if (isIOS) {
+ app.renderer.view.style.zIndex = '-5';
+ }
+
+ // 设置可排序
+ app.stage.sortableChildren = true;
+
+ // 添加 3 个 Container 用于做渲染
+ this.effectsContainer = new PIXI.Container();
+ this.effectsContainer.zIndex = 3;
+ this.figureContainer = new PIXI.Container();
+ this.figureContainer.sortableChildren = true; // 允许立绘启用 z-index
+ this.figureContainer.zIndex = 2;
+ this.backgroundContainer = new PIXI.Container();
+ this.backgroundContainer.zIndex = 0;
+ app.stage.addChild(this.effectsContainer, this.figureContainer, this.backgroundContainer);
+ this.currentApp = app;
+ // 每 5s 获取帧率,并且防 loader 死
+ const update = () => {
+ this.updateFps();
+ setTimeout(update, 10000);
+ };
+ update();
+ // loader 防死
+ const reload = () => {
+ setTimeout(reload, 500);
+ this.callLoader();
+ };
+ reload();
+ }
+
+ public getFigureObjects() {
+ return this.figureObjects;
+ }
+
+ public getAllLockedObject() {
+ return this.lockTransformTarget;
+ }
+
+ /**
+ * 注册动画
+ * @param animationObject
+ * @param key
+ * @param target
+ */
+ public registerAnimation(animationObject: IAnimationObject | null, key: string, target = 'default') {
+ if (!animationObject) return;
+ this.stageAnimations.push({ uuid: uuid(), animationObject, key: key, targetKey: target, type: 'common' });
+ // 上锁
+ this.lockStageObject(target);
+ animationObject.setStartState();
+ this.currentApp?.ticker.add(animationObject.tickerFunc);
+ }
+
+ /**
+ * 注册预设动画
+ * @param animationObject
+ * @param key
+ * @param target
+ * @param currentEffects
+ */
+ // eslint-disable-next-line max-params
+ public registerPresetAnimation(
+ animationObject: IAnimationObject | null,
+ key: string,
+ target = 'default',
+ currentEffects: IEffect[],
+ ) {
+ if (!animationObject) return;
+ const effect = currentEffects.find((effect) => effect.target === target);
+ if (effect) {
+ const targetPixiContainer = this.getStageObjByKey(target);
+ if (targetPixiContainer) {
+ const container = targetPixiContainer.pixiContainer;
+ Object.assign(container, effect.transform);
+ }
+ return;
+ }
+ this.stageAnimations.push({ uuid: uuid(), animationObject, key: key, targetKey: target, type: 'preset' });
+ // 上锁
+ this.lockStageObject(target);
+ animationObject.setStartState();
+ this.currentApp?.ticker.add(animationObject.tickerFunc);
+ }
+
+ public stopPresetAnimationOnTarget(target: string) {
+ const targetPresetAnimations = this.stageAnimations.find((e) => e.targetKey === target && e.type === 'preset');
+ if (targetPresetAnimations) {
+ this.removeAnimation(targetPresetAnimations.key);
+ }
+ }
+
+ /**
+ * 移除动画
+ * @param key
+ */
+ public removeAnimation(key: string) {
+ const index = this.stageAnimations.findIndex((e) => e.key === key);
+ if (index >= 0) {
+ const thisTickerFunc = this.stageAnimations[index];
+ this.currentApp?.ticker.remove(thisTickerFunc.animationObject.tickerFunc);
+ thisTickerFunc.animationObject.setEndState();
+ this.unlockStageObject(thisTickerFunc.targetKey ?? 'default');
+ this.stageAnimations.splice(index, 1);
+ }
+ }
+
+ public removeAnimationWithSetEffects(key: string) {
+ const index = this.stageAnimations.findIndex((e) => e.key === key);
+ if (index >= 0) {
+ const thisTickerFunc = this.stageAnimations[index];
+ this.currentApp?.ticker.remove(thisTickerFunc.animationObject.tickerFunc);
+ thisTickerFunc.animationObject.setEndState();
+ const webgalFilters = thisTickerFunc.animationObject.getEndFilterEffect?.() ?? {};
+ this.unlockStageObject(thisTickerFunc.targetKey ?? 'default');
+ if (thisTickerFunc.targetKey) {
+ const target = this.getStageObjByKey(thisTickerFunc.targetKey);
+ if (target) {
+ const targetTransform = {
+ alpha: target.pixiContainer.alphaFilterVal,
+ scale: {
+ x: target.pixiContainer.scale.x,
+ y: target.pixiContainer.scale.y,
+ },
+ // pivot: {
+ // x: target.pixiContainer.pivot.x,
+ // y: target.pixiContainer.pivot.y,
+ // },
+ position: {
+ x: target.pixiContainer.x,
+ y: target.pixiContainer.y,
+ },
+ rotation: target.pixiContainer.rotation,
+ // @ts-ignore
+ blur: target.pixiContainer.blur,
+ ...webgalFilters,
+ };
+ let effect: IEffect = {
+ target: thisTickerFunc.targetKey,
+ transform: targetTransform,
+ };
+ webgalStore.dispatch(stageActions.updateEffect(effect));
+ // if (!this.notUpdateBacklogEffects) updateCurrentBacklogEffects(webgalStore.getState().stage.effects);
+ }
+ }
+ this.stageAnimations.splice(index, 1);
+ }
+ }
+
+ // eslint-disable-next-line max-params
+ public performMouthSyncAnimation(
+ key: string,
+ targetAnimation: IFigureAssociatedAnimation,
+ mouthState: string,
+ presetPosition: string,
+ ) {
+ const currentFigure = this.getStageObjByKey(key)?.pixiContainer as WebGALPixiContainer;
+
+ if (!currentFigure) {
+ return;
+ }
+
+ const mouthTextureUrls: any = {
+ open: targetAnimation.mouthAnimation.open,
+ half_open: targetAnimation.mouthAnimation.halfOpen,
+ closed: targetAnimation.mouthAnimation.close,
+ };
+
+ // Load mouth texture (reuse if already loaded)
+ this.loadAsset(mouthTextureUrls[mouthState], () => {
+ const texture = this.assetLoader.resources[mouthTextureUrls[mouthState]].texture;
+ const sprite = currentFigure?.children?.[0] as PIXI.Sprite;
+ if (!texture || !sprite) {
+ return;
+ }
+ sprite.texture = texture;
+ });
+ }
+
+ // eslint-disable-next-line max-params
+ public performBlinkAnimation(
+ key: string,
+ targetAnimation: IFigureAssociatedAnimation,
+ blinkState: string,
+ presetPosition: string,
+ ) {
+ const currentFigure = this.getStageObjByKey(key)?.pixiContainer as WebGALPixiContainer;
+
+ if (!currentFigure) {
+ return;
+ }
+ const blinkTextureUrls: any = {
+ open: targetAnimation.blinkAnimation.open,
+ closed: targetAnimation.blinkAnimation.close,
+ };
+
+ // Load eye texture (reuse if already loaded)
+ this.loadAsset(blinkTextureUrls[blinkState], () => {
+ const texture = this.assetLoader.resources[blinkTextureUrls[blinkState]].texture;
+ const sprite = currentFigure?.children?.[0] as PIXI.Sprite;
+ if (!texture || !sprite) {
+ return;
+ }
+ sprite.texture = texture;
+ });
+ }
+
+ /**
+ * 添加背景
+ * @param key 背景的标识,一般和背景类型有关
+ * @param url 背景图片url
+ */
+ public addBg(key: string, url: string) {
+ // const loader = this.assetLoader;
+ const loader = this.assetLoader;
+ // 准备用于存放这个背景的 Container
+ const thisBgContainer = new WebGALPixiContainer();
+
+ // 是否有相同 key 的背景
+ const setBgIndex = this.backgroundObjects.findIndex((e) => e.key === key);
+ const isBgSet = setBgIndex >= 0;
+
+ // 已经有一个这个 key 的背景存在了
+ if (isBgSet) {
+ // 挤占
+ this.removeStageObjectByKey(key);
+ }
+
+ // 挂载
+ this.backgroundContainer.addChild(thisBgContainer);
+ const bgUuid = uuid();
+ this.backgroundObjects.push({
+ uuid: bgUuid,
+ key: key,
+ pixiContainer: thisBgContainer,
+ sourceUrl: url,
+ sourceType: 'img',
+ sourceExt: this.getExtName(url),
+ });
+
+ // 完成图片加载后执行的函数
+ const setup = () => {
+ // TODO:找一个更好的解法,现在的解法是无论是否复用原来的资源,都设置一个延时以让动画工作正常!
+
+ setTimeout(() => {
+ const texture = loader.resources?.[url]?.texture;
+ if (texture && this.getStageObjByUuid(bgUuid)) {
+ /**
+ * 重设大小
+ */
+ const originalWidth = texture.width;
+ const originalHeight = texture.height;
+ const scaleX = this.stageWidth / originalWidth;
+ const scaleY = this.stageHeight / originalHeight;
+ const targetScale = Math.max(scaleX, scaleY);
+ const bgSprite = new PIXI.Sprite(texture);
+ bgSprite.scale.x = targetScale;
+ bgSprite.scale.y = targetScale;
+ bgSprite.anchor.set(0.5);
+ bgSprite.position.y = this.stageHeight / 2;
+ thisBgContainer.setBaseX(this.stageWidth / 2);
+ thisBgContainer.setBaseY(this.stageHeight / 2);
+ thisBgContainer.pivot.set(0, this.stageHeight / 2);
+
+ // 挂载
+ thisBgContainer.addChild(bgSprite);
+ }
+ }, 0);
+ };
+
+ /**
+ * 加载器部分
+ */
+ this.cacheGC();
+ if (!loader.resources?.[url]?.texture) {
+ this.loadAsset(url, setup);
+ } else {
+ // 复用
+ setup();
+ }
+ }
+
+ public addSpineBg(key: string, url: string) {
+ const spineId = `spine-${url}`;
+ const loader = this.assetLoader;
+ // 准备用于存放这个背景的 Container
+ const thisBgContainer = new WebGALPixiContainer();
+
+ // 是否有相同 key 的背景
+ const setBgIndex = this.backgroundObjects.findIndex((e) => e.key === key);
+ const isBgSet = setBgIndex >= 0;
+
+ // 已经有一个这个 key 的背景存在了
+ if (isBgSet) {
+ // 挤占
+ this.removeStageObjectByKey(key);
+ }
+
+ // 挂载
+ this.backgroundContainer.addChild(thisBgContainer);
+ const bgUuid = uuid();
+ this.backgroundObjects.push({
+ uuid: bgUuid,
+ key: key,
+ pixiContainer: thisBgContainer,
+ sourceUrl: url,
+ sourceType: 'live2d',
+ sourceExt: this.getExtName(url),
+ });
+
+ // 完成图片加载后执行的函数
+ const setup = () => {
+ const spineResource: any = this.assetLoader.resources?.[spineId];
+ // TODO:找一个更好的解法,现在的解法是无论是否复用原来的资源,都设置一个延时以让动画工作正常!
+ setTimeout(() => {
+ if (spineResource && this.getStageObjByUuid(bgUuid)) {
+ const bgSpine = new Spine(spineResource.spineData);
+ const transY = spineResource?.spineData?.y ?? 0;
+ /**
+ * 重设大小
+ */
+ const originalWidth = bgSpine.width; // TODO: 视图大小可能小于画布大小,应提供参数指定视图大小
+ const originalHeight = bgSpine.height; // TODO: 视图大小可能小于画布大小,应提供参数指定视图大小
+ const scaleX = this.stageWidth / originalWidth;
+ const scaleY = this.stageHeight / originalHeight;
+ logger.debug('bgSpine state', bgSpine.state);
+ // TODO: 也许应该使用 setAnimation 播放初始动画
+ if (bgSpine.spineData.animations.length > 0) {
+ // 播放首个动画
+ bgSpine.state.setAnimation(0, bgSpine.spineData.animations[0].name, true);
+ }
+ const targetScale = Math.max(scaleX, scaleY);
+ const bgSprite = new PIXI.Sprite();
+ bgSprite.addChild(bgSpine);
+ bgSprite.scale.x = targetScale;
+ bgSprite.scale.y = targetScale;
+ bgSprite.anchor.set(0.5);
+ bgSprite.position.y = this.stageHeight / 2;
+ thisBgContainer.setBaseX(this.stageWidth / 2);
+ thisBgContainer.setBaseY(this.stageHeight / 2);
+ thisBgContainer.pivot.set(0, this.stageHeight / 2);
+
+ // 挂载
+ thisBgContainer.addChild(bgSprite);
+ }
+ }, 0);
+ };
+
+ /**
+ * 加载器部分
+ */
+ this.cacheGC();
+ if (!loader.resources?.[url]) {
+ this.loadAsset(url, setup, spineId);
+ } else {
+ // 复用
+ setup();
+ }
+ }
+
+ /**
+ * 添加立绘
+ * @param key 立绘的标识,一般和立绘位置有关
+ * @param url 立绘图片url
+ * @param presetPosition
+ */
+ public addFigure(key: string, url: string, presetPosition: 'left' | 'center' | 'right' = 'center') {
+ const loader = this.assetLoader;
+ // 准备用于存放这个立绘的 Container
+ const thisFigureContainer = new WebGALPixiContainer();
+
+ // 是否有相同 key 的立绘
+ const setFigIndex = this.figureObjects.findIndex((e) => e.key === key);
+ const isFigSet = setFigIndex >= 0;
+
+ // 已经有一个这个 key 的立绘存在了
+ if (isFigSet) {
+ this.removeStageObjectByKey(key);
+ }
+
+ const metadata = this.getFigureMetadataByKey(key);
+ if (metadata) {
+ if (metadata.zIndex) {
+ thisFigureContainer.zIndex = metadata.zIndex;
+ }
+ }
+ // 挂载
+ this.figureContainer.addChild(thisFigureContainer);
+ const figureUuid = uuid();
+ this.figureObjects.push({
+ uuid: figureUuid,
+ key: key,
+ pixiContainer: thisFigureContainer,
+ sourceUrl: url,
+ sourceType: 'img',
+ sourceExt: this.getExtName(url),
+ });
+
+ // 完成图片加载后执行的函数
+ const setup = () => {
+ // TODO:找一个更好的解法,现在的解法是无论是否复用原来的资源,都设置一个延时以让动画工作正常!
+ setTimeout(() => {
+ const texture = loader.resources?.[url]?.texture;
+ if (texture && this.getStageObjByUuid(figureUuid)) {
+ /**
+ * 重设大小
+ */
+ const originalWidth = texture.width;
+ const originalHeight = texture.height;
+ const scaleX = this.stageWidth / originalWidth;
+ const scaleY = this.stageHeight / originalHeight;
+ const targetScale = Math.min(scaleX, scaleY);
+ const figureSprite = new PIXI.Sprite(texture);
+ figureSprite.scale.x = targetScale;
+ figureSprite.scale.y = targetScale;
+ figureSprite.anchor.set(0.5);
+ figureSprite.position.y = this.stageHeight / 2;
+ const targetWidth = originalWidth * targetScale;
+ const targetHeight = originalHeight * targetScale;
+ thisFigureContainer.setBaseY(this.stageHeight / 2);
+ if (targetHeight < this.stageHeight) {
+ thisFigureContainer.setBaseY(this.stageHeight / 2 + this.stageHeight - targetHeight / 2);
+ }
+ if (presetPosition === 'center') {
+ thisFigureContainer.setBaseX(this.stageWidth / 2);
+ }
+ if (presetPosition === 'left') {
+ thisFigureContainer.setBaseX(targetWidth / 2);
+ }
+ if (presetPosition === 'right') {
+ thisFigureContainer.setBaseX(this.stageWidth - targetWidth / 2);
+ }
+ thisFigureContainer.pivot.set(0, this.stageHeight / 2);
+ thisFigureContainer.addChild(figureSprite);
+ }
+ }, 0);
+ };
+
+ /**
+ * 加载器部分
+ */
+ this.cacheGC();
+ if (!loader.resources?.[url]?.texture) {
+ this.loadAsset(url, setup);
+ } else {
+ // 复用
+ setup();
+ }
+ }
+
+ /**
+ * 添加 Spine 立绘
+ * @param key 立绘的标识,一般和立绘位置有关
+ * @param url 立绘图片url
+ * @param presetPosition
+ */
+ public addSpineFigure(key: string, url: string, presetPosition: 'left' | 'center' | 'right' = 'center') {
+ const spineId = `spine-${url}`;
+ const loader = this.assetLoader;
+ // 准备用于存放这个立绘的 Container
+ const thisFigureContainer = new WebGALPixiContainer();
+
+ // 是否有相同 key 的立绘
+ const setFigIndex = this.figureObjects.findIndex((e) => e.key === key);
+ const isFigSet = setFigIndex >= 0;
+
+ // 已经有一个这个 key 的立绘存在了
+ if (isFigSet) {
+ this.removeStageObjectByKey(key);
+ }
+
+ const metadata = this.getFigureMetadataByKey(key);
+ if (metadata) {
+ if (metadata.zIndex) {
+ thisFigureContainer.zIndex = metadata.zIndex;
+ }
+ }
+ // 挂载
+ this.figureContainer.addChild(thisFigureContainer);
+ const figureUuid = uuid();
+ this.figureObjects.push({
+ uuid: figureUuid,
+ key: key,
+ pixiContainer: thisFigureContainer,
+ sourceUrl: url,
+ sourceType: 'live2d',
+ sourceExt: this.getExtName(url),
+ });
+
+ // 完成图片加载后执行的函数
+ const setup = () => {
+ const spineResource: any = this.assetLoader.resources?.[spineId];
+ // TODO:找一个更好的解法,现在的解法是无论是否复用原来的资源,都设置一个延时以让动画工作正常!
+ setTimeout(() => {
+ if (spineResource && this.getStageObjByUuid(figureUuid)) {
+ const figureSpine = new Spine(spineResource.spineData);
+ const transY = spineResource?.spineData?.y ?? 0;
+ /**
+ * 重设大小
+ */
+ const originalWidth = figureSpine.width;
+ const originalHeight = figureSpine.height;
+ const scaleX = this.stageWidth / originalWidth;
+ const scaleY = this.stageHeight / originalHeight;
+ // 我也不知道为什么啊啊啊啊
+ figureSpine.y = -(scaleY * transY) / 2;
+ figureSpine.state.setAnimation(0, '07', true);
+ const targetScale = Math.min(scaleX, scaleY);
+ const figureSprite = new PIXI.Sprite();
+ figureSprite.addChild(figureSpine);
+ figureSprite.scale.x = targetScale;
+ figureSprite.scale.y = targetScale;
+ figureSprite.anchor.set(0.5);
+ figureSprite.position.y = this.stageHeight / 2;
+ const targetWidth = originalWidth * targetScale;
+ const targetHeight = originalHeight * targetScale;
+ thisFigureContainer.setBaseY(this.stageHeight / 2);
+ if (targetHeight < this.stageHeight) {
+ thisFigureContainer.setBaseY(this.stageHeight / 2 + this.stageHeight - targetHeight / 2);
+ }
+ if (presetPosition === 'center') {
+ thisFigureContainer.setBaseX(this.stageWidth / 2);
+ }
+ if (presetPosition === 'left') {
+ thisFigureContainer.setBaseX(targetWidth / 2);
+ }
+ if (presetPosition === 'right') {
+ thisFigureContainer.setBaseX(this.stageWidth - targetWidth / 2);
+ }
+ thisFigureContainer.pivot.set(0, this.stageHeight / 2);
+ thisFigureContainer.addChild(figureSprite);
+ }
+ }, 0);
+ };
+
+ /**
+ * 加载器部分
+ */
+ this.cacheGC();
+ if (!loader.resources?.[url]) {
+ this.loadAsset(url, setup, spineId);
+ } else {
+ // 复用
+ setup();
+ }
+ }
+
+ /**
+ * Live2d立绘,如果要使用 Live2D,取消这里的注释
+ * @param jsonPath
+ */
+ // eslint-disable-next-line max-params
+ // public addLive2dFigure(key: string, jsonPath: string, pos: string, motion: string, expression: string) {
+ // let stageWidth = this.stageWidth;
+ // let stageHeight = this.stageHeight;
+ // logger.debug('Using motion:', motion);
+ //
+ // figureCash.push(jsonPath);
+ //
+ // const loader = this.assetLoader;
+ // // 准备用于存放这个立绘的 Container
+ // const thisFigureContainer = new WebGALPixiContainer();
+ //
+ // // 是否有相同 key 的立绘
+ // const setFigIndex = this.figureObjects.findIndex((e) => e.key === key);
+ // const isFigSet = setFigIndex >= 0;
+ //
+ // // 已经有一个这个 key 的立绘存在了
+ // if (isFigSet) {
+ // this.removeStageObjectByKey(key);
+ // }
+ //
+ // const metadata = this.getFigureMetadataByKey(key);
+ // if (metadata) {
+ // if (metadata.zIndex) {
+ // thisFigureContainer.zIndex = metadata.zIndex;
+ // }
+ // }
+ // // 挂载
+ // this.figureContainer.addChild(thisFigureContainer);
+ // this.figureObjects.push({
+ // uuid: uuid(),
+ // key: key,
+ // pixiContainer: thisFigureContainer,
+ // sourceUrl: jsonPath,
+ // sourceType: 'live2d',
+ // sourceExt: 'json',
+ // });
+ // // eslint-disable-next-line @typescript-eslint/no-this-alias
+ // const instance = this;
+ //
+ // const setup = () => {
+ // if (thisFigureContainer) {
+ // (async function () {
+ // let overrideBounds: [number, number, number, number] = [0, 0, 0, 0];
+ // const mot = webgalStore.getState().stage.live2dMotion.find((e) => e.target === key);
+ // if (mot?.overrideBounds) {
+ // overrideBounds = mot.overrideBounds;
+ // }
+ // console.log(overrideBounds);
+ // const models = await Promise.all([
+ // Live2DModel.from(jsonPath, {
+ // autoInteract: false,
+ // overWriteBounds: {
+ // x0: overrideBounds[0],
+ // y0: overrideBounds[1],
+ // x1: overrideBounds[2],
+ // y1: overrideBounds[3],
+ // },
+ // }),
+ // ]);
+ //
+ // models.forEach((model) => {
+ // const scaleX = stageWidth / model.width;
+ // const scaleY = stageHeight / model.height;
+ // const targetScale = Math.min(scaleX, scaleY) * 1.5;
+ // const targetWidth = model.width * targetScale;
+ // // const targetHeight = model.height * targetScale;
+ //
+ // model.scale.set(targetScale);
+ // model.anchor.set(0.5);
+ // model.position.x = stageWidth / 2;
+ // model.position.y = stageHeight / 1.2;
+ //
+ // if (pos === 'left') {
+ // model.position.x = targetWidth / 2;
+ // }
+ // if (pos === 'right') {
+ // model.position.x = stageWidth - targetWidth / 2;
+ // }
+ //
+ // let motionToSet = motion;
+ // let animation_index = 0;
+ // let priority_number = 3;
+ // // var audio_link = voiceCash.pop();
+ //
+ // // model.motion(category_name, animation_index, priority_number,location.href + audio_link);
+ // /**
+ // * 检查 Motion 和 Expression
+ // */
+ // const motionFromState = webgalStore.getState().stage.live2dMotion.find((e) => e.target === key);
+ // const expressionFromState = webgalStore.getState().stage.live2dExpression.find((e) => e.target === key);
+ // if (motionFromState) {
+ // motionToSet = motionFromState.motion;
+ // }
+ // instance.updateL2dMotionByKey(key, motionToSet);
+ // model.motion(motionToSet, animation_index, priority_number);
+ // let expressionToSet = expression;
+ // if (expressionFromState) {
+ // expressionToSet = expressionFromState.expression;
+ // }
+ // instance.updateL2dExpressionByKey(key, expressionToSet);
+ // model.expression(expressionToSet);
+ // // @ts-ignore
+ // if (model.internalModel.eyeBlink) {
+ // // @ts-ignore
+ // model.internalModel.eyeBlink.blinkInterval = 1000 * 60 * 60 * 24; // @ts-ignore
+ // model.internalModel.eyeBlink.nextBlinkTimeLeft = 1000 * 60 * 60 * 24;
+ // }
+ //
+ // // lip-sync is still a problem and you can not.
+ // SoundManager.volume = 0; // @ts-ignore
+ // if (model.internalModel.angleXParamIndex !== undefined) model.internalModel.angleXParamIndex = 999; // @ts-ignore
+ // if (model.internalModel.angleYParamIndex !== undefined) model.internalModel.angleYParamIndex = 999; // @ts-ignore
+ // if (model.internalModel.angleZParamIndex !== undefined) model.internalModel.angleZParamIndex = 999;
+ // thisFigureContainer.addChild(model);
+ // });
+ // })();
+ // }
+ // };
+ //
+ // /**
+ // * 加载器部分
+ // */
+ // const resourses = Object.keys(loader.resources);
+ // this.cacheGC();
+ // if (!resourses.includes(jsonPath)) {
+ // this.loadAsset(jsonPath, setup);
+ // } else {
+ // // 复用
+ // setup();
+ // }
+ // }
+
+ public changeModelMotionByKey(key: string, motion: string) {
+ // logger.debug(`Applying motion ${motion} to ${key}`);
+ const target = this.figureObjects.find((e) => e.key === key);
+ if (target?.sourceType !== 'live2d') return;
+ const figureRecordTarget = this.live2dFigureRecorder.find((e) => e.target === key);
+ if (target && figureRecordTarget?.motion !== motion) {
+ const container = target.pixiContainer;
+ const children = container.children;
+ for (const model of children) {
+ let category_name = motion;
+ let animation_index = 0;
+ let priority_number = 3; // @ts-ignore
+ const internalModel = model?.internalModel ?? undefined; // 安全访问
+ internalModel?.motionManager?.stopAllMotions?.();
+ // @ts-ignore
+ model.motion(category_name, animation_index, priority_number);
+ }
+ this.updateL2dMotionByKey(key, motion);
+ }
+ }
+
+ public changeModelExpressionByKey(key: string, expression: string) {
+ // logger.debug(`Applying expression ${expression} to ${key}`);
+ const target = this.figureObjects.find((e) => e.key === key);
+ if (target?.sourceType !== 'live2d') return;
+ const figureRecordTarget = this.live2dFigureRecorder.find((e) => e.target === key);
+ if (target && figureRecordTarget?.expression !== expression) {
+ const container = target.pixiContainer;
+ const children = container.children;
+ for (const model of children) {
+ // @ts-ignore
+ model.expression(expression);
+ }
+ this.updateL2dExpressionByKey(key, expression);
+ }
+ }
+
+ public setModelMouthY(key: string, y: number) {
+ function mapToZeroOne(value: number) {
+ return value < 50 ? 0 : (value - 50) / 50;
+ }
+
+ const paramY = mapToZeroOne(y);
+ const target = this.figureObjects.find((e) => e.key === key);
+ if (target && target.sourceType === 'live2d') {
+ const container = target.pixiContainer;
+ const children = container.children;
+ for (const model of children) {
+ // @ts-ignore
+ if (model?.internalModel) {
+ // @ts-ignore
+ if (model?.internalModel?.coreModel?.setParamFloat)
+ // @ts-ignore
+ model?.internalModel?.coreModel?.setParamFloat?.('PARAM_MOUTH_OPEN_Y', paramY);
+ // @ts-ignore
+ if (model?.internalModel?.coreModel?.setParameterValueById)
+ // @ts-ignore
+ model?.internalModel?.coreModel?.setParameterValueById('ParamMouthOpenY', paramY);
+ }
+ }
+ }
+ }
+
+ /**
+ * 根据 key 获取舞台上的对象
+ * @param key
+ */
+ public getStageObjByKey(key: string) {
+ return [...this.figureObjects, ...this.backgroundObjects].find((e) => e.key === key);
+ }
+
+ public getStageObjByUuid(objUuid: string) {
+ return [...this.figureObjects, ...this.backgroundObjects].find((e) => e.uuid === objUuid);
+ }
+
+ public getAllStageObj() {
+ return [...this.figureObjects, ...this.backgroundObjects];
+ }
+
+ /**
+ * 根据 key 删除舞台上的对象
+ * @param key
+ */
+ public removeStageObjectByKey(key: string) {
+ const indexFig = this.figureObjects.findIndex((e) => e.key === key);
+ const indexBg = this.backgroundObjects.findIndex((e) => e.key === key);
+ if (indexFig >= 0) {
+ const bgSprite = this.figureObjects[indexFig];
+ for (const element of bgSprite.pixiContainer.children) {
+ element.destroy();
+ }
+ bgSprite.pixiContainer.destroy();
+ this.figureContainer.removeChild(bgSprite.pixiContainer);
+ this.figureObjects.splice(indexFig, 1);
+ }
+ if (indexBg >= 0) {
+ const bgSprite = this.backgroundObjects[indexBg];
+ for (const element of bgSprite.pixiContainer.children) {
+ element.destroy();
+ }
+ bgSprite.pixiContainer.destroy();
+ this.backgroundContainer.removeChild(bgSprite.pixiContainer);
+ this.backgroundObjects.splice(indexBg, 1);
+ }
+ // /**
+ // * 删掉相关 Effects,因为已经移除了
+ // */
+ // const prevEffects = webgalStore.getState().stage.effects;
+ // const newEffects = __.cloneDeep(prevEffects);
+ // const index = newEffects.findIndex((e) => e.target === key);
+ // if (index >= 0) {
+ // newEffects.splice(index, 1);
+ // }
+ // updateCurrentEffects(newEffects);
+ }
+
+ public cacheGC() {
+ PIXI.utils.clearTextureCache();
+ }
+
+ private updateL2dMotionByKey(target: string, motion: string) {
+ const figureTargetIndex = this.live2dFigureRecorder.findIndex((e) => e.target === target);
+ if (figureTargetIndex >= 0) {
+ this.live2dFigureRecorder[figureTargetIndex].motion = motion;
+ } else {
+ this.live2dFigureRecorder.push({ target, motion, expression: '' });
+ }
+ }
+
+ private updateL2dExpressionByKey(target: string, expression: string) {
+ const figureTargetIndex = this.live2dFigureRecorder.findIndex((e) => e.target === target);
+ if (figureTargetIndex >= 0) {
+ this.live2dFigureRecorder[figureTargetIndex].expression = expression;
+ } else {
+ this.live2dFigureRecorder.push({ target, motion: '', expression });
+ }
+ }
+
+ private loadAsset(url: string, callback: () => void, name?: string) {
+ /**
+ * Loader 复用疑似有问题,转而采用先前的单独方式
+ */
+ this.loadQueue.unshift({ url, callback, name });
+ /**
+ * 尝试启动加载
+ */
+ this.callLoader();
+ }
+
+ private callLoader() {
+ if (!this.assetLoader.loading) {
+ const front = this.loadQueue.shift();
+ if (front) {
+ try {
+ if (this.assetLoader.resources[front.url]) {
+ front.callback();
+ this.callLoader();
+ } else {
+ if (front.name) {
+ this.assetLoader.add(front.name, front.url).load(() => {
+ front.callback();
+ this.callLoader();
+ });
+ } else {
+ this.assetLoader.add(front.url).load(() => {
+ front.callback();
+ this.callLoader();
+ });
+ }
+ }
+ } catch (error) {
+ logger.fatal('PIXI Loader 故障', error);
+ front.callback();
+ // this.assetLoader.reset(); // 暂时先不用重置
+ this.callLoader();
+ }
+ }
+ }
+ }
+
+ private updateFps() {
+ getScreenFps?.(120).then((fps) => {
+ this.frameDuration = 1000 / (fps as number);
+ // logger.info('当前帧率', fps);
+ });
+ }
+
+ private lockStageObject(targetName: string) {
+ this.lockTransformTarget.push(targetName);
+ }
+
+ private unlockStageObject(targetName: string) {
+ const index = this.lockTransformTarget.findIndex((name) => name === targetName);
+ if (index >= 0) this.lockTransformTarget.splice(index, 1);
+ }
+
+ private getExtName(url: string) {
+ return url.split('.').pop() ?? 'png';
+ }
+
+ private getFigureMetadataByKey(key: string): IFigureMetadata | undefined {
+ console.log(key, webgalStore.getState().stage.figureMetaData);
+ return webgalStore.getState().stage.figureMetaData[key];
+ }
+}
+
+function updateCurrentBacklogEffects(newEffects: IEffect[]) {
+ /**
+ * 更新当前 backlog 条目的 effects 记录
+ */
+ setTimeout(() => {
+ WebGAL.backlogManager.editLastBacklogItemEffect(cloneDeep(newEffects));
+ }, 50);
+
+ webgalStore.dispatch(setStage({ key: 'effects', value: newEffects }));
+}
+
+/**
+ * @param {number} targetCount 不小于1的整数,表示经过targetCount帧之后返回结果
+ * @return {Promise}
+ */
+const getScreenFps = (() => {
+ // 先做一下兼容性处理
+ const nextFrame = [
+ window.requestAnimationFrame,
+ // @ts-ignore
+ window.webkitRequestAnimationFrame,
+ // @ts-ignore
+ window.mozRequestAnimationFrame,
+ ].find((fn) => fn);
+ if (!nextFrame) {
+ console.error('requestAnimationFrame is not supported!');
+ return;
+ }
+ return (targetCount = 60) => {
+ // 判断参数是否合规
+ if (targetCount < 1) throw new Error('targetCount cannot be less than 1.');
+ const beginDate = Date.now();
+ let count = 0;
+ return new Promise((resolve) => {
+ (function log() {
+ nextFrame(() => {
+ if (++count >= targetCount) {
+ const diffDate = Date.now() - beginDate;
+ const fps = (count / diffDate) * 1000;
+ return resolve(fps);
+ }
+ log();
+ });
+ })();
+ });
+ };
+})();
diff --git a/packages/webgal/src/Core/controller/stage/pixi/WebGALPixiContainer.ts b/packages/webgal/src/Core/controller/stage/pixi/WebGALPixiContainer.ts
new file mode 100644
index 000000000..5dc9047c2
--- /dev/null
+++ b/packages/webgal/src/Core/controller/stage/pixi/WebGALPixiContainer.ts
@@ -0,0 +1,331 @@
+import { OldFilmFilter } from '@pixi/filter-old-film';
+import { DotFilter } from '@pixi/filter-dot';
+import { ReflectionFilter } from '@pixi/filter-reflection';
+import { GlitchFilter } from '@pixi/filter-glitch';
+import { RGBSplitFilter } from '@pixi/filter-rgb-split';
+import { GodrayFilter } from '@pixi/filter-godray';
+import * as PIXI from 'pixi.js';
+import {
+ getOrCreateShockwaveFilterImpl,
+ getShockwaveFilter,
+ setShockwaveFilter,
+} from '@/Core/controller/stage/pixi/filters/ShockwaveFilter';
+import {
+ getOrCreateRadiusAlphaFilterImpl,
+ getRadiusAlphaFilter,
+ setRadiusAlphaFilter,
+} from '@/Core/controller/stage/pixi/shaders/RadiusAlphaFilter';
+
+export class WebGALPixiContainer extends PIXI.Container {
+ public containerFilters = new Map();
+ private baseX = 0;
+ private baseY = 0;
+
+ private alphaFilter = new PIXI.filters.AlphaFilter(1);
+
+ public constructor() {
+ super();
+ this.addFilter(this.alphaFilter);
+ }
+
+ public get alphaFilterVal() {
+ return this.alphaFilter.alpha;
+ }
+
+ public set alphaFilterVal(value: number) {
+ this.alphaFilter.alpha = value;
+ }
+
+ public addFilter(filter: PIXI.Filter) {
+ if (this.filters) {
+ this.filters.push(filter);
+ } else {
+ this.filters = [filter];
+ }
+ }
+
+ public removeFilter(name: string) {
+ const filter = this.containerFilters.get(name);
+ if (filter) {
+ const index = (this?.filters ?? []).findIndex((e) => e === filter);
+ if (this.filters) {
+ this.filters.splice(index, 1);
+ this.containerFilters.delete(name);
+ }
+ }
+ }
+
+ public get blur(): number {
+ // @ts-ignore
+ return this.getOrCreateBlurFilter().blur as number;
+ }
+
+ public set blur(value: number) {
+ // @ts-ignore
+ this.getOrCreateBlurFilter().blur = value;
+ }
+
+ public get x() {
+ const rX = super.position?.x ?? 0;
+ return rX - this.baseX;
+ }
+
+ public set x(value) {
+ if (!super.position) {
+ return;
+ }
+ super.position.x = value + this.baseX;
+ }
+
+ public get y() {
+ const rY = super.position?.y ?? 0;
+ return rY - this.baseY;
+ }
+
+ public set y(value) {
+ if (!super.position) {
+ return;
+ }
+ super.position.y = value + this.baseY;
+ }
+
+ public setBaseX(x: number) {
+ const originalX = this.x;
+ this.baseX = x;
+ this.x = originalX;
+ }
+
+ public setBaseY(y: number) {
+ const originalY = this.y;
+ this.baseY = y;
+ this.y = originalY;
+ }
+
+ public getOrCreateBlurFilter() {
+ const blurFilterFromMap = this.containerFilters.get('blur');
+ if (blurFilterFromMap) {
+ return blurFilterFromMap;
+ } else {
+ const blurFilter = new PIXI.filters.BlurFilter();
+ // 默认的 blur 是8,覆盖掉
+ blurFilter.blur = 0;
+ this.addFilter(blurFilter);
+ this.containerFilters.set('blur', blurFilter);
+ return blurFilter;
+ }
+ }
+
+ /**
+ * old film filter
+ * @public
+ */
+ public getOrCreateOldFilmFilter(createMode = true) {
+ const blurFilterFromMap = this.containerFilters.get('oldFilm');
+ if (blurFilterFromMap) {
+ return blurFilterFromMap;
+ } else {
+ if (createMode) {
+ const oldFilm = new OldFilmFilter();
+ this.addFilter(oldFilm);
+ this.containerFilters.set('oldFilm', oldFilm);
+ return oldFilm;
+ } else return null;
+ }
+ }
+ public get oldFilm(): number {
+ if (this.getOrCreateOldFilmFilter(false)) return 1;
+ return 0;
+ }
+
+ public set oldFilm(value: number) {
+ /**
+ * 如果是0,就移除这个滤镜
+ */
+ if (value === 0) {
+ this.removeFilter('oldFilm');
+ } else this.getOrCreateOldFilmFilter();
+ }
+
+ /**
+ * dot film filter
+ * @public
+ */
+ public getOrCreateDotFilter(createMode = true) {
+ const blurFilterFromMap = this.containerFilters.get('dotFilm');
+ if (blurFilterFromMap) {
+ return blurFilterFromMap;
+ } else {
+ if (createMode) {
+ const dotFilm = new DotFilter();
+ this.addFilter(dotFilm);
+ this.containerFilters.set('dotFilm', dotFilm);
+ return dotFilm;
+ } else return null;
+ }
+ }
+ public get dotFilm(): number {
+ if (this.getOrCreateDotFilter(false)) return 1;
+ return 0;
+ }
+
+ public set dotFilm(value: number) {
+ /**
+ * 如果是0,就移除这个滤镜
+ */
+ if (value === 0) {
+ this.removeFilter('dotFilm');
+ } else this.getOrCreateDotFilter();
+ }
+
+ /**
+ * reflection film filter
+ * @public
+ */
+ public getOrCreateReflectionFilter(createMode = true) {
+ const blurFilterFromMap = this.containerFilters.get('reflectionFilm');
+ if (blurFilterFromMap) {
+ return blurFilterFromMap;
+ } else {
+ if (createMode) {
+ const reflectionFilm = new ReflectionFilter();
+ this.addFilter(reflectionFilm);
+ this.containerFilters.set('reflectionFilm', reflectionFilm);
+ return reflectionFilm;
+ } else return null;
+ }
+ }
+ public get reflectionFilm(): number {
+ if (this.getOrCreateReflectionFilter(false)) return 1;
+ return 0;
+ }
+
+ public set reflectionFilm(value: number) {
+ /**
+ * 如果是0,就移除这个滤镜
+ */
+ if (value === 0) {
+ this.removeFilter('reflectionFilm');
+ } else this.getOrCreateReflectionFilter();
+ }
+
+ /**
+ * glitchFilter film filter
+ * @public
+ */
+ public getOrCreateGlitchFilter(createMode = true) {
+ const blurFilterFromMap = this.containerFilters.get('glitchFilm');
+ if (blurFilterFromMap) {
+ return blurFilterFromMap;
+ } else {
+ if (createMode) {
+ const glitchFilm = new GlitchFilter();
+ this.addFilter(glitchFilm);
+ this.containerFilters.set('glitchFilm', glitchFilm);
+ return glitchFilm;
+ } else return null;
+ }
+ }
+ public get glitchFilm(): number {
+ if (this.getOrCreateGlitchFilter(false)) return 1;
+ return 0;
+ }
+
+ public set glitchFilm(value: number) {
+ /**
+ * 如果是0,就移除这个滤镜
+ */
+ if (value === 0) {
+ this.removeFilter('glitchFilm');
+ } else this.getOrCreateGlitchFilter();
+ }
+
+ /**
+ * rgbSplitFilter film filter
+ * @public
+ */
+ public getOrCreateRGBSplitFilter(createMode = true) {
+ const blurFilterFromMap = this.containerFilters.get('rgbFilm');
+ if (blurFilterFromMap) {
+ return blurFilterFromMap;
+ } else {
+ if (createMode) {
+ const rgbFilm = new RGBSplitFilter();
+ this.addFilter(rgbFilm);
+ this.containerFilters.set('rgbFilm', rgbFilm);
+ return rgbFilm;
+ }
+ }
+ }
+ public get rgbFilm(): number {
+ if (this.getOrCreateRGBSplitFilter(false)) return 1;
+ return 0;
+ }
+
+ public set rgbFilm(value: number) {
+ /**
+ * 如果是0,就移除这个滤镜
+ */
+ if (value === 0) {
+ this.removeFilter('rgbFilm');
+ } else this.getOrCreateRGBSplitFilter();
+ }
+
+ /**
+ * godrayFilter film filter
+ * @public
+ */
+ public getOrCreateGodrayFilter(createMode = true) {
+ const blurFilterFromMap = this.containerFilters.get('godrayFilm');
+ if (blurFilterFromMap) {
+ return blurFilterFromMap;
+ } else {
+ if (createMode) {
+ const godrayFilm = new GodrayFilter();
+ this.addFilter(godrayFilm);
+ this.containerFilters.set('godrayFilm', godrayFilm);
+ return godrayFilm;
+ }
+ }
+ }
+ public get godrayFilm(): number {
+ if (this.getOrCreateGodrayFilter(false)) return 1;
+ return 0;
+ }
+
+ public set godrayFilm(value: number) {
+ /**
+ * 如果是0,就移除这个滤镜
+ */
+ if (value === 0) {
+ this.removeFilter('godrayFilm');
+ } else this.getOrCreateGodrayFilter();
+ }
+
+ /**
+ * ShockwaveFilter
+ */
+
+ public getOrCreateShockwaveFilter(createMode = true) {
+ return getOrCreateShockwaveFilterImpl(this, createMode);
+ }
+ public get shockwaveFilter(): number {
+ return getShockwaveFilter(this);
+ }
+ public set shockwaveFilter(value: number) {
+ setShockwaveFilter(this, value);
+ }
+
+ /**
+ * RadiusAlphaFilter
+ */
+
+ public getOrCreateRadiusAlphaFilter(createMode = true) {
+ return getOrCreateRadiusAlphaFilterImpl(this, createMode);
+ }
+ public get radiusAlphaFilter(): number {
+ return getRadiusAlphaFilter(this);
+ }
+ public set radiusAlphaFilter(value: number) {
+ setRadiusAlphaFilter(this, value);
+ }
+}
diff --git a/packages/webgal/src/Core/controller/stage/pixi/animations/generateTransformAnimationObj.ts b/packages/webgal/src/Core/controller/stage/pixi/animations/generateTransformAnimationObj.ts
new file mode 100644
index 000000000..2c0396415
--- /dev/null
+++ b/packages/webgal/src/Core/controller/stage/pixi/animations/generateTransformAnimationObj.ts
@@ -0,0 +1,31 @@
+import { ITransform } from '@/store/stageInterface';
+import { webgalStore } from '@/store/store';
+
+type AnimationFrame = ITransform & { duration: number };
+type AnimationObj = Array;
+
+export function generateTransformAnimationObj(
+ target: string,
+ applyFrame: AnimationFrame,
+ duration: number | string | boolean | null,
+): AnimationObj {
+ let animationObj;
+ // 获取那个 target 的当前变换
+ const transformState = webgalStore.getState().stage.effects;
+ const targetEffect = transformState.find((effect) => effect.target === target);
+ applyFrame.duration = 500;
+ if (duration && typeof duration === 'number') {
+ applyFrame.duration = duration;
+ }
+ animationObj = [applyFrame];
+ // 找到 effect
+ if (targetEffect) {
+ const effectWithDuration = { ...targetEffect!.transform!, duration: 0 };
+ animationObj.unshift(effectWithDuration);
+ } else {
+ // 应用默认effect,也就是最终的 effect 的 alpha = 0 版本
+ const effectWithDuration = { ...applyFrame, alpha: 0, duration: 0 };
+ animationObj.unshift(effectWithDuration);
+ }
+ return animationObj;
+}
diff --git a/packages/webgal/src/Core/controller/stage/pixi/animations/index.ts b/packages/webgal/src/Core/controller/stage/pixi/animations/index.ts
new file mode 100644
index 000000000..94998d7b0
--- /dev/null
+++ b/packages/webgal/src/Core/controller/stage/pixi/animations/index.ts
@@ -0,0 +1,9 @@
+import { generateUniversalSoftInAnimationObj } from '@/Core/controller/stage/pixi/animations/universalSoftIn';
+import { generateUniversalSoftOffAnimationObj } from '@/Core/controller/stage/pixi/animations/universalSoftOff';
+import { generateTestblurAnimationObj } from '@/Core/controller/stage/pixi/animations/testblur';
+
+export const webgalAnimations: Array<{ name: string; animationGenerateFunc: Function }> = [
+ { name: 'universalSoftIn', animationGenerateFunc: generateUniversalSoftInAnimationObj },
+ { name: 'universalSoftOff', animationGenerateFunc: generateUniversalSoftOffAnimationObj },
+ { name: 'testblur', animationGenerateFunc: generateTestblurAnimationObj },
+];
diff --git a/packages/webgal/src/Core/controller/stage/pixi/animations/template.ts b/packages/webgal/src/Core/controller/stage/pixi/animations/template.ts
new file mode 100644
index 000000000..74b017a10
--- /dev/null
+++ b/packages/webgal/src/Core/controller/stage/pixi/animations/template.ts
@@ -0,0 +1,49 @@
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 动画创建模板
+ * @param targetKey 作用目标
+ * @param duration 持续时间
+ */
+export function generateTemplateAnimationObj(targetKey: string, duration: number) {
+ const target = WebGAL.gameplay.pixiStage!.getStageObjByKey(targetKey);
+
+ // 先设置一个通用的初态
+
+ // TODO:通用初态设置
+ /**
+ * 在此书写为动画设置初态的操作
+ */
+ function setStartState() {}
+
+ // TODO:通用终态设置
+ /**
+ * 在此书写为动画设置终态的操作
+ */
+ function setEndState() {}
+
+ /**
+ * 在此书写动画每一帧执行的函数
+ * @param delta
+ */
+ function tickerFunc(delta: number) {
+ if (target) {
+ // 要操控的精灵
+ const sprite = target.pixiContainer;
+ // 每一帧的时间
+ const baseDuration = WebGAL.gameplay.pixiStage!.frameDuration;
+
+ /**
+ * 在下面书写具体的动画
+ */
+
+ // 具体的操作......
+ }
+ }
+
+ return {
+ setStartState,
+ setEndState,
+ tickerFunc,
+ };
+}
diff --git a/packages/webgal/src/Core/controller/stage/pixi/animations/testblur.ts b/packages/webgal/src/Core/controller/stage/pixi/animations/testblur.ts
new file mode 100644
index 000000000..2bb9a98e9
--- /dev/null
+++ b/packages/webgal/src/Core/controller/stage/pixi/animations/testblur.ts
@@ -0,0 +1,59 @@
+import { WebGAL } from '@/Core/WebGAL';
+
+export function generateTestblurAnimationObj(targetKey: string, duration: number) {
+ const target = WebGAL.gameplay.pixiStage!.getStageObjByKey(targetKey);
+
+ // 先设置一个通用的初态
+
+ // TODO:通用初态设置
+ /**
+ * 在此书写为动画设置初态的操作
+ */
+ function setStartState() {
+ if (target) {
+ target.pixiContainer.alpha = 0;
+ // @ts-ignore
+ target.pixiContainer.blur = 0;
+ }
+ }
+
+ // TODO:通用终态设置
+ /**
+ * 在此书写为动画设置终态的操作
+ */
+ function setEndState() {
+ if (target) {
+ target.pixiContainer.alpha = 1;
+ // @ts-ignore
+ target.pixiContainer.blur = 5;
+ }
+ }
+
+ /**
+ * 在此书写动画每一帧执行的函数
+ * @param delta
+ */
+ function tickerFunc(delta: number) {
+ if (target) {
+ const container = target.pixiContainer;
+ const baseDuration = WebGAL.gameplay.pixiStage!.frameDuration;
+ const currentAddOplityDelta = (duration / baseDuration) * delta;
+ const increasement = 1 / currentAddOplityDelta;
+ const decreasement = 5 / currentAddOplityDelta;
+ if (container.alpha < 1) {
+ container.alpha += increasement;
+ }
+ // @ts-ignore
+ if (container.blur < 5) {
+ // @ts-ignore
+ container.blur += decreasement;
+ }
+ }
+ }
+
+ return {
+ setStartState,
+ setEndState,
+ tickerFunc,
+ };
+}
diff --git a/packages/webgal/src/Core/controller/stage/pixi/animations/timeline.ts b/packages/webgal/src/Core/controller/stage/pixi/animations/timeline.ts
new file mode 100644
index 000000000..8d94cd474
--- /dev/null
+++ b/packages/webgal/src/Core/controller/stage/pixi/animations/timeline.ts
@@ -0,0 +1,132 @@
+import { ITransform } from '@/store/stageInterface';
+import { animate } from 'popmotion';
+import { WebGAL } from '@/Core/WebGAL';
+import { webgalStore } from '@/store/store';
+import { stageActions } from '@/store/stageReducer';
+import omitBy from 'lodash/omitBy';
+import isUndefined from 'lodash/isUndefined';
+
+/**
+ * 动画创建模板
+ * @param timeline
+ * @param targetKey 作用目标
+ * @param duration 持续时间
+ */
+export function generateTimelineObj(
+ timeline: Array,
+ targetKey: string,
+ duration: number,
+) {
+ for (const segment of timeline) {
+ // 处理 alphaL
+ // @ts-ignore
+ segment['alphaFilterVal'] = segment.alpha;
+ segment.alpha = 1;
+ }
+ const target = WebGAL.gameplay.pixiStage!.getStageObjByKey(targetKey);
+ let currentDelay = 0;
+ const values = [];
+ const times: number[] = [];
+ for (const segment of timeline) {
+ const segmentDuration = segment.duration;
+ currentDelay += segmentDuration;
+ const { position, scale, ...segmentValues } = segment;
+ // 不能用 scale,因为 popmotion 不能用嵌套
+ values.push({ x: position.x, y: position.y, scaleX: scale.x, scaleY: scale.y, ...segmentValues });
+ if (duration !== 0) {
+ times.push(currentDelay / duration);
+ } else times.push(0);
+ }
+ const container = target?.pixiContainer;
+ let animateInstance: ReturnType | null = null;
+ // 只有有 duration 的时候才有动画
+ if (duration > 0) {
+ animateInstance = animate({
+ to: values,
+ offset: times,
+ duration,
+ onUpdate: (updateValue) => {
+ if (container) {
+ const { scaleX, scaleY, ...val } = updateValue;
+ Object.assign(container, omitBy(val, isUndefined));
+ // 因为 popmotion 不能用嵌套,scale 要手动设置
+ if (!isUndefined(scaleX)) container.scale.x = scaleX;
+ if (!isUndefined(scaleY)) container.scale.y = scaleY;
+ }
+ },
+ });
+ }
+
+ const { duration: sliceDuration, ...endState } = getEndStateEffect();
+ webgalStore.dispatch(stageActions.updateEffect({ target: targetKey, transform: endState }));
+
+ /**
+ * 在此书写为动画设置初态的操作
+ */
+ function setStartState() {
+ if (target?.pixiContainer) {
+ // 不能赋值到 position,因为 x 和 y 被 WebGALPixiContainer 代理,而 position 属性没有代理
+ const { position, scale, ...state } = getStartStateEffect();
+ const assignValue = omitBy({ x: position.x, y: position.y, ...state }, isUndefined);
+ Object.assign(target?.pixiContainer, assignValue);
+ if (target?.pixiContainer) {
+ if (!isUndefined(scale.x)) {
+ target.pixiContainer.scale.x = scale.x;
+ }
+ if (!isUndefined(scale?.y)) {
+ target.pixiContainer.scale.y = scale.y;
+ }
+ }
+ }
+ }
+
+ /**
+ * 在此书写为动画设置终态的操作
+ */
+ function setEndState() {
+ if (animateInstance) animateInstance.stop();
+ animateInstance = null;
+ if (target?.pixiContainer) {
+ // 不能赋值到 position,因为 x 和 y 被 WebGALPixiContainer 代理,而 position 属性没有代理
+ // 不能赋值到 position,因为 x 和 y 被 WebGALPixiContainer 代理,而 position 属性没有代理
+ const { position, scale, ...state } = getEndStateEffect();
+ const assignValue = omitBy({ x: position.x, y: position.y, ...state }, isUndefined);
+ Object.assign(target?.pixiContainer, assignValue);
+ if (target?.pixiContainer) {
+ if (!isUndefined(scale.x)) {
+ target.pixiContainer.scale.x = scale.x;
+ }
+ if (!isUndefined(scale?.y)) {
+ target.pixiContainer.scale.y = scale.y;
+ }
+ }
+ }
+ }
+
+ /**
+ * 在此书写动画每一帧执行的函数
+ * @param delta
+ */
+ function tickerFunc(delta: number) {}
+
+ function getStartStateEffect() {
+ return timeline[0];
+ }
+
+ function getEndStateEffect() {
+ return timeline[timeline.length - 1];
+ }
+
+ function getEndFilterEffect() {
+ const endSegment = timeline[timeline.length - 1];
+ const { alpha, rotation, blur, duration, scale, position, ...rest } = endSegment;
+ return rest;
+ }
+
+ return {
+ setStartState,
+ setEndState,
+ tickerFunc,
+ getEndFilterEffect,
+ };
+}
diff --git a/packages/webgal/src/Core/controller/stage/pixi/animations/universalSoftIn.ts b/packages/webgal/src/Core/controller/stage/pixi/animations/universalSoftIn.ts
new file mode 100644
index 000000000..1d7ebcffd
--- /dev/null
+++ b/packages/webgal/src/Core/controller/stage/pixi/animations/universalSoftIn.ts
@@ -0,0 +1,50 @@
+import { WebGAL } from '@/Core/WebGAL';
+
+export function generateUniversalSoftInAnimationObj(targetKey: string, duration: number) {
+ const target = WebGAL.gameplay.pixiStage!.getStageObjByKey(targetKey);
+
+ // 先设置一个通用的初态
+
+ // TODO:通用初态设置
+ /**
+ * 在此书写为动画设置初态的操作
+ */
+ function setStartState() {
+ if (target) {
+ target.pixiContainer.alphaFilterVal = 0;
+ }
+ }
+
+ // TODO:通用终态设置
+ /**
+ * 在此书写为动画设置终态的操作
+ */
+ function setEndState() {
+ if (target) {
+ target.pixiContainer.alphaFilterVal = 1;
+ }
+ }
+
+ /**
+ * 在此书写动画每一帧执行的函数
+ * @param delta
+ */
+ function tickerFunc(delta: number) {
+ if (target) {
+ const sprite = target.pixiContainer;
+ const baseDuration = WebGAL.gameplay.pixiStage!.frameDuration;
+ const currentAddOplityDelta = (duration / baseDuration) * delta;
+ const increasement = 1 / currentAddOplityDelta;
+ // const decreasement = 5 / currentAddOplityDelta;
+ if (sprite.alphaFilterVal < 1) {
+ sprite.alpha += increasement;
+ }
+ }
+ }
+
+ return {
+ setStartState,
+ setEndState,
+ tickerFunc,
+ };
+}
diff --git a/packages/webgal/src/Core/controller/stage/pixi/animations/universalSoftOff.ts b/packages/webgal/src/Core/controller/stage/pixi/animations/universalSoftOff.ts
new file mode 100644
index 000000000..0d6c8915d
--- /dev/null
+++ b/packages/webgal/src/Core/controller/stage/pixi/animations/universalSoftOff.ts
@@ -0,0 +1,41 @@
+import { WebGAL } from '@/Core/WebGAL';
+
+export function generateUniversalSoftOffAnimationObj(targetKey: string, duration: number) {
+ const target = WebGAL.gameplay.pixiStage!.getStageObjByKey(targetKey);
+
+ // 先设置一个通用的初态
+
+ /**
+ * 在此书写为动画设置初态的操作
+ */
+ function setStartState() {}
+
+ /**
+ * 在此书写为动画设置终态的操作
+ */
+ function setEndState() {
+ if (target) target.pixiContainer.alphaFilterVal = 0;
+ }
+
+ /**
+ * 在此书写动画每一帧执行的函数
+ * @param delta
+ */
+ function tickerFunc(delta: number) {
+ if (target) {
+ const targetContainer = target.pixiContainer;
+ const baseDuration = WebGAL.gameplay.pixiStage!.frameDuration;
+ const currentAddOplityDelta = (duration / baseDuration) * delta;
+ const decreasement = 1 / currentAddOplityDelta;
+ if (targetContainer.alphaFilterVal > 0) {
+ targetContainer.alphaFilterVal -= decreasement;
+ }
+ }
+ }
+
+ return {
+ setStartState,
+ setEndState,
+ tickerFunc,
+ };
+}
diff --git a/packages/webgal/src/Core/controller/stage/pixi/filters/ShockwaveFilter.ts b/packages/webgal/src/Core/controller/stage/pixi/filters/ShockwaveFilter.ts
new file mode 100644
index 000000000..1598901cc
--- /dev/null
+++ b/packages/webgal/src/Core/controller/stage/pixi/filters/ShockwaveFilter.ts
@@ -0,0 +1,39 @@
+import { WebGALPixiContainer } from '@/Core/controller/stage/pixi/WebGALPixiContainer';
+import { ShockwaveFilter } from 'pixi-filters';
+
+const FILTER_NAME = 'shockwaveFilter';
+
+export function getOrCreateShockwaveFilterImpl(container: WebGALPixiContainer, createMode: boolean) {
+ const shockwaveFilterFromMap = container.containerFilters.get(FILTER_NAME);
+ if (shockwaveFilterFromMap) {
+ return shockwaveFilterFromMap;
+ } else {
+ if (createMode) {
+ const shockwaveFilter = new ShockwaveFilter([1280, 720]);
+ shockwaveFilter.time = 0;
+ container.addFilter(shockwaveFilter);
+ container.containerFilters.set(FILTER_NAME, shockwaveFilter);
+ return shockwaveFilter;
+ }
+ }
+}
+
+export function getShockwaveFilter(container: WebGALPixiContainer) {
+ if (container.getOrCreateShockwaveFilter(false)) {
+ const shockwaveFilter = container.getOrCreateShockwaveFilter() as ShockwaveFilter;
+ return shockwaveFilter.time;
+ }
+ return 0;
+}
+
+export function setShockwaveFilter(container: WebGALPixiContainer, value: number) {
+ /**
+ * 如果是0,就移除这个滤镜
+ */
+ if (value === 0) {
+ container.removeFilter(FILTER_NAME);
+ } else {
+ const shockwaveFilter = container.getOrCreateShockwaveFilter() as ShockwaveFilter;
+ if (shockwaveFilter) shockwaveFilter.time = value;
+ }
+}
diff --git a/packages/webgal/src/Core/controller/stage/pixi/shaders/RadiusAlphaFilter.ts b/packages/webgal/src/Core/controller/stage/pixi/shaders/RadiusAlphaFilter.ts
new file mode 100644
index 000000000..5f3dab198
--- /dev/null
+++ b/packages/webgal/src/Core/controller/stage/pixi/shaders/RadiusAlphaFilter.ts
@@ -0,0 +1,93 @@
+import * as PIXI from 'pixi.js';
+import { WebGALPixiContainer } from '@/Core/controller/stage/pixi/WebGALPixiContainer';
+
+const INIT_RAD = 0;
+const FILTER_NAME = 'radiusAlphaFilter';
+
+class RadiusAlphaFilter extends PIXI.Filter {
+ public constructor(center: PIXI.Point, radius: number) {
+ const fragmentShader = `
+// 半径透明度的fragment shader
+precision mediump float;
+
+uniform sampler2D uSampler; // 输入纹理
+varying vec2 vTextureCoord; // 当前片元的纹理坐标
+uniform vec2 center; // 圆心坐标
+uniform float radius; // 圆的半径
+
+void main(void) {
+ vec4 color = texture2D(uSampler, vTextureCoord);
+
+ // 计算屏幕宽高比
+ float aspect = 16.0 / 9.0;
+
+ // 根据宽高比校正纹理坐标
+ vec2 aspectCorrectCoord = vTextureCoord;
+ aspectCorrectCoord.x *= aspect;
+
+ // 计算片元到圆心的距离
+ float dist = distance(aspectCorrectCoord, center * vec2(aspect, 1.0));
+
+ // 使用smoothstep函数计算alpha值,实现边缘羽化效果
+ float alpha = smoothstep(radius, radius + 0.05, dist);
+
+ // 输出最终颜色
+ gl_FragColor = color * (1.0 - alpha);
+}
+ `; // 填入上面的fragment shader代码
+ super(null as any, fragmentShader);
+ this.uniforms.center = [center.x, center.y];
+ this.uniforms.radius = radius;
+ }
+
+ public set center(value: PIXI.Point) {
+ this.uniforms.center = [value.x, value.y];
+ }
+
+ public get center(): PIXI.Point {
+ return new PIXI.Point(this.uniforms.center[0], this.uniforms.center[1]);
+ }
+
+ public set radius(value: number) {
+ this.uniforms.radius = value;
+ }
+
+ public get radius(): number {
+ return this.uniforms.radius;
+ }
+}
+
+export function getOrCreateRadiusAlphaFilterImpl(container: WebGALPixiContainer, createMode: boolean) {
+ const shockwaveFilterFromMap = container.containerFilters.get(FILTER_NAME);
+ if (shockwaveFilterFromMap) {
+ return shockwaveFilterFromMap;
+ } else {
+ if (createMode) {
+ const shockwaveFilter = new RadiusAlphaFilter(new PIXI.Point(0.5, 0.5), INIT_RAD);
+ shockwaveFilter.radius = INIT_RAD;
+ container.addFilter(shockwaveFilter);
+ container.containerFilters.set(FILTER_NAME, shockwaveFilter);
+ return shockwaveFilter;
+ }
+ }
+}
+
+export function getRadiusAlphaFilter(container: WebGALPixiContainer) {
+ if (container.getOrCreateShockwaveFilter(false)) {
+ const shockwaveFilter = container.getOrCreateRadiusAlphaFilter() as RadiusAlphaFilter;
+ return shockwaveFilter.radius;
+ }
+ return INIT_RAD;
+}
+
+export function setRadiusAlphaFilter(container: WebGALPixiContainer, value: number) {
+ /**
+ * 如果是0,就移除这个滤镜
+ */
+ if (value === 0) {
+ container.removeFilter(FILTER_NAME);
+ } else {
+ const shockwaveFilter = container.getOrCreateRadiusAlphaFilter() as RadiusAlphaFilter;
+ if (shockwaveFilter) shockwaveFilter.radius = value;
+ }
+}
diff --git a/packages/webgal/src/Core/controller/stage/playBgm.ts b/packages/webgal/src/Core/controller/stage/playBgm.ts
new file mode 100644
index 000000000..b6e76c2e8
--- /dev/null
+++ b/packages/webgal/src/Core/controller/stage/playBgm.ts
@@ -0,0 +1,48 @@
+import { webgalStore } from '@/store/store';
+import { setStage } from '@/store/stageReducer';
+import { logger } from '@/Core/util/logger';
+
+// /**
+// * 停止bgm
+// */
+// export const eraseBgm = () => {
+// logger.debug(`停止bgm`);
+// // 停止之前的bgm
+// let VocalControl: any = document.getElementById('currentBgm');
+// if (VocalControl !== null) {
+// VocalControl.currentTime = 0;
+// if (!VocalControl.paused) VocalControl.pause();
+// }
+// // 获得舞台状态并设置
+// webgalStore.dispatch(setStage({key: 'bgm', value: ''}));
+// };
+
+let emptyBgmTimeout: ReturnType;
+
+/**
+ * 播放bgm
+ * @param url bgm路径
+ * @param enter 淡入时间(单位毫秒)
+ * @param volume 背景音乐 音量调整(0 - 100)
+ */
+export function playBgm(url: string, enter = 0, volume = 100): void {
+ logger.debug('playing bgm' + url);
+ if (url === '') {
+ emptyBgmTimeout = setTimeout(() => {
+ // 淡入淡出效果结束后,将 bgm 置空
+ webgalStore.dispatch(setStage({ key: 'bgm', value: { src: '', enter: 0, volume: 100 } }));
+ }, enter);
+ const lastSrc = webgalStore.getState().stage.bgm.src;
+ webgalStore.dispatch(setStage({ key: 'bgm', value: { src: lastSrc, enter: -enter, volume: volume } }));
+ } else {
+ // 不要清除bgm了!
+ clearTimeout(emptyBgmTimeout);
+ webgalStore.dispatch(setStage({ key: 'bgm', value: { src: url, enter: enter, volume: volume } }));
+ }
+ setTimeout(() => {
+ const audioElement = document.getElementById('currentBgm') as HTMLAudioElement;
+ if (audioElement.src) {
+ audioElement?.play();
+ }
+ }, 0);
+}
diff --git a/packages/webgal/src/Core/controller/stage/resetStage.ts b/packages/webgal/src/Core/controller/stage/resetStage.ts
new file mode 100644
index 000000000..4bc087086
--- /dev/null
+++ b/packages/webgal/src/Core/controller/stage/resetStage.ts
@@ -0,0 +1,29 @@
+import { initState, resetStageState, setStage } from '@/store/stageReducer';
+import { webgalStore } from '@/store/store';
+import cloneDeep from 'lodash/cloneDeep';
+import { WebGAL } from '@/Core/WebGAL';
+
+export const resetStage = (resetBacklog: boolean, resetSceneAndVar = true) => {
+ /**
+ * 清空运行时
+ */
+ if (resetBacklog) {
+ WebGAL.backlogManager.makeBacklogEmpty();
+ }
+ // 清空sceneData,并重新获取
+ if (resetSceneAndVar) {
+ WebGAL.sceneManager.resetScene();
+ }
+
+ // 清空所有演出和timeOut
+ WebGAL.gameplay.performController.removeAllPerform();
+ WebGAL.gameplay.resetGamePlay();
+
+ // 清空舞台状态表
+ const initSceneDataCopy = cloneDeep(initState);
+ const currentVars = webgalStore.getState().stage.GameVar;
+ webgalStore.dispatch(resetStageState(initSceneDataCopy));
+ if (!resetSceneAndVar) {
+ webgalStore.dispatch(setStage({ key: 'GameVar', value: currentVars }));
+ }
+};
diff --git a/packages/webgal/src/Core/controller/stage/setVolume.ts b/packages/webgal/src/Core/controller/stage/setVolume.ts
new file mode 100644
index 000000000..14f0ec36f
--- /dev/null
+++ b/packages/webgal/src/Core/controller/stage/setVolume.ts
@@ -0,0 +1,21 @@
+import { logger } from '../../util/logger';
+import { webgalStore } from '@/store/store';
+
+/**
+ * 设置音量
+ */
+export const setVolume = () => {
+ const userDataState = webgalStore.getState().userData;
+ const mainVol = userDataState.optionData.volumeMain;
+ const vocalVol = mainVol * 0.01 * userDataState.optionData.vocalVolume * 0.01;
+ const bgmVol = mainVol * 0.01 * userDataState.optionData.bgmVolume * 0.01;
+ logger.debug(`设置背景音量:${bgmVol},语音音量:${vocalVol}`);
+ // const bgmElement: any = document.getElementById('currentBgm');
+ // if (bgmElement) {
+ // bgmElement.volume = bgmVol.toString();
+ // }
+ // const vocalElement: any = document.getElementById('currentVocal');
+ // if (vocalElement) {
+ // vocalElement.volume = vocalVol.toString();
+ // }
+};
diff --git a/packages/webgal/src/Core/controller/storage/fastSaveLoad.ts b/packages/webgal/src/Core/controller/storage/fastSaveLoad.ts
new file mode 100644
index 000000000..61c5975cb
--- /dev/null
+++ b/packages/webgal/src/Core/controller/storage/fastSaveLoad.ts
@@ -0,0 +1,62 @@
+import { webgalStore } from '@/store/store';
+import { getStorageAsync, setStorageAsync } from '@/Core/controller/storage/storageController';
+import { ISaveData } from '@/store/userDataInterface';
+import { loadGameFromStageData } from '@/Core/controller/storage/loadGame';
+import { generateCurrentStageData } from '@/Core/controller/storage/saveGame';
+import cloneDeep from 'lodash/cloneDeep';
+import { WebGAL } from '@/Core/WebGAL';
+import { saveActions } from '@/store/savesReducer';
+import { dumpFastSaveToStorage, getFastSaveFromStorage } from '@/Core/controller/storage/savesController';
+
+export let fastSaveGameKey = '';
+export let isFastSaveKey = '';
+let lock = true;
+
+export function initKey() {
+ lock = false;
+ fastSaveGameKey = `FastSaveKey-${WebGAL.gameName}-${WebGAL.gameKey}`;
+ isFastSaveKey = `FastSaveActive-${WebGAL.gameName}-${WebGAL.gameKey}`;
+}
+
+/**
+ * 用于紧急回避时的数据存储 & 快速保存
+ */
+export async function fastSaveGame() {
+ const saveData: ISaveData = generateCurrentStageData(-1, false);
+ const newSaveData = cloneDeep(saveData);
+ webgalStore.dispatch(saveActions.setFastSave(newSaveData));
+ await dumpFastSaveToStorage();
+}
+
+/**
+ * 判断是否有无存储紧急回避时的数据
+ */
+export async function hasFastSaveRecord() {
+ // return await localforage.getItem(isFastSaveKey);
+ await getStorageAsync();
+ return webgalStore.getState().saveData.quickSaveData !== null;
+}
+
+/**
+ * 加载紧急回避时的数据
+ */
+export async function loadFastSaveGame() {
+ // 获得存档文件
+ // const loadFile: ISaveData | null = await localforage.getItem(fastSaveGameKey);
+ await getFastSaveFromStorage();
+ const loadFile: ISaveData | null = webgalStore.getState().saveData.quickSaveData;
+ if (!loadFile) {
+ return;
+ }
+ loadGameFromStageData(loadFile);
+}
+
+/**
+ * 移除紧急回避的数据
+ */
+export async function removeFastSaveGameRecord() {
+ webgalStore.dispatch(saveActions.resetFastSave());
+ await setStorageAsync();
+ // await localforage.setItem(isFastSaveKey, false);
+ // await localforage.setItem(fastSaveGameKey, null);
+}
diff --git a/packages/webgal/src/Core/controller/storage/jumpFromBacklog.ts b/packages/webgal/src/Core/controller/storage/jumpFromBacklog.ts
new file mode 100644
index 000000000..2ebf4ce6b
--- /dev/null
+++ b/packages/webgal/src/Core/controller/storage/jumpFromBacklog.ts
@@ -0,0 +1,80 @@
+import { logger } from '../../util/logger';
+import { sceneFetcher } from '../scene/sceneFetcher';
+import { sceneParser } from '../../parser/sceneParser';
+import { IStageState } from '@/store/stageInterface';
+import { webgalStore } from '@/store/store';
+import { resetStageState, stageActions } from '@/store/stageReducer';
+import { setVisibility } from '@/store/GUIReducer';
+import { runScript } from '@/Core/controller/gamePlay/runScript';
+import { stopAllPerform } from '@/Core/controller/gamePlay/stopAllPerform';
+import cloneDeep from 'lodash/cloneDeep';
+import uniqWith from 'lodash/uniqWith';
+import { scenePrefetcher } from '@/Core/util/prefetcher/scenePrefetcher';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 恢复演出
+ */
+export const restorePerform = () => {
+ const stageState = webgalStore.getState().stage;
+ const performToRestore = cloneDeep(stageState.PerformList);
+ // 清除状态表中演出序列
+ webgalStore.dispatch(stageActions.removeAllPerform());
+ performToRestore.forEach((e) => {
+ runScript(e.script);
+ });
+};
+
+/**
+ * 从 backlog 跳转至一个先前的状态
+ * @param index
+ * @param refetchScene
+ */
+export const jumpFromBacklog = (index: number, refetchScene = true) => {
+ const dispatch = webgalStore.dispatch;
+ // 获得存档文件
+ const backlogFile = WebGAL.backlogManager.getBacklog()[index];
+ logger.debug('读取的backlog数据', backlogFile);
+ // 重新获取并同步场景状态
+ if (refetchScene)
+ sceneFetcher(backlogFile.saveScene.sceneUrl).then((rawScene) => {
+ WebGAL.sceneManager.sceneData.currentScene = sceneParser(
+ rawScene,
+ backlogFile.saveScene.sceneName,
+ backlogFile.saveScene.sceneUrl,
+ );
+ // 开始场景的预加载
+ const subSceneList = WebGAL.sceneManager.sceneData.currentScene.subSceneList;
+ WebGAL.sceneManager.settledScenes.push(WebGAL.sceneManager.sceneData.currentScene.sceneUrl); // 放入已加载场景列表,避免递归加载相同场景
+ const subSceneListUniq = uniqWith(subSceneList); // 去重
+ scenePrefetcher(subSceneListUniq);
+ });
+ WebGAL.sceneManager.sceneData.currentSentenceId = backlogFile.saveScene.currentSentenceId;
+ WebGAL.sceneManager.sceneData.sceneStack = cloneDeep(backlogFile.saveScene.sceneStack);
+
+ // 强制停止所有演出
+ stopAllPerform();
+
+ // 弹出backlog项目到指定状态
+ for (let i = WebGAL.backlogManager.getBacklog().length - 1; i > index; i--) {
+ WebGAL.backlogManager.getBacklog().pop();
+ }
+
+ // 要记录本句 Backlog
+ WebGAL.backlogManager.isSaveBacklogNext = true;
+
+ // 恢复舞台状态
+ const newStageState: IStageState = cloneDeep(backlogFile.currentStageState);
+
+ dispatch(resetStageState(newStageState));
+
+ // 恢复演出
+ setTimeout(restorePerform, 0);
+
+ // 关闭backlog界面
+ dispatch(setVisibility({ component: 'showBacklog', visibility: false }));
+
+ // 重新显示 TextBox
+ dispatch(setVisibility({ component: 'showTextBox', visibility: true }));
+};
diff --git a/packages/webgal/src/Core/controller/storage/loadGame.ts b/packages/webgal/src/Core/controller/storage/loadGame.ts
new file mode 100644
index 000000000..ae471ab7e
--- /dev/null
+++ b/packages/webgal/src/Core/controller/storage/loadGame.ts
@@ -0,0 +1,77 @@
+import { ISaveData } from '@/store/userDataInterface';
+import { logger } from '../../util/logger';
+import { sceneFetcher } from '../scene/sceneFetcher';
+import { sceneParser } from '../../parser/sceneParser';
+import { webgalStore } from '@/store/store';
+import { resetStageState } from '@/store/stageReducer';
+import { setVisibility } from '@/store/GUIReducer';
+import { restorePerform } from './jumpFromBacklog';
+import { stopAllPerform } from '@/Core/controller/gamePlay/stopAllPerform';
+import cloneDeep from 'lodash/cloneDeep';
+import uniqWith from 'lodash/uniqWith';
+import { scenePrefetcher } from '@/Core/util/prefetcher/scenePrefetcher';
+import { setEbg } from '@/Core/gameScripts/changeBg/setEbg';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 读取游戏存档
+ * @param index 要读取的存档的档位
+ */
+export const loadGame = (index: number) => {
+ const userDataState = webgalStore.getState().saveData;
+ // 获得存档文件
+ const loadFile: ISaveData = userDataState.saveData[index];
+ logger.debug('读取的存档数据', loadFile);
+ // 加载存档
+ loadGameFromStageData(loadFile);
+};
+
+export function loadGameFromStageData(stageData: ISaveData) {
+ if (!stageData) {
+ logger.info('暂无存档');
+ return;
+ }
+ const loadFile = stageData;
+ // 重新获取并同步场景状态
+ sceneFetcher(loadFile.sceneData.sceneUrl).then((rawScene) => {
+ WebGAL.sceneManager.sceneData.currentScene = sceneParser(
+ rawScene,
+ loadFile.sceneData.sceneName,
+ loadFile.sceneData.sceneUrl,
+ );
+ // 开始场景的预加载
+ const subSceneList = WebGAL.sceneManager.sceneData.currentScene.subSceneList;
+ WebGAL.sceneManager.settledScenes.push(WebGAL.sceneManager.sceneData.currentScene.sceneUrl); // 放入已加载场景列表,避免递归加载相同场景
+ const subSceneListUniq = uniqWith(subSceneList); // 去重
+ scenePrefetcher(subSceneListUniq);
+ });
+ WebGAL.sceneManager.sceneData.currentSentenceId = loadFile.sceneData.currentSentenceId;
+ WebGAL.sceneManager.sceneData.sceneStack = cloneDeep(loadFile.sceneData.sceneStack);
+
+ // 强制停止所有演出
+ stopAllPerform();
+
+ // 恢复backlog
+ const newBacklog = loadFile.backlog;
+ WebGAL.backlogManager.getBacklog().splice(0, WebGAL.backlogManager.getBacklog().length); // 清空原backlog
+ for (const e of newBacklog) {
+ WebGAL.backlogManager.getBacklog().push(e);
+ }
+
+ // 恢复舞台状态
+ const newStageState = cloneDeep(loadFile.nowStageState);
+ const dispatch = webgalStore.dispatch;
+ dispatch(resetStageState(newStageState));
+
+ // 恢复演出
+ setTimeout(restorePerform, 0);
+ // restorePerform();
+
+ dispatch(setVisibility({ component: 'showTitle', visibility: false }));
+ dispatch(setVisibility({ component: 'showMenuPanel', visibility: false }));
+ /**
+ * 恢复模糊背景
+ */
+ setEbg(webgalStore.getState().stage.bgName);
+}
diff --git a/packages/webgal/src/Core/controller/storage/saveGame.ts b/packages/webgal/src/Core/controller/storage/saveGame.ts
new file mode 100644
index 000000000..c729b334c
--- /dev/null
+++ b/packages/webgal/src/Core/controller/storage/saveGame.ts
@@ -0,0 +1,60 @@
+import { logger } from '../../util/logger';
+import { ISaveData } from '@/store/userDataInterface';
+import { dumpToStorageFast } from './storageController';
+import { webgalStore } from '@/store/store';
+import { setUserData } from '@/store/userDataReducer';
+import cloneDeep from 'lodash/cloneDeep';
+
+import { WebGAL } from '@/Core/WebGAL';
+import { saveActions } from '@/store/savesReducer';
+import { dumpSavesToStorage } from '@/Core/controller/storage/savesController';
+
+/**
+ * 保存游戏
+ * @param index 游戏的档位
+ */
+export const saveGame = (index: number) => {
+ const saveData: ISaveData = generateCurrentStageData(index);
+ webgalStore.dispatch(saveActions.saveGame({ index, saveData }));
+ dumpSavesToStorage(index, index);
+};
+
+/**
+ * 生成现在游戏的数据快照
+ * @param index 游戏的档位
+ */
+export function generateCurrentStageData(index: number, isSavePreviewImage = true) {
+ const stageState = webgalStore.getState().stage;
+ const saveBacklog = cloneDeep(WebGAL.backlogManager.getBacklog());
+
+ /**
+ * 生成缩略图
+ */
+
+ let urlToSave = '';
+ if (isSavePreviewImage) {
+ const canvas: HTMLCanvasElement = document.getElementById('pixiCanvas')! as HTMLCanvasElement;
+ const canvas2 = document.createElement('canvas');
+ const context = canvas2.getContext('2d');
+ canvas2.width = 480;
+ canvas2.height = 270;
+ context!.drawImage(canvas, 0, 0, 480, 270);
+ urlToSave = canvas2.toDataURL('image/webp', 0.5);
+ canvas2.remove();
+ }
+ const saveData: ISaveData = {
+ nowStageState: cloneDeep(stageState),
+ backlog: saveBacklog, // 舞台数据
+ index: index, // 存档的序号
+ saveTime: new Date().toLocaleDateString() + ' ' + new Date().toLocaleTimeString('chinese', { hour12: false }), // 保存时间
+ // 场景数据
+ sceneData: {
+ currentSentenceId: WebGAL.sceneManager.sceneData.currentSentenceId, // 当前语句ID
+ sceneStack: cloneDeep(WebGAL.sceneManager.sceneData.sceneStack), // 场景栈
+ sceneName: WebGAL.sceneManager.sceneData.currentScene.sceneName, // 场景名称
+ sceneUrl: WebGAL.sceneManager.sceneData.currentScene.sceneUrl, // 场景url
+ },
+ previewImage: urlToSave,
+ };
+ return saveData;
+}
diff --git a/packages/webgal/src/Core/controller/storage/savesController.ts b/packages/webgal/src/Core/controller/storage/savesController.ts
new file mode 100644
index 000000000..3a01b4046
--- /dev/null
+++ b/packages/webgal/src/Core/controller/storage/savesController.ts
@@ -0,0 +1,36 @@
+import localforage from 'localforage';
+import { WebGAL } from '@/Core/WebGAL';
+import { logger } from '@/Core/util/logger';
+import { webgalStore } from '@/store/store';
+import { saveActions } from '@/store/savesReducer';
+import { ISaveData } from '@/store/userDataInterface';
+
+export function dumpSavesToStorage(startIndex: number, endIndex: number) {
+ for (let i = startIndex; i <= endIndex; i++) {
+ const save = webgalStore.getState().saveData.saveData[i];
+ localforage.setItem(`${WebGAL.gameKey}-saves${i}`, save).then(() => {
+ logger.info(`存档${i}写入本地存储`);
+ });
+ }
+}
+
+export function getSavesFromStorage(startIndex: number, endIndex: number) {
+ for (let i = startIndex; i <= endIndex; i++) {
+ localforage.getItem(`${WebGAL.gameKey}-saves${i}`).then((save) => {
+ webgalStore.dispatch(saveActions.saveGame({ index: i, saveData: save as ISaveData }));
+ logger.info(`存档${i}读取自本地存储`);
+ });
+ }
+}
+
+export async function dumpFastSaveToStorage() {
+ const save = webgalStore.getState().saveData.quickSaveData;
+ await localforage.setItem(`${WebGAL.gameKey}-saves-fast`, save);
+ logger.info(`快速存档写入本地存储`);
+}
+
+export async function getFastSaveFromStorage() {
+ const save = await localforage.getItem(`${WebGAL.gameKey}-saves-fast`);
+ webgalStore.dispatch(saveActions.setFastSave(save as ISaveData));
+ logger.info(`快速存档读取自本地存储`);
+}
diff --git a/packages/webgal/src/Core/controller/storage/storageController.ts b/packages/webgal/src/Core/controller/storage/storageController.ts
new file mode 100644
index 000000000..1d0a1fb86
--- /dev/null
+++ b/packages/webgal/src/Core/controller/storage/storageController.ts
@@ -0,0 +1,96 @@
+import * as localforage from 'localforage';
+import { IUserData } from '@/store/userDataInterface';
+import { logger } from '../../util/logger';
+import { webgalStore } from '@/store/store';
+import { initState, resetUserData } from '@/store/userDataReducer';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 写入本地存储
+ */
+export const setStorage = debounce(() => {
+ const userDataState = webgalStore.getState().userData;
+ localforage.setItem(WebGAL.gameKey, userDataState).then(() => {
+ logger.info('写入本地存储');
+ });
+}, 100);
+
+/**
+ * 从本地存储获取数据
+ */
+export const getStorage = debounce(() => {
+ localforage.getItem(WebGAL.gameKey).then((newUserData) => {
+ // 如果没有数据或者属性不完全,重新初始化
+ if (!newUserData || !checkUserDataProperty(newUserData)) {
+ logger.warn('现在重置数据');
+ setStorage();
+ return;
+ }
+ webgalStore.dispatch(resetUserData(newUserData as IUserData));
+ });
+}, 100);
+
+/**
+ * 防抖函数
+ * @param func 要执行的函数
+ * @param wait 防抖等待时间
+ */
+function debounce(func: (...args: T[]) => K, wait: number) {
+ let timeout: ReturnType;
+
+ function context(...args: T[]): K {
+ clearTimeout(timeout);
+ let ret!: K;
+ timeout = setTimeout(() => {
+ ret = func.apply(context, args);
+ }, wait);
+ return ret;
+ }
+
+ return context;
+}
+
+export const dumpToStorageFast = () => {
+ const userDataState = webgalStore.getState().userData;
+ localforage.setItem(WebGAL.gameKey, userDataState).then(() => {
+ localforage.getItem(WebGAL.gameKey).then((newUserData) => {
+ // 如果没有数据,初始化
+ if (!newUserData) {
+ setStorage();
+ return;
+ }
+ webgalStore.dispatch(resetUserData(newUserData as IUserData));
+ });
+ logger.info('同步本地存储');
+ });
+};
+
+/**
+ * 检查用户数据属性是否齐全
+ * @param userData 需要检查的数据
+ */
+function checkUserDataProperty(userData: any) {
+ let result = true;
+ for (const key in initState) {
+ if (!userData.hasOwnProperty(key)) {
+ result = false;
+ }
+ }
+ return result;
+}
+
+export async function setStorageAsync() {
+ const userDataState = webgalStore.getState().userData;
+ return await localforage.setItem(WebGAL.gameKey, userDataState);
+}
+
+export async function getStorageAsync() {
+ const newUserData = await localforage.getItem(WebGAL.gameKey);
+ if (!newUserData || !checkUserDataProperty(newUserData)) {
+ const userDataState = webgalStore.getState().userData;
+ logger.warn('现在重置数据');
+ return await localforage.setItem(WebGAL.gameKey, userDataState);
+ } else webgalStore.dispatch(resetUserData(newUserData as IUserData));
+ return;
+}
diff --git a/packages/webgal/src/Core/gameScripts/applyStyle.ts b/packages/webgal/src/Core/gameScripts/applyStyle.ts
new file mode 100644
index 000000000..ca7e85d7e
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/applyStyle.ts
@@ -0,0 +1,30 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { webgalStore } from '@/store/store';
+import { stageActions } from '@/store/stageReducer';
+
+/**
+ * 语句执行的模板代码
+ * @param sentence
+ */
+export const applyStyle = (sentence: ISentence): IPerform => {
+ const { content } = sentence;
+ const applyStyleSegments = content.split(',');
+ for (const applyStyleSegment of applyStyleSegments) {
+ const splitSegment = applyStyleSegment.split('->');
+ if (splitSegment.length >= 2) {
+ const classNameToBeChange = splitSegment[0];
+ const classNameChangeTo = splitSegment[1];
+ webgalStore.dispatch(stageActions.replaceUIlable([classNameToBeChange, classNameChangeTo]));
+ }
+ }
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/bgm.ts b/packages/webgal/src/Core/gameScripts/bgm.ts
new file mode 100644
index 000000000..1a973eb23
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/bgm.ts
@@ -0,0 +1,41 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { playBgm } from '@/Core/controller/stage/playBgm';
+import { getSentenceArgByKey } from '@/Core/util/getSentenceArg';
+import { webgalStore } from '@/store/store';
+import { unlockBgmInUserData } from '@/store/userDataReducer';
+
+/**
+ * 播放一段bgm
+ * @param sentence
+ */
+export const bgm = (sentence: ISentence): IPerform => {
+ let url: string = sentence.content; // 获取bgm的url
+ let name = '';
+ let series = 'default';
+ sentence.args.forEach((e) => {
+ if (e.key === 'unlockname') {
+ name = e.value.toString();
+ }
+ if (e.key === 'series') {
+ series = e.value.toString();
+ }
+ });
+ const enter = getSentenceArgByKey(sentence, 'enter'); // 获取bgm的淡入时间
+ const volume = getSentenceArgByKey(sentence, 'volume'); // 获取bgm的音量比
+ if (name !== '') webgalStore.dispatch(unlockBgmInUserData({ name, url, series }));
+ playBgm(
+ url,
+ typeof enter === 'number' && enter >= 0 ? enter : 0, // 已正确设置淡入时间时,进行淡入
+ typeof volume === 'number' && volume >= 0 && volume <= 100 ? volume : 100, // 已正确设置音量比时,进行音量调整
+ );
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: true,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/callSceneScript.ts b/packages/webgal/src/Core/gameScripts/callSceneScript.ts
new file mode 100644
index 000000000..48aec5494
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/callSceneScript.ts
@@ -0,0 +1,22 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { callScene } from '../controller/scene/callScene';
+
+/**
+ * 调用一个场景,在场景结束后回到调用这个场景的父场景。
+ * @param sentence
+ */
+export const callSceneScript = (sentence: ISentence): IPerform => {
+ const sceneNameArray: Array = sentence.content.split('/');
+ const sceneName = sceneNameArray[sceneNameArray.length - 1];
+ callScene(sentence.content, sceneName);
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: true,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/changeBg/index.ts b/packages/webgal/src/Core/gameScripts/changeBg/index.ts
new file mode 100644
index 000000000..9917789f6
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/changeBg/index.ts
@@ -0,0 +1,104 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+// import {getRandomPerformName} from '../../../util/getRandomPerformName';
+import styles from '@/Stage/stage.module.scss';
+import { webgalStore } from '@/store/store';
+import { setStage, stageActions } from '@/store/stageReducer';
+import { getSentenceArgByKey } from '@/Core/util/getSentenceArg';
+import { unlockCgInUserData } from '@/store/userDataReducer';
+import { logger } from '@/Core/util/logger';
+import { ITransform } from '@/store/stageInterface';
+import { generateTransformAnimationObj } from '@/Core/controller/stage/pixi/animations/generateTransformAnimationObj';
+import { IUserAnimation } from '@/Core/Modules/animations';
+import cloneDeep from 'lodash/cloneDeep';
+import { getAnimateDuration } from '@/Core/Modules/animationFunctions';
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 进行背景图片的切换
+ * @param sentence 语句
+ * @return {IPerform}
+ */
+export const changeBg = (sentence: ISentence): IPerform => {
+ const url = sentence.content;
+ let name = '';
+ let series = 'default';
+ sentence.args.forEach((e) => {
+ if (e.key === 'unlockname') {
+ name = e.value.toString();
+ }
+ if (e.key === 'series') {
+ series = e.value.toString();
+ }
+ });
+
+ const dispatch = webgalStore.dispatch;
+ if (name !== '') dispatch(unlockCgInUserData({ name, url, series }));
+
+ /**
+ * 删掉相关 Effects,因为已经移除了
+ */
+ dispatch(stageActions.removeEffectByTargetId(`bg-main`));
+
+ // 处理 transform 和 默认 transform
+ const transformString = getSentenceArgByKey(sentence, 'transform');
+ let duration = getSentenceArgByKey(sentence, 'duration');
+ if (!duration || typeof duration !== 'number') {
+ duration = 1000;
+ }
+ let animationObj: (ITransform & {
+ duration: number;
+ })[];
+ if (transformString) {
+ try {
+ const frame = JSON.parse(transformString.toString()) as ITransform & { duration: number };
+ animationObj = generateTransformAnimationObj('bg-main', frame, duration);
+ // 因为是切换,必须把一开始的 alpha 改为 0
+ animationObj[0].alpha = 0;
+ const animationName = (Math.random() * 10).toString(16);
+ const newAnimation: IUserAnimation = { name: animationName, effects: animationObj };
+ WebGAL.animationManager.addAnimation(newAnimation);
+ duration = getAnimateDuration(animationName);
+ WebGAL.animationManager.nextEnterAnimationName.set('bg-main', animationName);
+ } catch (e) {
+ // 解析都错误了,歇逼吧
+ applyDefaultTransform();
+ }
+ } else {
+ applyDefaultTransform();
+ }
+
+ function applyDefaultTransform() {
+ // 应用默认的
+ const frame = {};
+ animationObj = generateTransformAnimationObj('bg-main', frame as ITransform & { duration: number }, duration);
+ // 因为是切换,必须把一开始的 alpha 改为 0
+ animationObj[0].alpha = 0;
+ const animationName = (Math.random() * 10).toString(16);
+ const newAnimation: IUserAnimation = { name: animationName, effects: animationObj };
+ WebGAL.animationManager.addAnimation(newAnimation);
+ duration = getAnimateDuration(animationName);
+ WebGAL.animationManager.nextEnterAnimationName.set('bg-main', animationName);
+ }
+
+ // 应用动画的优先级更高一点
+ if (getSentenceArgByKey(sentence, 'enter')) {
+ WebGAL.animationManager.nextEnterAnimationName.set('bg-main', getSentenceArgByKey(sentence, 'enter')!.toString());
+ duration = getAnimateDuration(getSentenceArgByKey(sentence, 'enter')!.toString());
+ }
+ if (getSentenceArgByKey(sentence, 'exit')) {
+ WebGAL.animationManager.nextExitAnimationName.set('bg-main-off', getSentenceArgByKey(sentence, 'exit')!.toString());
+ duration = getAnimateDuration(getSentenceArgByKey(sentence, 'exit')!.toString());
+ }
+ dispatch(setStage({ key: 'bgName', value: sentence.content }));
+
+ return {
+ performName: 'none',
+ duration,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/changeBg/setEbg.ts b/packages/webgal/src/Core/gameScripts/changeBg/setEbg.ts
new file mode 100644
index 000000000..d018903ff
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/changeBg/setEbg.ts
@@ -0,0 +1,6 @@
+export function setEbg(url: string) {
+ const ebg = document.getElementById('ebg');
+ if (ebg) {
+ ebg.style.backgroundImage = `url("${url}")`;
+ }
+}
diff --git a/packages/webgal/src/Core/gameScripts/changeFigure.ts b/packages/webgal/src/Core/gameScripts/changeFigure.ts
new file mode 100644
index 000000000..bad8e7201
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/changeFigure.ts
@@ -0,0 +1,292 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { webgalStore } from '@/store/store';
+import { setStage, stageActions } from '@/store/stageReducer';
+import cloneDeep from 'lodash/cloneDeep';
+import { getSentenceArgByKey } from '@/Core/util/getSentenceArg';
+import { IFreeFigure, IStageState, ITransform } from '@/store/stageInterface';
+import { IUserAnimation } from '@/Core/Modules/animations';
+import { generateTransformAnimationObj } from '@/Core/controller/stage/pixi/animations/generateTransformAnimationObj';
+import { assetSetter, fileType } from '@/Core/util/gameAssetsAccess/assetSetter';
+import { logger } from '@/Core/util/logger';
+import { getAnimateDuration } from '@/Core/Modules/animationFunctions';
+import { WebGAL } from '@/Core/WebGAL';
+/**
+ * 更改立绘
+ * @param sentence 语句
+ */
+// eslint-disable-next-line complexity
+export function changeFigure(sentence: ISentence): IPerform {
+ // 根据参数设置指定位置
+ let pos: 'center' | 'left' | 'right' = 'center';
+ let content = sentence.content;
+ let isFreeFigure = false;
+ let motion = '';
+ let expression = '';
+ let key = '';
+ let duration = 500;
+ let mouthOpen = '';
+ let mouthClose = '';
+ let mouthHalfOpen = '';
+ let eyesOpen = '';
+ let eyesClose = '';
+ let animationFlag: any = '';
+ let mouthAnimationKey: any = 'mouthAnimation';
+ let eyesAnimationKey: any = 'blinkAnimation';
+ let overrideBounds = '';
+ let zIndex = -1;
+ const dispatch = webgalStore.dispatch;
+
+ for (const e of sentence.args) {
+ switch (e.key) {
+ case 'left':
+ if (e.value === true) {
+ pos = 'left';
+ mouthAnimationKey = 'mouthAnimationLeft';
+ eyesAnimationKey = 'blinkAnimationLeft';
+ }
+ break;
+ case 'right':
+ if (e.value === true) {
+ pos = 'right';
+ mouthAnimationKey = 'mouthAnimationRight';
+ eyesAnimationKey = 'blinkAnimationRight';
+ }
+ break;
+ case 'clear':
+ if (e.value === true) {
+ content = '';
+ }
+ break;
+ case 'id':
+ isFreeFigure = true;
+ key = e.value.toString();
+ break;
+ case 'motion':
+ motion = e.value.toString();
+ break;
+ case 'bounds':
+ overrideBounds = String(e.value);
+ break;
+ case 'expression':
+ expression = e.value.toString();
+ break;
+ case 'mouthOpen':
+ mouthOpen = e.value.toString();
+ mouthOpen = assetSetter(mouthOpen, fileType.figure);
+ break;
+ case 'mouthClose':
+ mouthClose = e.value.toString();
+ mouthClose = assetSetter(mouthClose, fileType.figure);
+ break;
+ case 'mouthHalfOpen':
+ mouthHalfOpen = e.value.toString();
+ mouthHalfOpen = assetSetter(mouthHalfOpen, fileType.figure);
+ break;
+ case 'eyesOpen':
+ eyesOpen = e.value.toString();
+ eyesOpen = assetSetter(eyesOpen, fileType.figure);
+ break;
+ case 'eyesClose':
+ eyesClose = e.value.toString();
+ eyesClose = assetSetter(eyesClose, fileType.figure);
+ break;
+ case 'animationFlag':
+ animationFlag = e.value.toString();
+ break;
+ case 'none':
+ content = '';
+ break;
+ case 'zIndex':
+ zIndex = Number(e.value);
+ break;
+ default:
+ break;
+ }
+ }
+
+ const id = key ? key : `fig-${pos}`;
+
+ const currentFigureAssociatedAnimation = webgalStore.getState().stage.figureAssociatedAnimation;
+ const filteredFigureAssociatedAnimation = currentFigureAssociatedAnimation.filter((item) => item.targetId !== id);
+ const newFigureAssociatedAnimationItem = {
+ targetId: id,
+ animationFlag: animationFlag,
+ mouthAnimation: {
+ open: mouthOpen,
+ close: mouthClose,
+ halfOpen: mouthHalfOpen,
+ },
+ blinkAnimation: {
+ open: eyesOpen,
+ close: eyesClose,
+ },
+ };
+ filteredFigureAssociatedAnimation.push(newFigureAssociatedAnimationItem);
+ dispatch(setStage({ key: 'figureAssociatedAnimation', value: filteredFigureAssociatedAnimation }));
+
+ /**
+ * 如果 url 没变,不移除
+ */
+ let isRemoveEffects = true;
+ if (key !== '') {
+ const figWithKey = webgalStore.getState().stage.freeFigure.find((e) => e.key === key);
+ if (figWithKey) {
+ if (figWithKey.name === sentence.content) {
+ isRemoveEffects = false;
+ }
+ }
+ } else {
+ if (pos === 'center') {
+ if (webgalStore.getState().stage.figName === sentence.content) {
+ isRemoveEffects = false;
+ }
+ }
+ if (pos === 'left') {
+ if (webgalStore.getState().stage.figNameLeft === sentence.content) {
+ isRemoveEffects = false;
+ }
+ }
+ if (pos === 'right') {
+ if (webgalStore.getState().stage.figNameRight === sentence.content) {
+ isRemoveEffects = false;
+ }
+ }
+ }
+ /**
+ * 处理 Effects
+ */
+ if (isRemoveEffects) {
+ const deleteKey = `fig-${pos}`;
+ const deleteKey2 = `${key}`;
+ webgalStore.dispatch(stageActions.removeEffectByTargetId(deleteKey));
+ webgalStore.dispatch(stageActions.removeEffectByTargetId(deleteKey2));
+ // 重设 figureMetaData,这里是 zIndex,实际上任何键都可以,因为整体是移除那条记录
+ dispatch(stageActions.setFigureMetaData([deleteKey, 'zIndex', 0, true]));
+ dispatch(stageActions.setFigureMetaData([deleteKey2, 'zIndex', 0, true]));
+ }
+ const setAnimationNames = (key: string, sentence: ISentence) => {
+ // 处理 transform 和 默认 transform
+ const transformString = getSentenceArgByKey(sentence, 'transform');
+ const durationFromArg = getSentenceArgByKey(sentence, 'duration');
+ if (durationFromArg && typeof durationFromArg === 'number') {
+ duration = durationFromArg;
+ }
+ let animationObj: (ITransform & {
+ duration: number;
+ })[];
+ if (transformString) {
+ console.log(transformString);
+ try {
+ const frame = JSON.parse(transformString.toString()) as ITransform & { duration: number };
+ animationObj = generateTransformAnimationObj(key, frame, duration);
+ // 因为是切换,必须把一开始的 alpha 改为 0
+ animationObj[0].alpha = 0;
+ const animationName = (Math.random() * 10).toString(16);
+ const newAnimation: IUserAnimation = { name: animationName, effects: animationObj };
+ WebGAL.animationManager.addAnimation(newAnimation);
+ duration = getAnimateDuration(animationName);
+ WebGAL.animationManager.nextEnterAnimationName.set(key, animationName);
+ } catch (e) {
+ // 解析都错误了,歇逼吧
+ applyDefaultTransform();
+ }
+ } else {
+ applyDefaultTransform();
+ }
+
+ function applyDefaultTransform() {
+ // 应用默认的
+ const frame = {};
+ animationObj = generateTransformAnimationObj(key, frame as ITransform & { duration: number }, duration);
+ // 因为是切换,必须把一开始的 alpha 改为 0
+ animationObj[0].alpha = 0;
+ const animationName = (Math.random() * 10).toString(16);
+ const newAnimation: IUserAnimation = { name: animationName, effects: animationObj };
+ WebGAL.animationManager.addAnimation(newAnimation);
+ duration = getAnimateDuration(animationName);
+ WebGAL.animationManager.nextEnterAnimationName.set(key, animationName);
+ }
+ const enterAnim = getSentenceArgByKey(sentence, 'enter');
+ const exitAnim = getSentenceArgByKey(sentence, 'exit');
+ if (enterAnim) {
+ WebGAL.animationManager.nextEnterAnimationName.set(key, enterAnim.toString());
+ duration = getAnimateDuration(enterAnim.toString());
+ }
+ if (exitAnim) {
+ WebGAL.animationManager.nextExitAnimationName.set(key + '-off', exitAnim.toString());
+ duration = getAnimateDuration(exitAnim.toString());
+ }
+ };
+ if (isFreeFigure) {
+ /**
+ * 下面的代码是设置自由立绘的
+ */
+ const freeFigureItem: IFreeFigure = { key, name: content, basePosition: pos };
+ setAnimationNames(key, sentence);
+ if (motion || overrideBounds) {
+ dispatch(
+ stageActions.setLive2dMotion({ target: key, motion, overrideBounds: getOverrideBoundsArr(overrideBounds) }),
+ );
+ }
+ if (expression) {
+ dispatch(stageActions.setLive2dExpression({ target: key, expression }));
+ }
+ if (zIndex > 0) {
+ dispatch(stageActions.setFigureMetaData([key, 'zIndex', zIndex, false]));
+ }
+ dispatch(stageActions.setFreeFigureByKey(freeFigureItem));
+ } else {
+ /**
+ * 下面的代码是设置与位置关联的立绘的
+ */
+ const positionMap = {
+ center: 'fig-center',
+ left: 'fig-left',
+ right: 'fig-right',
+ };
+ const dispatchMap: Record = {
+ center: 'figName',
+ left: 'figNameLeft',
+ right: 'figNameRight',
+ };
+
+ key = positionMap[pos];
+ setAnimationNames(key, sentence);
+ if (motion || overrideBounds) {
+ dispatch(
+ stageActions.setLive2dMotion({ target: key, motion, overrideBounds: getOverrideBoundsArr(overrideBounds) }),
+ );
+ }
+ if (expression) {
+ dispatch(stageActions.setLive2dExpression({ target: key, expression }));
+ }
+ if (zIndex > 0) {
+ dispatch(stageActions.setFigureMetaData([key, 'zIndex', zIndex, false]));
+ }
+ dispatch(setStage({ key: dispatchMap[pos], value: content }));
+ }
+
+ return {
+ performName: 'none',
+ duration,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => false,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+}
+
+function getOverrideBoundsArr(raw: string): undefined | [number, number, number, number] {
+ const parseOverrideBoundsResult = raw.split(',').map((e) => Number(e));
+ let isPass = true;
+ parseOverrideBoundsResult.forEach((e) => {
+ if (isNaN(e)) {
+ isPass = false;
+ }
+ });
+ isPass = isPass && parseOverrideBoundsResult.length === 4;
+ if (isPass) return parseOverrideBoundsResult as [number, number, number, number];
+ else return undefined;
+}
diff --git a/packages/webgal/src/Core/gameScripts/changeSceneScript.ts b/packages/webgal/src/Core/gameScripts/changeSceneScript.ts
new file mode 100644
index 000000000..91a2d17a7
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/changeSceneScript.ts
@@ -0,0 +1,22 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { changeScene } from '../controller/scene/changeScene';
+
+/**
+ * 切换场景。在场景结束后不会回到父场景。
+ * @param sentence
+ */
+export const changeSceneScript = (sentence: ISentence): IPerform => {
+ const sceneNameArray: Array = sentence.content.split('/');
+ const sceneName = sceneNameArray[sceneNameArray.length - 1];
+ changeScene(sentence.content, sceneName);
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: true,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/choose/choose.module.scss b/packages/webgal/src/Core/gameScripts/choose/choose.module.scss
new file mode 100644
index 000000000..4040078bc
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/choose/choose.module.scss
@@ -0,0 +1,54 @@
+.Choose_Main {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-flow: column;
+ justify-content: center;
+ align-items: center;
+ z-index: 13;
+ background: rgba(0, 0, 0, 0.05);
+}
+
+.Choose_item {
+ font-family: "WebgalUI", serif;
+ cursor: pointer;
+ min-width: 50%;
+ padding: 0.25em 1em 0.25em 1em;
+ font-size: 265%;
+ color: #8E354A;
+ text-align: center;
+ border-radius: 4px;
+ border: 3px solid rgba(0, 0, 0, 0);
+ box-shadow: 0 0 25px rgba(0, 0, 0, 0.25);
+ background: rgba(255, 255, 255, 0.65);
+ margin: 0.25em 0 0.25em 0;
+ transition: background-color 0.5s, border 0.5s, font-weight 0.5s, box-shadow 0.5s;
+
+ &:hover {
+ background: rgba(255, 255, 255, 0.9);
+ box-shadow: 0 0 25px rgba(0, 0, 0, 0.35);
+ border: 3px solid #8E354A;
+ }
+}
+
+.Choose_item_disabled {
+ font-family: "WebgalUI", serif;
+ cursor: not-allowed;
+ min-width: 50%;
+ padding: 0.25em 1em 0.25em 1em;
+ font-size: 265%;
+ color: rgba(142, 53, 74, 0.5);
+ text-align: center;
+ border-radius: 4px;
+ border: 3px solid rgba(0, 0, 0, 0);
+ box-shadow: 0 0 25px rgba(0, 0, 0, 0.25);
+ background: rgba(255, 255, 255, 0.5);
+ margin: 0.25em 0 0.25em 0;
+ transition: background-color 0.5s, border 0.5s, font-weight 0.5s, box-shadow 0.5s;
+}
+
+.Choose_item_outer {
+ color: #000;
+ min-width: 50%;
+}
diff --git a/packages/webgal/src/Core/gameScripts/choose/index.tsx b/packages/webgal/src/Core/gameScripts/choose/index.tsx
new file mode 100644
index 000000000..ef22cc7ec
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/choose/index.tsx
@@ -0,0 +1,119 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { changeScene } from '@/Core/controller/scene/changeScene';
+import { jmp } from '@/Core/gameScripts/label/jmp';
+import ReactDOM from 'react-dom';
+import React from 'react';
+import styles from './choose.module.scss';
+import { webgalStore } from '@/store/store';
+import { textFont } from '@/store/userDataInterface';
+import { PerformController } from '@/Core/Modules/perform/performController';
+import { useSEByWebgalStore } from '@/hooks/useSoundEffect';
+import { WebGAL } from '@/Core/WebGAL';
+import { whenChecker } from '@/Core/controller/gamePlay/scriptExecutor';
+import useEscape from '@/hooks/useEscape';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import { Provider } from 'react-redux';
+
+class ChooseOption {
+ /**
+ * 格式:
+ * (showConditionVar>1)[enableConditionVar>2]->text:jump
+ */
+ public static parse(script: string): ChooseOption {
+ const parts = script.split('->');
+ const conditonPart = parts.length > 1 ? parts[0] : null;
+ const mainPart = parts.length > 1 ? parts[1] : parts[0];
+ const mainPartNodes = mainPart.split(/(? {
+ const chooseOptionScripts = sentence.content.split(/(? ChooseOption.parse(e));
+
+ // eslint-disable-next-line react/no-deprecated
+ ReactDOM.render(
+
+
+ ,
+ document.getElementById('chooseContainer'),
+ );
+ return {
+ performName: 'choose',
+ duration: 1000 * 60 * 60 * 24,
+ isHoldOn: false,
+ stopFunction: () => {
+ // eslint-disable-next-line react/no-deprecated
+ ReactDOM.render(
, document.getElementById('chooseContainer'));
+ },
+ blockingNext: () => true,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
+
+function Choose(props: { chooseOptions: ChooseOption[] }) {
+ const fontFamily = webgalStore.getState().userData.optionData.textboxFont;
+ const font = fontFamily === textFont.song ? '"思源宋体", serif' : '"WebgalUI", serif';
+ const { playSeEnter, playSeClick } = useSEByWebgalStore();
+ const applyStyle = useApplyStyle('Stage/Choose/choose.scss');
+ // 运行时计算JSX.Element[]
+ const runtimeBuildList = (chooseListFull: ChooseOption[]) => {
+ return chooseListFull
+ .filter((e, i) => whenChecker(e.showCondition))
+ .map((e, i) => {
+ const enable = whenChecker(e.enableCondition);
+ const className = enable
+ ? applyStyle('Choose_item', styles.Choose_item)
+ : applyStyle('Choose_item_disabled', styles.Choose_item_disabled);
+ const onClick = enable
+ ? () => {
+ playSeClick();
+ if (e.jumpToScene) {
+ changeScene(e.jump, e.text);
+ } else {
+ jmp(e.jump);
+ }
+ WebGAL.gameplay.performController.unmountPerform('choose');
+ }
+ : () => {};
+ return (
+
+ );
+ });
+ };
+
+ return {runtimeBuildList(props.chooseOptions)}
;
+}
diff --git a/packages/webgal/src/Core/gameScripts/comment.ts b/packages/webgal/src/Core/gameScripts/comment.ts
new file mode 100644
index 000000000..8d2b9917d
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/comment.ts
@@ -0,0 +1,20 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { logger } from '@/Core/util/logger';
+
+/**
+ * 注释,打LOG
+ * @param sentence
+ */
+export const comment = (sentence: ISentence): IPerform => {
+ logger.debug(`脚本内注释${sentence.content}`);
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/end.ts b/packages/webgal/src/Core/gameScripts/end.ts
new file mode 100644
index 000000000..3902472bf
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/end.ts
@@ -0,0 +1,44 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { assetSetter, fileType } from '@/Core/util/gameAssetsAccess/assetSetter';
+import { sceneFetcher } from '@/Core/controller/scene/sceneFetcher';
+import { sceneParser } from '@/Core/parser/sceneParser';
+import { resetStage } from '@/Core/controller/stage/resetStage';
+import { webgalStore } from '@/store/store';
+import { setVisibility } from '@/store/GUIReducer';
+import { playBgm } from '@/Core/controller/stage/playBgm';
+import { WebGAL } from '@/Core/WebGAL';
+import { dumpToStorageFast } from '@/Core/controller/storage/storageController';
+import { saveActions } from '@/store/savesReducer';
+
+/**
+ * 结束游戏
+ * @param sentence
+ */
+export const end = (sentence: ISentence): IPerform => {
+ resetStage(true);
+ const dispatch = webgalStore.dispatch;
+ // 重新获取初始场景
+ const sceneUrl: string = assetSetter('start.txt', fileType.scene);
+ // 为了在 scriptExecutor 自增 sentenceId 后再重置场景
+ setTimeout(() => {
+ WebGAL.sceneManager.resetScene();
+ }, 5);
+ dispatch(saveActions.resetFastSave());
+ dumpToStorageFast();
+ sceneFetcher(sceneUrl).then((rawScene) => {
+ // 场景写入到运行时
+ WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, 'start.txt', sceneUrl);
+ });
+ dispatch(setVisibility({ component: 'showTitle', visibility: true }));
+ playBgm(webgalStore.getState().GUI.titleBgm);
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/filmMode.ts b/packages/webgal/src/Core/gameScripts/filmMode.ts
new file mode 100644
index 000000000..3009cc215
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/filmMode.ts
@@ -0,0 +1,25 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { webgalStore } from '@/store/store';
+import { setStage } from '@/store/stageReducer';
+
+/**
+ * 语句执行的模板代码
+ * @param sentence
+ */
+export const filmMode = (sentence: ISentence): IPerform => {
+ if (sentence.content !== '' && sentence.content !== 'none') {
+ webgalStore.dispatch(setStage({ key: 'enableFilm', value: sentence.content }));
+ } else {
+ webgalStore.dispatch(setStage({ key: 'enableFilm', value: '' }));
+ }
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/getUserInput/getUserInput.module.scss b/packages/webgal/src/Core/gameScripts/getUserInput/getUserInput.module.scss
new file mode 100644
index 000000000..930d0463c
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/getUserInput/getUserInput.module.scss
@@ -0,0 +1,106 @@
+.Choose_Main {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-flow: column;
+ justify-content: center;
+ align-items: center;
+ z-index: 13;
+ background: rgba(0, 0, 0, 0.05);
+}
+
+.Choose_item {
+ cursor: pointer;
+ min-width: 50%;
+ padding: 0.25em 1em 0.25em 1em;
+ font-size: 265%;
+ color: #005caf;
+ text-align: center;
+ border-radius: 4px;
+ border: 3px solid rgba(0, 0, 0, 0);
+ box-shadow: 0 0 25px rgba(0, 0, 0, 0.25);
+ background: rgba(255, 255, 255, 0.65);
+ margin: 0.25em 0 0.25em 0;
+ transition: background-color 0.5s, border 0.5s, font-weight 0.5s, box-shadow 0.5s;
+}
+
+//.title {
+// cursor: pointer;
+// min-width: 50%;
+// padding: 0.25em 1em 0.25em 1em;
+// font-size: 265%;
+// color: #8E354A;
+// text-align: center;
+// border-radius: 4px;
+// border: 3px solid rgba(0, 0, 0, 0);
+// box-shadow: 0 0 25px rgba(0, 0, 0, 0.25);
+// background: rgba(255, 255, 255, 0.65);
+// margin: 0.25em 0 0.25em 0;
+// transition: background-color 0.5s, border 0.5s, font-weight 0.5s, box-shadow 0.5s;
+//}
+
+.glabalDialog_container_inner {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-flow: column;
+ background: linear-gradient(to right,
+ rgba(0, 92, 175, 0) 0%,
+ rgba(0, 92, 175, 0.5) 33%,
+ rgba(0, 92, 175, 0.85) 50%,
+ rgba(0, 92, 175, 0.5) 66%,
+ rgba(0, 92, 175, 0) 100%
+ );
+ padding: 1em 5em 1.5em 5em;
+}
+
+.glabalDialog_container {
+ //height: 20%;
+ color:white;
+ width: 100%;
+ border-top: 4px solid;
+ border-bottom: 4px solid;
+ border-image: linear-gradient(to right,
+ rgba(255, 255, 255, 0.05) 0%,
+ rgba(255, 255, 255, 0.85) 33%,
+ rgba(255, 255, 255, 1) 50%,
+ rgba(255, 255, 255, 0.85) 66%,
+ rgba(255, 255, 255, 0.05) 100%
+ ) 1;
+ //padding: 1px 1px 1px 1px;
+}
+
+.title {
+ font-size: 300%;
+ letter-spacing: 0.05em;
+ text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
+}
+
+.Choose_item:hover {
+ //font-weight: bold;
+ background: rgba(255, 255, 255, 0.9);
+ box-shadow: 0 0 25px rgba(0, 0, 0, 0.35);
+ border: 3px solid #005caf;
+}
+
+.button {
+ font-size: 200%;
+ padding: 0.15em 1em 0.15em 1em;
+ margin: 0.2em 1em 0.2em 1em;
+ cursor: pointer;
+ transition: background-color 0.33s, color 0.33s, font-weight 0.33s, transform 0.33s;
+ text-shadow: 0 0 10px rgba(255, 255, 255, 1);
+ border-radius: 5px;
+ //background: rgba(0, 0, 0, 0.05);
+}
+
+.button:hover {
+ font-weight: bold;
+ color: #005caf;
+ transform: scale(1.1, 1.1);
+ text-shadow: 0 0 15px rgba(0, 0, 0, 0);
+ background: rgba(255, 255, 255, 0.85);
+}
diff --git a/packages/webgal/src/Core/gameScripts/getUserInput/index.tsx b/packages/webgal/src/Core/gameScripts/getUserInput/index.tsx
new file mode 100644
index 000000000..68fb584bd
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/getUserInput/index.tsx
@@ -0,0 +1,72 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { changeScene } from '@/Core/controller/scene/changeScene';
+import { jmp } from '@/Core/gameScripts/label/jmp';
+import ReactDOM from 'react-dom';
+import React from 'react';
+import styles from './getUserInput.module.scss';
+import { webgalStore } from '@/store/store';
+import { textFont } from '@/store/userDataInterface';
+import { PerformController } from '@/Core/Modules/perform/performController';
+import { useSEByWebgalStore } from '@/hooks/useSoundEffect';
+import { WebGAL } from '@/Core/WebGAL';
+import { getSentenceArgByKey } from '@/Core/util/getSentenceArg';
+import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
+import { setStageVar } from '@/store/stageReducer';
+
+/**
+ * 显示选择枝
+ * @param sentence
+ */
+export const getUserInput = (sentence: ISentence): IPerform => {
+ const varKey = sentence.content.toString().trim();
+ const titleFromArgs = getSentenceArgByKey(sentence, 'title');
+ const title = (titleFromArgs === 0 ? 'Please Input' : titleFromArgs) ?? 'Please Input';
+ const buttonTextFromArgs = getSentenceArgByKey(sentence, 'buttonText');
+ const buttonText = (buttonTextFromArgs === 0 ? 'OK' : buttonTextFromArgs) ?? 'OK';
+ const fontFamily = webgalStore.getState().userData.optionData.textboxFont;
+ const font = fontFamily === textFont.song ? '"思源宋体", serif' : '"WebgalUI", serif';
+ const { playSeEnter, playSeClick } = useSEByWebgalStore();
+ const chooseElements = (
+
+
+
{title}
+
+
{
+ const userInput: HTMLInputElement = document.getElementById('user-input') as HTMLInputElement;
+ if (userInput) {
+ webgalStore.dispatch(
+ setStageVar({ key: varKey, value: (userInput?.value ?? '') === '' ? ' ' : userInput?.value ?? '' }),
+ );
+ }
+ playSeClick();
+ WebGAL.gameplay.performController.unmountPerform('userInput');
+ nextSentence();
+ }}
+ className={styles.button}
+ >
+ {buttonText}
+
+
+
+ );
+ // eslint-disable-next-line react/no-deprecated
+ ReactDOM.render(
+ {chooseElements}
,
+ document.getElementById('chooseContainer'),
+ );
+ return {
+ performName: 'userInput',
+ duration: 1000 * 60 * 60 * 24,
+ isHoldOn: false,
+ stopFunction: () => {
+ // eslint-disable-next-line react/no-deprecated
+ ReactDOM.render(
, document.getElementById('chooseContainer'));
+ },
+ blockingNext: () => true,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/intro.tsx b/packages/webgal/src/Core/gameScripts/intro.tsx
new file mode 100644
index 000000000..0132ba0ec
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/intro.tsx
@@ -0,0 +1,215 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import styles from '@/Stage/FullScreenPerform/fullScreenPerform.module.scss';
+import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
+import { PerformController } from '@/Core/Modules/perform/performController';
+import { logger } from '@/Core/util/logger';
+import { WebGAL } from '@/Core/WebGAL';
+import { replace } from 'lodash';
+import useEscape from '@/hooks/useEscape';
+/**
+ * 显示一小段黑屏演示
+ * @param sentence
+ */
+export const intro = (sentence: ISentence): IPerform => {
+ /**
+ * intro 内部控制
+ */
+
+ const performName = `introPerform${Math.random().toString()}`;
+ let fontSize: string | undefined;
+ let backgroundColor: any = 'rgba(0, 0, 0, 1)';
+ let color: any = 'rgba(255, 255, 255, 1)';
+ const animationClass: any = (type: string, length = 0) => {
+ switch (type) {
+ case 'fadeIn':
+ return styles.fadeIn;
+ case 'slideIn':
+ return styles.slideIn;
+ case 'typingEffect':
+ return `${styles.typingEffect} ${length}`;
+ case 'pixelateEffect':
+ return styles.pixelateEffect;
+ case 'revealAnimation':
+ return styles.revealAnimation;
+ default:
+ return styles.fadeIn;
+ }
+ };
+ let chosenAnimationClass = styles.fadeIn;
+ let delayTime = 1500;
+ let isHold = false;
+ let isUserForward = false;
+
+ for (const e of sentence.args) {
+ if (e.key === 'backgroundColor') {
+ backgroundColor = e.value || 'rgba(0, 0, 0, 1)';
+ }
+ if (e.key === 'fontColor') {
+ color = e.value || 'rgba(255, 255, 255, 1)';
+ }
+ if (e.key === 'fontSize') {
+ switch (e.value) {
+ case 'small':
+ fontSize = '280%';
+ break;
+ case 'medium':
+ fontSize = '350%';
+ break;
+ case 'large':
+ fontSize = '420%';
+ break;
+ }
+ }
+ if (e.key === 'animation') {
+ chosenAnimationClass = animationClass(e.value);
+ }
+ if (e.key === 'delayTime') {
+ const parsedValue = parseInt(e.value.toString(), 10);
+ delayTime = isNaN(parsedValue) ? delayTime : parsedValue;
+ }
+ if (e.key === 'hold') {
+ if (e.value === true) {
+ isHold = true;
+ }
+ }
+ if (e.key === 'userForward') {
+ // 用户手动控制向前步进
+ if (e.value === true) {
+ isUserForward = true;
+ isHold = true; // 用户手动控制向前步进,所以必须是 hold
+ delayTime = 99999999; // 设置一个很大的延迟,这样自然就看起来不自动继续了
+ }
+ }
+ }
+
+ const introContainerStyle = {
+ background: backgroundColor,
+ color: color,
+ fontSize: fontSize || '350%',
+ width: '100%',
+ height: '100%',
+ };
+ const introArray: Array = sentence.content.split(/(? useEscape(val));
+
+ let endWait = 1000;
+ let baseDuration = endWait + delayTime * introArray.length;
+ const duration = isHold ? 1000 * 60 * 60 * 24 : 1000 + delayTime * introArray.length;
+ let isBlocking = true;
+ let setBlockingStateTimeout = setTimeout(() => {
+ isBlocking = false;
+ }, baseDuration);
+
+ let timeout = setTimeout(() => {});
+ const toNextIntroElement = () => {
+ const introContainer = document.getElementById('introContainer');
+ // 由于用户操作,相当于时间向前推进,这时候更新这个演出的预计完成时间
+ baseDuration -= delayTime;
+ clearTimeout(setBlockingStateTimeout);
+ setBlockingStateTimeout = setTimeout(() => {
+ isBlocking = false;
+ }, baseDuration);
+ if (introContainer) {
+ const children = introContainer.childNodes[0].childNodes[0].childNodes as any;
+ const len = children.length;
+ if (isUserForward) {
+ let isEnd = true;
+ for (const node of children) {
+ // 当前语句的延迟显示时间
+ const currentDelay = Number(node.style.animationDelay.split('ms')[0]);
+ // 当前语句还没有显示,降低显示延迟,因为现在时间因为用户操作,相当于向前推进了
+ if (currentDelay > 0) {
+ isEnd = false;
+ // 用 Animation API 操作,浏览器版本太低就无办法了
+ const nodeAnimations = node.getAnimations();
+ node.style.animationDelay = '0ms ';
+ for (const ani of nodeAnimations) {
+ ani.currentTime = 0;
+ ani.play();
+ }
+ }
+ }
+ if (isEnd) {
+ clearTimeout(timeout);
+ clearTimeout(setBlockingStateTimeout);
+ WebGAL.gameplay.performController.unmountPerform(performName);
+ }
+ return;
+ }
+ children.forEach((node: HTMLDivElement, index: number) => {
+ // 当前语句的延迟显示时间
+ const currentDelay = Number(node.style.animationDelay.split('ms')[0]);
+ // 当前语句还没有显示,降低显示延迟,因为现在时间因为用户操作,相当于向前推进了
+ if (currentDelay > 0) {
+ node.style.animationDelay = `${currentDelay - delayTime}ms`;
+ }
+ // 最后一个元素了
+ if (index === len - 1) {
+ // 并且已经完全显示了,这时候进行下一步
+ if (currentDelay === 0) {
+ clearTimeout(timeout);
+ WebGAL.gameplay.performController.unmountPerform(performName);
+ // 卸载函数发生在 nextSentence 生效前,所以不需要做下一行的操作。
+ // setTimeout(nextSentence, 0);
+ } else {
+ // 还没有完全显示,但是因为时间的推进,要提前完成演出,更新用于结束演出的计时器
+ clearTimeout(timeout);
+ // 如果 Hold 了,自然不要自动结束
+ if (!isHold) {
+ timeout = setTimeout(() => {
+ WebGAL.gameplay.performController.unmountPerform(performName);
+ }, baseDuration);
+ }
+ }
+ }
+ });
+ }
+ };
+
+ /**
+ * 接受 next 事件
+ */
+ WebGAL.events.userInteractNext.on(toNextIntroElement);
+
+ const showIntro = introArray.map((e, i) => (
+
+ {e}
+ {e === '' ? '\u00a0' : ''}
+
+ ));
+ const intro = (
+
+ );
+ // eslint-disable-next-line react/no-deprecated
+ ReactDOM.render(intro, document.getElementById('introContainer'));
+ const introContainer = document.getElementById('introContainer');
+
+ if (introContainer) {
+ introContainer.style.display = 'block';
+ }
+
+ return {
+ performName,
+ duration,
+ isHoldOn: false,
+ stopFunction: () => {
+ const introContainer = document.getElementById('introContainer');
+ if (introContainer) {
+ introContainer.style.display = 'none';
+ }
+ WebGAL.events.userInteractNext.off(toNextIntroElement);
+ },
+ blockingNext: () => isBlocking,
+ blockingAuto: () => isBlocking,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ goNextWhenOver: true,
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/jumpLabel.ts b/packages/webgal/src/Core/gameScripts/jumpLabel.ts
new file mode 100644
index 000000000..9baaf2b4c
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/jumpLabel.ts
@@ -0,0 +1,20 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { jmp } from '@/Core/gameScripts/label/jmp';
+
+/**
+ * 跳转到指定标签
+ * @param sentence
+ */
+export const jumpLabel = (sentence: ISentence): IPerform => {
+ jmp(sentence.content);
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/label/index.ts b/packages/webgal/src/Core/gameScripts/label/index.ts
new file mode 100644
index 000000000..c0e2f4b99
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/label/index.ts
@@ -0,0 +1,18 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+
+/**
+ * 标签代码,什么也不做
+ * @param sentence
+ */
+export const label = (sentence: ISentence): IPerform => {
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/label/jmp.ts b/packages/webgal/src/Core/gameScripts/label/jmp.ts
new file mode 100644
index 000000000..0e3f4d1d7
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/label/jmp.ts
@@ -0,0 +1,17 @@
+import { commandType } from '@/Core/controller/scene/sceneInterface';
+import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+export const jmp = (labelName: string) => {
+ // 在当前场景中找到指定的标签。
+ const currentLine = WebGAL.sceneManager.sceneData.currentSentenceId;
+ let result = currentLine;
+ WebGAL.sceneManager.sceneData.currentScene.sentenceList.forEach((sentence, index) => {
+ if (sentence.command === commandType.label && sentence.content === labelName && index !== currentLine) {
+ result = index;
+ }
+ });
+ WebGAL.sceneManager.sceneData.currentSentenceId = result;
+ setTimeout(nextSentence, 1);
+};
diff --git a/packages/webgal/src/Core/gameScripts/miniAvatar.ts b/packages/webgal/src/Core/gameScripts/miniAvatar.ts
new file mode 100644
index 000000000..fb5a40351
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/miniAvatar.ts
@@ -0,0 +1,25 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { webgalStore } from '@/store/store';
+import { setStage } from '@/store/stageReducer';
+
+/**
+ * 显示小头像
+ * @param sentence
+ */
+export const miniAvatar = (sentence: ISentence): IPerform => {
+ let content = sentence.content;
+ if (sentence.content === 'none' || sentence.content === '') {
+ content = '';
+ }
+ webgalStore.dispatch(setStage({ key: 'miniAvatar', value: content }));
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: true,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/pixi/index.ts b/packages/webgal/src/Core/gameScripts/pixi/index.ts
new file mode 100644
index 000000000..57ca15f14
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/pixi/index.ts
@@ -0,0 +1,45 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { logger } from '@/Core/util/logger';
+import { IResult, call } from '../../util/pixiPerformManager/pixiPerformManager';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 运行一段pixi演出
+ * @param sentence
+ */
+export const pixi = (sentence: ISentence): IPerform => {
+ const pixiPerformName = 'PixiPerform' + sentence.content;
+ WebGAL.gameplay.performController.performList.forEach((e) => {
+ if (e.performName === pixiPerformName) {
+ return {
+ performName: 'none',
+ duration: 0,
+ isOver: false,
+ isHoldOn: true,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => false,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+ }
+ });
+ const res: IResult = call(sentence.content);
+ const { container, tickerKey } = res;
+
+ return {
+ performName: pixiPerformName,
+ duration: 0,
+ isHoldOn: true,
+ stopFunction: () => {
+ logger.warn('现在正在卸载pixi演出');
+ container.destroy({ texture: true, baseTexture: true });
+ WebGAL.gameplay.pixiStage?.effectsContainer.removeChild(container);
+ WebGAL.gameplay.pixiStage?.removeAnimation(tickerKey);
+ },
+ blockingNext: () => false,
+ blockingAuto: () => false,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/pixi/performs/cherryBlossoms.ts b/packages/webgal/src/Core/gameScripts/pixi/performs/cherryBlossoms.ts
new file mode 100644
index 000000000..e8f74811a
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/pixi/performs/cherryBlossoms.ts
@@ -0,0 +1,90 @@
+import * as PIXI from 'pixi.js';
+import { registerPerform } from '@/Core/util/pixiPerformManager/pixiPerformManager';
+
+import { WebGAL } from '@/Core/WebGAL';
+import { SCREEN_CONSTANTS } from '@/Core/util/constants';
+
+const pixicherryBlossoms = (cherryBlossomsSpeed: number) => {
+ // アニメーション パラメータ
+ // 倍率を設定
+ // 动画参数
+ // 设置缩放的系数
+ const scalePreset = 0.15;
+
+ const effectsContainer = WebGAL!.gameplay!.pixiStage!.effectsContainer;
+ const app = WebGAL!.gameplay!.pixiStage!.currentApp!;
+ const container = new PIXI.Container();
+ effectsContainer.addChild(container);
+ // テクスチャを作成
+ // 创建纹理
+ const texture = PIXI.Texture.from('./game/tex/cherryBlossoms.png');
+ // コンテナを中央に移動
+ // 将容器移到中心
+ container.x = app.screen.width / 2;
+ container.y = app.screen.height / 2;
+ container.pivot.x = container.width / 2;
+ container.pivot.y = container.height / 2;
+ // ズームを調整
+ // 调整缩放
+ container.scale.x = 1;
+ container.scale.y = 1;
+ // container.rotation = -0.2;
+ const bunnyList: any = [];
+ // アニメーションの更新を監視
+ // 监听动画更新
+ function tickerFn(delta: number) {
+ // 桜の位置を制御するために使用される長さと幅を取得します
+ // 获取长宽,用于控制花出现位置
+ const stageWidth = SCREEN_CONSTANTS.width;
+ const stageHeight = SCREEN_CONSTANTS.height;
+ // オブジェクトを作成
+ // 创建对象
+ const bunny = new PIXI.Sprite(texture);
+ let scaleRand = 0.25;
+
+ bunny.scale.x = scalePreset * scaleRand;
+ bunny.scale.y = scalePreset * scaleRand;
+ // アンカーポイントを設定
+ // 设置锚点
+ bunny.anchor.set(0.5);
+ // ランダムな桜の位置
+ // 随机花位置
+ bunny.x = Math.random() * stageWidth - 0.5 * stageWidth;
+ bunny.y = 0 - 0.5 * stageHeight;
+ // @ts-ignore
+ bunny['dropSpeed'] = Math.random() * 5;
+ // @ts-ignore
+ bunny['acc'] = Math.random();
+ container.addChild(bunny);
+ bunnyList.push(bunny);
+
+ let count = 0;
+ for (const e of bunnyList) {
+ count++;
+ const randomNumber = Math.random();
+ e['dropSpeed'] = e['acc'] * 0.01 + e['dropSpeed'];
+ e.y += delta * cherryBlossomsSpeed * e['dropSpeed'] * 0.3 + 0.7;
+ const addX = count % 2 === 0;
+ if (addX) {
+ e.x += delta * randomNumber * 0.5;
+ e.rotation += delta * randomNumber * 0.03;
+ } else {
+ e.x -= delta * randomNumber * 0.5;
+ e.rotation -= delta * randomNumber * 0.03;
+ }
+ }
+ // 同じ画面上の桜の数を制御します
+ // 控制同屏花数
+ if (bunnyList.length >= 200) {
+ bunnyList.shift()?.destroy();
+ container.removeChild(container.children[0]);
+ }
+ }
+ WebGAL!.gameplay!.pixiStage!.registerAnimation(
+ { setStartState: () => {}, setEndState: () => {}, tickerFunc: tickerFn },
+ 'cherryBlossoms-Ticker',
+ );
+ return { container, tickerKey: 'cherryBlossoms-Ticker' };
+};
+
+registerPerform('cherryBlossoms', () => pixicherryBlossoms(3));
diff --git a/packages/webgal/src/Core/gameScripts/pixi/performs/rain.ts b/packages/webgal/src/Core/gameScripts/pixi/performs/rain.ts
new file mode 100644
index 000000000..81a86365c
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/pixi/performs/rain.ts
@@ -0,0 +1,84 @@
+import * as PIXI from 'pixi.js';
+import { registerPerform } from '@/Core/util/pixiPerformManager/pixiPerformManager';
+
+import { WebGAL } from '@/Core/WebGAL';
+import { SCREEN_CONSTANTS } from '@/Core/util/constants';
+
+const pixiRain = (rainSpeed: number, number: number) => {
+ // 动画参数
+ // 设置缩放的系数
+ const scalePreset = 0.48;
+
+ const effectsContainer = WebGAL.gameplay.pixiStage!.effectsContainer!;
+ const app = WebGAL.gameplay.pixiStage!.currentApp!;
+ const container = new PIXI.Container();
+ effectsContainer.addChild(container);
+ // 创建纹理
+ const texture = PIXI.Texture.from('./game/tex/raindrop.png');
+ // 将容器移到中心
+ container.x = app.screen.width / 2;
+ container.y = app.screen.height / 2;
+ container.pivot.x = container.width / 2;
+ container.pivot.y = container.height / 2;
+ // 调整缩放
+ container.scale.x = 1;
+ container.scale.y = 1;
+ // container.rotation = -0.2;
+ const bunnyList: PIXI.Sprite[] = [];
+ // 监听动画更新
+ function ticker(delta: number) {
+ // 获取长宽,用于控制雪花出现位置
+ const stageWidth = SCREEN_CONSTANTS.width;
+ const stageHeight = SCREEN_CONSTANTS.height;
+ for (let i = 0; i < number; i++) {
+ // 创建对象
+ const bunny = new PIXI.Sprite(texture);
+ // 随机雨点大小
+ let scaleRand = Math.random();
+ if (scaleRand <= 0.5) {
+ scaleRand = 0.5;
+ }
+ bunny.scale.x = scalePreset * scaleRand;
+ bunny.scale.y = scalePreset * scaleRand;
+ // 设置锚点
+ bunny.anchor.set(0.5);
+ // 随机雪花位置
+ bunny.x = Math.random() * stageWidth - 0.5 * stageWidth;
+ bunny.y = 0 - 0.5 * stageHeight;
+ // @ts-ignore
+ bunny['dropSpeed'] = Math.random() * 2;
+ // @ts-ignore
+ bunny['acc'] = Math.random();
+ bunny['alpha'] = Math.random();
+ if (bunny['alpha'] >= 0.5) {
+ bunny['alpha'] = 0.5;
+ }
+ if (bunny['alpha'] <= 0.2) {
+ bunny['alpha'] = 0.2;
+ }
+ container.addChild(bunny);
+ // 控制每片雨点
+ bunnyList.push(bunny);
+
+ // 控制同屏雨点数
+ if (bunnyList.length >= 2500) {
+ bunnyList.shift()?.destroy();
+ container.removeChild(container.children[0]);
+ }
+ }
+ // 雨点落下
+ for (const e of bunnyList) {
+ // @ts-ignore
+ e['dropSpeed'] = e['acc'] * 0.01 + e['dropSpeed'];
+ // @ts-ignore
+ e.y += delta * rainSpeed * e['dropSpeed'] * 1.1 + 3;
+ }
+ }
+ WebGAL.gameplay.pixiStage?.registerAnimation(
+ { setStartState: () => {}, setEndState: () => {}, tickerFunc: ticker },
+ 'rain-Ticker',
+ );
+ return { container, tickerKey: 'rain-Ticker' };
+};
+
+registerPerform('rain', () => pixiRain(6, 10));
diff --git a/packages/webgal/src/Core/gameScripts/pixi/performs/snow.ts b/packages/webgal/src/Core/gameScripts/pixi/performs/snow.ts
new file mode 100644
index 000000000..1c54db7ee
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/pixi/performs/snow.ts
@@ -0,0 +1,82 @@
+import * as PIXI from 'pixi.js';
+import { registerPerform } from '@/Core/util/pixiPerformManager/pixiPerformManager';
+
+import { WebGAL } from '@/Core/WebGAL';
+import { SCREEN_CONSTANTS } from '@/Core/util/constants';
+
+const pixiSnow = (snowSpeed: number) => {
+ // 动画参数
+ // 设置缩放的系数
+ const scalePreset = 0.144;
+
+ const effectsContainer = WebGAL.gameplay.pixiStage!.effectsContainer!;
+ const app = WebGAL.gameplay.pixiStage!.currentApp!;
+ const container = new PIXI.Container();
+ effectsContainer.addChild(container);
+ // 创建纹理
+ const texture = PIXI.Texture.from('./game/tex/snowFlake_min.png');
+ // 将容器移到中心
+ container.x = app.screen.width / 2;
+ container.y = app.screen.height / 2;
+ container.pivot.x = container.width / 2;
+ container.pivot.y = container.height / 2;
+ // 调整缩放
+ container.scale.x = 1;
+ container.scale.y = 1;
+ // container.rotation = -0.2;
+ const bunnyList: any = [];
+ // 监听动画更新
+ function tickerFn(delta: number) {
+ // 获取长宽,用于控制雪花出现位置
+ const stageWidth = SCREEN_CONSTANTS.width;
+ const stageHeight = SCREEN_CONSTANTS.height;
+ // 创建对象
+ const bunny = new PIXI.Sprite(texture);
+ // 随机雪花大小
+ let scaleRand = Math.random();
+ if (scaleRand <= 0.5) {
+ scaleRand = 0.5;
+ }
+ bunny.scale.x = scalePreset * scaleRand;
+ bunny.scale.y = scalePreset * scaleRand;
+ // 设置锚点
+ bunny.anchor.set(0.5);
+ // 随机雪花位置
+ bunny.x = Math.random() * stageWidth - 0.5 * stageWidth;
+ bunny.y = 0 - 0.5 * stageHeight;
+ // @ts-ignore
+ bunny['dropSpeed'] = Math.random() * 2;
+ // @ts-ignore
+ bunny['acc'] = Math.random();
+ container.addChild(bunny);
+ // 控制每片雪花
+ bunnyList.push(bunny);
+ let count = 0; // 用于判断雪花往左还是往右飘,是2的倍数则往左
+ for (const e of bunnyList) {
+ count++;
+ const randomNumber = Math.random();
+ e['dropSpeed'] = e['acc'] * 0.01 + e['dropSpeed'];
+ e.y += delta * snowSpeed * e['dropSpeed'] * 0.3 + 0.7;
+ const addX = count % 2 === 0;
+ if (addX) {
+ e.x += delta * randomNumber * 0.5;
+ e.rotation += delta * randomNumber * 0.03;
+ } else {
+ e.x -= delta * randomNumber * 0.5;
+ e.rotation -= delta * randomNumber * 0.03;
+ }
+ }
+ // 控制同屏雪花数
+ if (bunnyList.length >= 500) {
+ bunnyList.shift()?.destroy();
+ container.removeChild(container.children[0]);
+ }
+ }
+ WebGAL.gameplay.pixiStage?.registerAnimation(
+ { setStartState: () => {}, setEndState: () => {}, tickerFunc: tickerFn },
+ 'snow-Ticker',
+ );
+ return { container, tickerKey: 'snow-Ticker' };
+};
+
+registerPerform('snow', () => pixiSnow(3));
diff --git a/packages/webgal/src/Core/gameScripts/pixi/pixiInit.ts b/packages/webgal/src/Core/gameScripts/pixi/pixiInit.ts
new file mode 100644
index 000000000..60c7a5aef
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/pixi/pixiInit.ts
@@ -0,0 +1,46 @@
+import { commandType, ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { logger } from '@/Core/util/logger';
+import { webgalStore } from '@/store/store';
+import { resetStageState, stageActions } from '@/store/stageReducer';
+import cloneDeep from 'lodash/cloneDeep';
+
+import { WebGAL } from '@/Core/WebGAL';
+import { IRunPerform } from '@/store/stageInterface';
+
+/**
+ * 初始化pixi
+ * @param sentence
+ */
+export const pixiInit = (sentence: ISentence): IPerform => {
+ WebGAL.gameplay.performController.performList.forEach((e) => {
+ if (e.performName.match(/PixiPerform/)) {
+ logger.warn('pixi 被脚本重新初始化', e.performName);
+ /**
+ * 卸载演出
+ */
+ for (let i = 0; i < WebGAL.gameplay.performController.performList.length; i++) {
+ const e2 = WebGAL.gameplay.performController.performList[i];
+ if (e2.performName === e.performName) {
+ e2.stopFunction();
+ clearTimeout(e2.stopTimeout as unknown as number);
+ WebGAL.gameplay.performController.performList.splice(i, 1);
+ i--;
+ }
+ }
+ /**
+ * 从状态表里清除演出
+ */
+ webgalStore.dispatch(stageActions.removeAllPixiPerforms());
+ }
+ });
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/playEffect.ts b/packages/webgal/src/Core/gameScripts/playEffect.ts
new file mode 100644
index 000000000..4fa726b91
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/playEffect.ts
@@ -0,0 +1,107 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { logger } from '@/Core/util/logger';
+import { RootState, webgalStore } from '@/store/store';
+import { getSentenceArgByKey } from '@/Core/util/getSentenceArg';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { useSelector } from 'react-redux';
+import { WebGAL } from '@/Core/WebGAL';
+import { WEBGAL_NONE } from '@/Core/constants';
+
+/**
+ * 播放一段效果音
+ * @param sentence 语句
+ */
+export const playEffect = (sentence: ISentence): IPerform => {
+ logger.debug('play SE');
+ // 如果有ID,这里被覆写,一般用于循环的情况
+ // 有循环参数且有 ID,就循环
+ let performInitName = 'effect-sound';
+ // 清除先前的效果音
+ WebGAL.gameplay.performController.unmountPerform(performInitName, true);
+ let url = sentence.content;
+ let isLoop = false;
+ // 清除带 id 的效果音
+ if (getSentenceArgByKey(sentence, 'id')) {
+ const id = getSentenceArgByKey(sentence, 'id')?.toString() ?? '';
+ performInitName = `effect-sound-${id}`;
+ WebGAL.gameplay.performController.unmountPerform(performInitName, true);
+ isLoop = true;
+ }
+ let isOver = false;
+ if (!url || url === WEBGAL_NONE) {
+ return {
+ performName: WEBGAL_NONE,
+ duration: 0,
+ isHoldOn: false,
+ blockingAuto(): boolean {
+ return false;
+ },
+ blockingNext(): boolean {
+ return false;
+ },
+ stopFunction(): void {},
+ stopTimeout: undefined,
+ };
+ }
+ return {
+ performName: 'none',
+ blockingAuto(): boolean {
+ return false;
+ },
+ blockingNext(): boolean {
+ return false;
+ },
+ isHoldOn: false,
+ stopFunction(): void {},
+ stopTimeout: undefined,
+
+ duration: 1000 * 60 * 60,
+ arrangePerformPromise: new Promise((resolve) => {
+ // 播放效果音
+ setTimeout(() => {
+ const volumeArg = getSentenceArgByKey(sentence, 'volume');
+ let seElement = document.createElement('audio');
+ seElement.src = url;
+ if (isLoop) {
+ seElement.loop = true;
+ }
+ const userDataState = webgalStore.getState().userData;
+ const mainVol = userDataState.optionData.volumeMain;
+ // Work when volumeArg is a number between 0 and 100
+ const volume = typeof volumeArg === 'number' && volumeArg >= 0 && volumeArg <= 100 ? volumeArg : 100;
+ const seVol = mainVol * 0.01 * (userDataState.optionData?.seVolume ?? 100) * 0.01 * volume * 0.01;
+ seElement.volume = seVol;
+ seElement.currentTime = 0;
+ const perform: IPerform = {
+ performName: performInitName,
+ duration: 1000 * 60 * 60,
+ isHoldOn: isLoop,
+ skipNextCollect: true,
+ stopFunction: () => {
+ // 演出已经结束了,所以不用播放效果音了
+ seElement.pause();
+ seElement.remove();
+ },
+ blockingNext: () => false,
+ blockingAuto: () => {
+ // loop 的话就不 block auto
+ if (isLoop) return false;
+ return !isOver;
+ },
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+ resolve(perform);
+ seElement?.play();
+ seElement.onended = () => {
+ for (const e of WebGAL.gameplay.performController.performList) {
+ if (e.performName === performInitName) {
+ isOver = true;
+ e.stopFunction();
+ WebGAL.gameplay.performController.unmountPerform(e.performName);
+ }
+ }
+ };
+ }, 1);
+ }),
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/playVideo.tsx b/packages/webgal/src/Core/gameScripts/playVideo.tsx
new file mode 100644
index 000000000..f57e41fdd
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/playVideo.tsx
@@ -0,0 +1,118 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import styles from '@/Stage/FullScreenPerform/fullScreenPerform.module.scss';
+import { webgalStore } from '@/store/store';
+import { getRandomPerformName, PerformController } from '@/Core/Modules/perform/performController';
+import { getSentenceArgByKey } from '@/Core/util/getSentenceArg';
+import { WebGAL } from '@/Core/WebGAL';
+/**
+ * 播放一段视频 * @param sentence
+ */
+export const playVideo = (sentence: ISentence): IPerform => {
+ const userDataState = webgalStore.getState().userData;
+ const mainVol = userDataState.optionData.volumeMain;
+ const vocalVol = mainVol * 0.01 * userDataState.optionData.vocalVolume * 0.01;
+ const bgmVol = mainVol * 0.01 * userDataState.optionData.bgmVolume * 0.01;
+ const performInitName: string = getRandomPerformName();
+
+ let blockingNext = getSentenceArgByKey(sentence, 'skipOff');
+ let blockingNextFlag = false;
+ if (blockingNext) {
+ blockingNextFlag = true;
+ }
+
+ // eslint-disable-next-line react/no-deprecated
+ ReactDOM.render(
+
+
+
,
+ document.getElementById('videoContainer'),
+ );
+ let isOver = false;
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => blockingNextFlag,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ arrangePerformPromise: new Promise((resolve) => {
+ /**
+ * 启动视频播放
+ */
+ setTimeout(() => {
+ let VocalControl: any = document.getElementById('playVideoElement');
+ if (VocalControl !== null) {
+ VocalControl.currentTime = 0;
+ VocalControl.volume = bgmVol;
+ const endPerform = () => {
+ for (const e of WebGAL.gameplay.performController.performList) {
+ if (e.performName === performInitName) {
+ isOver = true;
+ e.stopFunction();
+ WebGAL.gameplay.performController.unmountPerform(e.performName);
+ }
+ }
+ };
+ const skipVideo = () => {
+ endPerform();
+ };
+ // 双击可跳过视频
+ WebGAL.events.fullscreenDbClick.on(skipVideo);
+ // 播放并作为一个特别演出加入
+ const perform = {
+ performName: performInitName,
+ duration: 1000 * 60 * 60,
+ isOver: false,
+ isHoldOn: false,
+ stopFunction: () => {
+ WebGAL.events.fullscreenDbClick.off(skipVideo);
+ /**
+ * 恢复音量
+ */
+ const bgmElement: any = document.getElementById('currentBgm');
+ if (bgmElement) {
+ bgmElement.volume = bgmVol.toString();
+ }
+ const vocalElement: any = document.getElementById('currentVocal');
+ if (bgmElement) {
+ vocalElement.volume = vocalVol.toString();
+ }
+ // eslint-disable-next-line react/no-deprecated
+ ReactDOM.render(
, document.getElementById('videoContainer'));
+ },
+ blockingNext: () => blockingNextFlag,
+ blockingAuto: () => {
+ return !isOver;
+ },
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ goNextWhenOver: true,
+ };
+ resolve(perform);
+ /**
+ * 把bgm和语音的音量设为0
+ */
+ const vocalVol2 = 0;
+ const bgmVol2 = 0;
+ const bgmElement: any = document.getElementById('currentBgm');
+ if (bgmElement) {
+ bgmElement.volume = bgmVol2.toString();
+ }
+ const vocalElement: any = document.getElementById('currentVocal');
+ if (bgmElement) {
+ vocalElement.volume = vocalVol2.toString();
+ }
+
+ VocalControl?.play();
+
+ VocalControl.onended = () => {
+ endPerform();
+ };
+ }
+ }, 1);
+ }),
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/say.ts b/packages/webgal/src/Core/gameScripts/say.ts
new file mode 100644
index 000000000..95fb97cfa
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/say.ts
@@ -0,0 +1,173 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { playVocal } from './vocal';
+import { webgalStore } from '@/store/store';
+import { setStage } from '@/store/stageReducer';
+import { useTextDelay } from '@/hooks/useTextOptions';
+import { getRandomPerformName, PerformController } from '@/Core/Modules/perform/performController';
+import { getSentenceArgByKey } from '@/Core/util/getSentenceArg';
+import { textSize, voiceOption } from '@/store/userDataInterface';
+import { WebGAL } from '@/Core/WebGAL';
+import { compileSentence } from '@/Stage/TextBox/TextBox';
+import { performMouthAnimation } from '@/Core/gameScripts/vocal/vocalAnimation';
+import { match } from '@/Core/util/match';
+
+/**
+ * 进行普通对话的显示
+ * @param sentence 语句
+ * @return {IPerform} 执行的演出
+ */
+export const say = (sentence: ISentence): IPerform => {
+ const stageState = webgalStore.getState().stage;
+ const userDataState = webgalStore.getState().userData;
+ const dispatch = webgalStore.dispatch;
+ let dialogKey = Math.random().toString(); // 生成一个随机的key
+ let dialogToShow = sentence.content; // 获取对话内容
+ if (dialogToShow) {
+ dialogToShow = String(dialogToShow).replace(/ /g, '\u00a0'); // 替换空格
+ }
+ const isConcat = getSentenceArgByKey(sentence, 'concat'); // 是否是继承语句
+ const isNotend = getSentenceArgByKey(sentence, 'notend') as boolean; // 是否有 notend 参数
+ const speaker = getSentenceArgByKey(sentence, 'speaker'); // 获取说话者
+ const clear = getSentenceArgByKey(sentence, 'clear'); // 是否清除说话者
+ const vocal = getSentenceArgByKey(sentence, 'vocal'); // 是否播放语音
+
+ // 如果是concat,那么就继承上一句的key,并且继承上一句对话。
+ if (isConcat) {
+ dialogKey = stageState.currentDialogKey;
+ dialogToShow = stageState.showText + dialogToShow;
+ dispatch(setStage({ key: 'currentConcatDialogPrev', value: stageState.showText }));
+ } else {
+ dispatch(setStage({ key: 'currentConcatDialogPrev', value: '' }));
+ }
+
+ // 设置文本显示
+ dispatch(setStage({ key: 'showText', value: dialogToShow }));
+ dispatch(setStage({ key: 'vocal', value: '' }));
+
+ // 清除语音
+ if (!(userDataState.optionData.voiceInterruption === voiceOption.no && vocal === null)) {
+ // 只有开关设置为不中断,并且没有语音的时候,才需要不中断
+ dispatch(setStage({ key: 'playVocal', value: '' }));
+ WebGAL.gameplay.performController.unmountPerform('vocal-play', true);
+ }
+ // 设置key
+ dispatch(setStage({ key: 'currentDialogKey', value: dialogKey }));
+ // 计算延迟
+ const textDelay = useTextDelay(userDataState.optionData.textSpeed);
+ // 本句延迟
+ const textNodes = compileSentence(sentence.content, 3);
+ const len = textNodes.reduce((prev, curr) => prev + curr.length, 0);
+ const sentenceDelay = textDelay * len;
+
+ for (const e of sentence.args) {
+ if (e.key === 'fontSize') {
+ switch (e.value) {
+ case 'default':
+ dispatch(setStage({ key: 'showTextSize', value: -1 }));
+ break;
+ case 'small':
+ dispatch(setStage({ key: 'showTextSize', value: textSize.small }));
+ break;
+ case 'medium':
+ dispatch(setStage({ key: 'showTextSize', value: textSize.medium }));
+ break;
+ case 'large':
+ dispatch(setStage({ key: 'showTextSize', value: textSize.large }));
+ break;
+ }
+ }
+ }
+
+ // 设置显示的角色名称
+ let showName: string | number | boolean = stageState.showName; // 先默认继承
+ if (speaker !== null) {
+ showName = speaker;
+ }
+ if (clear) {
+ showName = '';
+ }
+ dispatch(setStage({ key: 'showName', value: showName }));
+
+ // 模拟说话
+ let performSimulateVocalTimeout: ReturnType | null = null;
+ let performSimulateVocalDelay = 0;
+ let pos = '';
+ let key = '';
+ for (const e of sentence.args) {
+ if (e.value === true) {
+ match(e.key)
+ .with('left', () => {
+ pos = 'left';
+ })
+ .with('right', () => {
+ pos = 'right';
+ })
+ .endsWith('center', () => {
+ pos = 'center';
+ });
+ }
+ if (e.key === 'figureId') {
+ key = `${e.value.toString()}`;
+ }
+ }
+ let audioLevel = 80;
+ const performSimulateVocal = (end = false) => {
+ let nextAudioLevel = audioLevel + (Math.random() * 60 - 30); // 在 -30 到 +30 之间波动
+ // 确保波动幅度不小于 5
+ if (Math.abs(nextAudioLevel - audioLevel) < 5) {
+ nextAudioLevel = audioLevel + Math.sign(nextAudioLevel - audioLevel) * 5;
+ }
+ // 确保结果在 25 到 100 之间
+ audioLevel = Math.max(15, Math.min(nextAudioLevel, 100));
+ const currentStageState = webgalStore.getState().stage;
+ const figureAssociatedAnimation = currentStageState.figureAssociatedAnimation;
+ const animationItem = figureAssociatedAnimation.find((tid) => tid.targetId === key);
+ const targetKey = key ? key : `fig-${pos}`;
+ if (end) {
+ audioLevel = 0;
+ }
+ performMouthAnimation({
+ audioLevel,
+ OPEN_THRESHOLD: 50,
+ HALF_OPEN_THRESHOLD: 25,
+ currentMouthValue: 0,
+ lerpSpeed: 1,
+ key: targetKey,
+ animationItem,
+ pos,
+ });
+ if (!end) performSimulateVocalTimeout = setTimeout(performSimulateVocal, 50);
+ };
+ // 播放一段语音
+ if (vocal) {
+ playVocal(sentence);
+ } else if (key || pos) {
+ performSimulateVocalDelay = len * 250;
+ performSimulateVocal();
+ }
+
+ const performInitName: string = getRandomPerformName();
+ let endDelay = 750 - userDataState.optionData.textSpeed * 250;
+ // 如果有 notend 参数,那么就不需要等待
+ if (isNotend) {
+ endDelay = 0;
+ }
+
+ return {
+ performName: performInitName,
+ duration: sentenceDelay + endDelay + performSimulateVocalDelay,
+ isHoldOn: false,
+ stopFunction: () => {
+ WebGAL.events.textSettle.emit();
+ if (performSimulateVocalTimeout) {
+ performSimulateVocal(true);
+ clearTimeout(performSimulateVocalTimeout);
+ }
+ },
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ goNextWhenOver: isNotend,
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/setAnimation.ts b/packages/webgal/src/Core/gameScripts/setAnimation.ts
new file mode 100644
index 000000000..26712fad5
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/setAnimation.ts
@@ -0,0 +1,47 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { getSentenceArgByKey } from '@/Core/util/getSentenceArg';
+import { IAnimationObject } from '@/Core/controller/stage/pixi/PixiController';
+import { logger } from '@/Core/util/logger';
+import { webgalStore } from '@/store/store';
+
+import { getAnimateDuration, getAnimationObject } from '@/Core/Modules/animationFunctions';
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 设置背景动画
+ * @param sentence
+ */
+export const setAnimation = (sentence: ISentence): IPerform => {
+ const startDialogKey = webgalStore.getState().stage.currentDialogKey;
+ const animationName = sentence.content;
+ const animationDuration = getAnimateDuration(animationName);
+ const target = (getSentenceArgByKey(sentence, 'target')?.toString() ?? 'default_id').toString();
+ const key = `${target}-${animationName}-${animationDuration}`;
+ let stopFunction;
+ setTimeout(() => {
+ WebGAL.gameplay.pixiStage?.stopPresetAnimationOnTarget(target);
+ const animationObj: IAnimationObject | null = getAnimationObject(animationName, target, animationDuration);
+ if (animationObj) {
+ logger.debug(`动画${animationName}作用在${target}`, animationDuration);
+ WebGAL.gameplay.pixiStage?.registerAnimation(animationObj, key, target);
+ }
+ }, 0);
+ stopFunction = () => {
+ setTimeout(() => {
+ const endDialogKey = webgalStore.getState().stage.currentDialogKey;
+ const isHasNext = startDialogKey !== endDialogKey;
+ WebGAL.gameplay.pixiStage?.removeAnimationWithSetEffects(key);
+ }, 0);
+ };
+
+ return {
+ performName: key,
+ duration: animationDuration,
+ isHoldOn: false,
+ stopFunction,
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/setComplexAnimation.ts b/packages/webgal/src/Core/gameScripts/setComplexAnimation.ts
new file mode 100644
index 000000000..4c30cac0c
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/setComplexAnimation.ts
@@ -0,0 +1,52 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { getSentenceArgByKey } from '@/Core/util/getSentenceArg';
+import { webgalAnimations } from '@/Core/controller/stage/pixi/animations';
+import { IAnimationObject } from '@/Core/controller/stage/pixi/PixiController';
+import { logger } from '@/Core/util/logger';
+import { webgalStore } from '@/store/store';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 设置背景动画
+ * @param sentence
+ */
+export const setComplexAnimation = (sentence: ISentence): IPerform => {
+ const startDialogKey = webgalStore.getState().stage.currentDialogKey;
+ const animationName = sentence.content;
+ const animationDuration = (getSentenceArgByKey(sentence, 'duration') ?? 0) as number;
+ const target = (getSentenceArgByKey(sentence, 'target')?.toString() ?? '0') as string;
+ const key = `${target}-${animationName}-${animationDuration}`;
+ const animationFunction: Function | null = getAnimationObject(animationName);
+ let stopFunction: () => void = () => {};
+ if (animationFunction) {
+ logger.debug(`动画${animationName}作用在${target}`, animationDuration);
+ const animationObj: IAnimationObject = animationFunction(target, animationDuration);
+ WebGAL.gameplay.pixiStage?.stopPresetAnimationOnTarget(target);
+ WebGAL.gameplay.pixiStage?.registerAnimation(animationObj, key, target);
+ stopFunction = () => {
+ const endDialogKey = webgalStore.getState().stage.currentDialogKey;
+ const isHasNext = startDialogKey !== endDialogKey;
+ WebGAL.gameplay.pixiStage?.removeAnimationWithSetEffects(key);
+ };
+ }
+ return {
+ performName: key,
+ duration: animationDuration,
+ isHoldOn: false,
+ stopFunction,
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
+
+function getAnimationObject(animationName: string): Function | null {
+ const result = webgalAnimations.find((e) => e.name === animationName);
+ logger.debug('装载动画', result);
+ if (result) {
+ return result.animationGenerateFunc;
+ }
+ return null;
+}
diff --git a/packages/webgal/src/Core/gameScripts/setFilter.ts b/packages/webgal/src/Core/gameScripts/setFilter.ts
new file mode 100644
index 000000000..90a5d9e8f
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/setFilter.ts
@@ -0,0 +1,18 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+
+/**
+ * 设置背景效果
+ * @param sentence
+ */
+export const setFilter = (sentence: ISentence): IPerform => {
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/setTempAnimation.ts b/packages/webgal/src/Core/gameScripts/setTempAnimation.ts
new file mode 100644
index 000000000..6ac435d1b
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/setTempAnimation.ts
@@ -0,0 +1,59 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { getSentenceArgByKey } from '@/Core/util/getSentenceArg';
+import { IAnimationObject } from '@/Core/controller/stage/pixi/PixiController';
+import { logger } from '@/Core/util/logger';
+import { webgalStore } from '@/store/store';
+import { generateTimelineObj } from '@/Core/controller/stage/pixi/animations/timeline';
+import cloneDeep from 'lodash/cloneDeep';
+import { baseTransform } from '@/store/stageInterface';
+import { IUserAnimation } from '../Modules/animations';
+import { getAnimateDuration, getAnimationObject } from '@/Core/Modules/animationFunctions';
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 设置临时动画
+ * @param sentence
+ */
+export const setTempAnimation = (sentence: ISentence): IPerform => {
+ const startDialogKey = webgalStore.getState().stage.currentDialogKey;
+ const animationName = (Math.random() * 10).toString(16);
+ const animationString = sentence.content;
+ let animationObj;
+ try {
+ animationObj = JSON.parse(animationString);
+ } catch (e) {
+ animationObj = [];
+ }
+ const newAnimation: IUserAnimation = { name: animationName, effects: animationObj };
+ WebGAL.animationManager.addAnimation(newAnimation);
+ const animationDuration = getAnimateDuration(animationName);
+ const target = (getSentenceArgByKey(sentence, 'target')?.toString() ?? '0') as string;
+ const key = `${target}-${animationName}-${animationDuration}`;
+ let stopFunction = () => {};
+ setTimeout(() => {
+ WebGAL.gameplay.pixiStage?.stopPresetAnimationOnTarget(target);
+ const animationObj: IAnimationObject | null = getAnimationObject(animationName, target, animationDuration);
+ if (animationObj) {
+ logger.debug(`动画${animationName}作用在${target}`, animationDuration);
+ WebGAL.gameplay.pixiStage?.registerAnimation(animationObj, key, target);
+ }
+ }, 0);
+ stopFunction = () => {
+ setTimeout(() => {
+ const endDialogKey = webgalStore.getState().stage.currentDialogKey;
+ const isHasNext = startDialogKey !== endDialogKey;
+ WebGAL.gameplay.pixiStage?.removeAnimationWithSetEffects(key);
+ }, 0);
+ };
+
+ return {
+ performName: key,
+ duration: animationDuration,
+ isHoldOn: false,
+ stopFunction,
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/setTextbox.ts b/packages/webgal/src/Core/gameScripts/setTextbox.ts
new file mode 100644
index 000000000..f112cee41
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/setTextbox.ts
@@ -0,0 +1,25 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { webgalStore } from '@/store/store';
+import { setStage } from '@/store/stageReducer';
+
+/**
+ * 语句执行的模板代码
+ * @param sentence
+ */
+export function setTextbox(sentence: ISentence): IPerform {
+ if (sentence.content === 'hide') {
+ webgalStore.dispatch(setStage({ key: 'isDisableTextbox', value: true }));
+ } else {
+ webgalStore.dispatch(setStage({ key: 'isDisableTextbox', value: false }));
+ }
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+}
diff --git a/packages/webgal/src/Core/gameScripts/setTransform.ts b/packages/webgal/src/Core/gameScripts/setTransform.ts
new file mode 100644
index 000000000..d094638bb
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/setTransform.ts
@@ -0,0 +1,93 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { getSentenceArgByKey } from '@/Core/util/getSentenceArg';
+import { IAnimationObject } from '@/Core/controller/stage/pixi/PixiController';
+import { logger } from '@/Core/util/logger';
+import { webgalStore } from '@/store/store';
+import { generateTimelineObj } from '@/Core/controller/stage/pixi/animations/timeline';
+import cloneDeep from 'lodash/cloneDeep';
+import { baseTransform, ITransform } from '@/store/stageInterface';
+import { IUserAnimation } from '../Modules/animations';
+import { generateTransformAnimationObj } from '@/Core/controller/stage/pixi/animations/generateTransformAnimationObj';
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 设置变换
+ * @param sentence
+ */
+export const setTransform = (sentence: ISentence): IPerform => {
+ const startDialogKey = webgalStore.getState().stage.currentDialogKey;
+ const animationName = (Math.random() * 10).toString(16);
+ const animationString = sentence.content;
+ let animationObj: (ITransform & {
+ duration: number;
+ })[];
+ const duration = getSentenceArgByKey(sentence, 'duration');
+ const target = (getSentenceArgByKey(sentence, 'target')?.toString() ?? '0') as string;
+ try {
+ const frame = JSON.parse(animationString) as ITransform & { duration: number };
+ animationObj = generateTransformAnimationObj(target, frame, duration);
+ } catch (e) {
+ // 解析都错误了,歇逼吧
+ animationObj = [];
+ }
+
+ const newAnimation: IUserAnimation = { name: animationName, effects: animationObj };
+ WebGAL.animationManager.addAnimation(newAnimation);
+ const animationDuration = getAnimateDuration(animationName);
+
+ const key = `${target}-${animationName}-${animationDuration}`;
+ let stopFunction = () => {};
+ setTimeout(() => {
+ WebGAL.gameplay.pixiStage?.stopPresetAnimationOnTarget(target);
+ const animationObj: IAnimationObject | null = getAnimationObject(animationName, target, animationDuration);
+ if (animationObj) {
+ logger.debug(`动画${animationName}作用在${target}`, animationDuration);
+ WebGAL.gameplay.pixiStage?.registerAnimation(animationObj, key, target);
+ }
+ }, 0);
+ stopFunction = () => {
+ setTimeout(() => {
+ const endDialogKey = webgalStore.getState().stage.currentDialogKey;
+ const isHasNext = startDialogKey !== endDialogKey;
+ WebGAL.gameplay.pixiStage?.removeAnimationWithSetEffects(key);
+ }, 0);
+ };
+
+ return {
+ performName: key,
+ duration: animationDuration,
+ isHoldOn: false,
+ stopFunction,
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
+
+function getAnimationObject(animationName: string, target: string, duration: number) {
+ const effect = WebGAL.animationManager.getAnimations().find((ani) => ani.name === animationName);
+ if (effect) {
+ const mappedEffects = effect.effects.map((effect) => {
+ const newEffect = cloneDeep({ ...baseTransform, duration: 0 });
+ Object.assign(newEffect, effect);
+ newEffect.duration = effect.duration;
+ return newEffect;
+ });
+ logger.debug('装载自定义动画', mappedEffects);
+ return generateTimelineObj(mappedEffects, target, duration);
+ }
+ return null;
+}
+
+function getAnimateDuration(animationName: string) {
+ const effect = WebGAL.animationManager.getAnimations().find((ani) => ani.name === animationName);
+ if (effect) {
+ let duration = 0;
+ effect.effects.forEach((e) => {
+ duration += e.duration;
+ });
+ return duration;
+ }
+ return 0;
+}
diff --git a/packages/webgal/src/Core/gameScripts/setTransition.ts b/packages/webgal/src/Core/gameScripts/setTransition.ts
new file mode 100644
index 000000000..1ad723329
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/setTransition.ts
@@ -0,0 +1,36 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { webgalStore } from '@/store/store';
+import cloneDeep from 'lodash/cloneDeep';
+import { getSentenceArgByKey } from '@/Core/util/getSentenceArg';
+import { setStage } from '@/store/stageReducer';
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 设置转场效果
+ * @param sentence
+ */
+export const setTransition = (sentence: ISentence): IPerform => {
+ // 根据参数设置指定位置
+ let key = '0';
+ for (const e of sentence.args) {
+ if (e.key === 'target') {
+ key = e.value.toString();
+ }
+ }
+ if (getSentenceArgByKey(sentence, 'enter')) {
+ WebGAL.animationManager.nextEnterAnimationName.set(key, getSentenceArgByKey(sentence, 'enter')!.toString());
+ }
+ if (getSentenceArgByKey(sentence, 'exit')) {
+ WebGAL.animationManager.nextExitAnimationName.set(key + '-off', getSentenceArgByKey(sentence, 'exit')!.toString());
+ }
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => false,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/setVar.ts b/packages/webgal/src/Core/gameScripts/setVar.ts
new file mode 100644
index 000000000..ff6ac5508
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/setVar.ts
@@ -0,0 +1,127 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { webgalStore } from '@/store/store';
+import { setStageVar } from '@/store/stageReducer';
+import { logger } from '@/Core/util/logger';
+import { compile } from 'angular-expressions';
+import { setScriptManagedGlobalVar } from '@/store/userDataReducer';
+import { ActionCreatorWithPayload } from '@reduxjs/toolkit';
+import { ISetGameVar } from '@/store/stageInterface';
+import { dumpToStorageFast } from '@/Core/controller/storage/storageController';
+import get from 'lodash/get';
+/**
+ * 设置变量
+ * @param sentence
+ */
+export const setVar = (sentence: ISentence): IPerform => {
+ let setGlobal = false;
+ sentence.args.forEach((e) => {
+ if (e.key === 'global') {
+ setGlobal = true;
+ }
+ });
+ let targetReducerFunction: ActionCreatorWithPayload;
+ if (setGlobal) {
+ targetReducerFunction = setScriptManagedGlobalVar;
+ } else {
+ targetReducerFunction = setStageVar;
+ }
+ // 先把表达式拆分为变量名和赋值语句
+ if (sentence.content.match(/\s*=\s*/)) {
+ const key = sentence.content.split(/\s*=\s*/)[0];
+ const valExp = sentence.content.split(/\s*=\s*/)[1];
+ if (valExp === 'random()') {
+ webgalStore.dispatch(targetReducerFunction({ key, value: Math.random() }));
+ } else if (valExp.match(/[+\-*\/()]/)) {
+ // 如果包含加减乘除号,则运算
+ // 先取出运算表达式中的变量
+ const valExpArr = valExp.split(/([+\-*\/()])/g);
+ // 将变量替换为变量的值,然后合成表达式字符串
+ const valExp2 = valExpArr
+ .map((e) => {
+ if (!e.trim().match(/^[a-zA-Z_$][a-zA-Z0-9_.]*$/)) {
+ // 检查是否是变量名,不是就返回本身
+ return e;
+ }
+ const _r = getValueFromStateElseKey(e.trim(), true);
+ return typeof _r === 'string' ? `'${_r}'` : _r;
+ })
+ .reduce((pre, curr) => pre + curr, '');
+ let result = '';
+ try {
+ const exp = compile(valExp2);
+ result = exp();
+ } catch (e) {
+ logger.error('expression compile error', e);
+ }
+ webgalStore.dispatch(targetReducerFunction({ key, value: result }));
+ } else if (valExp.match(/true|false/)) {
+ if (valExp.match(/true/)) {
+ webgalStore.dispatch(targetReducerFunction({ key, value: true }));
+ }
+ if (valExp.match(/false/)) {
+ webgalStore.dispatch(targetReducerFunction({ key, value: false }));
+ }
+ } else if (valExp.length === 0) {
+ webgalStore.dispatch(targetReducerFunction({ key, value: '' }));
+ } else {
+ if (!isNaN(Number(valExp))) {
+ webgalStore.dispatch(targetReducerFunction({ key, value: Number(valExp) }));
+ } else {
+ // 字符串
+ webgalStore.dispatch(targetReducerFunction({ key, value: getValueFromStateElseKey(valExp, true) }));
+ }
+ }
+ if (setGlobal) {
+ logger.debug('设置全局变量:', { key, value: webgalStore.getState().userData.globalGameVar[key] });
+ dumpToStorageFast();
+ } else {
+ logger.debug('设置变量:', { key, value: webgalStore.getState().stage.GameVar[key] });
+ }
+ }
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
+
+type BaseVal = string | number | boolean | undefined;
+
+/**
+ * 取不到时返回 undefined
+ */
+export function getValueFromState(key: string) {
+ let ret: any;
+ const stage = webgalStore.getState().stage;
+ const userData = webgalStore.getState().userData;
+ const _Merge = { stage, userData }; // 不要直接合并到一起,防止可能的键冲突
+ if (stage.GameVar.hasOwnProperty(key)) {
+ ret = stage.GameVar[key];
+ } else if (userData.globalGameVar.hasOwnProperty(key)) {
+ ret = userData.globalGameVar[key];
+ } else if (key.startsWith('$')) {
+ const propertyKey = key.replace('$', '');
+ ret = get(_Merge, propertyKey, undefined) as BaseVal;
+ }
+ return ret;
+}
+
+/**
+ * 取不到时返回 {key}
+ */
+export function getValueFromStateElseKey(key: string, useKeyNameAsReturn = false) {
+ const valueFromState = getValueFromState(key);
+ if (valueFromState === null || valueFromState === undefined) {
+ logger.warn('valueFromState result null, key = ' + key);
+ if (useKeyNameAsReturn) {
+ return key;
+ }
+ return `{${key}}`;
+ }
+ return valueFromState;
+}
diff --git a/packages/webgal/src/Core/gameScripts/showVars.ts b/packages/webgal/src/Core/gameScripts/showVars.ts
new file mode 100644
index 000000000..1e677d176
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/showVars.ts
@@ -0,0 +1,43 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { webgalStore } from '@/store/store';
+import { setStage } from '@/store/stageReducer';
+import { logger } from '@/Core/util/logger';
+import { getRandomPerformName } from '@/Core/Modules/perform/performController';
+import { PERFORM_CONFIG } from '@/config';
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 进行普通对话的显示
+ * @param sentence 语句
+ * @return {IPerform} 执行的演出
+ */
+export const showVars = (sentence: ISentence): IPerform => {
+ const stageState = webgalStore.getState().stage;
+ const userDataState = webgalStore.getState().userData;
+ const dispatch = webgalStore.dispatch;
+ // 设置文本显示
+ const allVar = {
+ stageGameVar: stageState.GameVar,
+ globalGameVar: userDataState.globalGameVar,
+ };
+ dispatch(setStage({ key: 'showText', value: JSON.stringify(allVar) }));
+ dispatch(setStage({ key: 'showName', value: '展示变量' }));
+ logger.debug('展示变量:', allVar);
+ setTimeout(() => {
+ WebGAL.events.textSettle.emit();
+ }, 0);
+ const performInitName: string = getRandomPerformName();
+ const endDelay = 750 - userDataState.optionData.textSpeed * 250;
+ return {
+ performName: performInitName,
+ duration: endDelay,
+ isHoldOn: false,
+ stopFunction: () => {
+ WebGAL.events.textSettle.emit();
+ },
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/template.ts b/packages/webgal/src/Core/gameScripts/template.ts
new file mode 100644
index 000000000..4945e9d4a
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/template.ts
@@ -0,0 +1,18 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+
+/**
+ * 语句执行的模板代码
+ * @param sentence
+ */
+export const template = (sentence: ISentence): IPerform => {
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/unlockBgm.ts b/packages/webgal/src/Core/gameScripts/unlockBgm.ts
new file mode 100644
index 000000000..8eb1493d2
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/unlockBgm.ts
@@ -0,0 +1,39 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { webgalStore } from '@/store/store';
+import { unlockBgmInUserData } from '@/store/userDataReducer';
+import localforage from 'localforage';
+import { logger } from '@/Core/util/logger';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 解锁bgm
+ * @param sentence
+ */
+export const unlockBgm = (sentence: ISentence): IPerform => {
+ const url = sentence.content;
+ let name = sentence.content;
+ let series = 'default';
+ sentence.args.forEach((e) => {
+ if (e.key === 'name') {
+ name = e.value.toString();
+ }
+ if (e.key === 'series') {
+ series = e.value.toString();
+ }
+ });
+ logger.info(`解锁BGM:${name},路径:${url},所属系列:${series}`);
+ webgalStore.dispatch(unlockBgmInUserData({ name, url, series }));
+ const userDataState = webgalStore.getState().userData;
+ localforage.setItem(WebGAL.gameKey, userDataState).then(() => {});
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/unlockCg.ts b/packages/webgal/src/Core/gameScripts/unlockCg.ts
new file mode 100644
index 000000000..88772eb97
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/unlockCg.ts
@@ -0,0 +1,39 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+import { webgalStore } from '@/store/store';
+import { unlockCgInUserData } from '@/store/userDataReducer';
+import { logger } from '@/Core/util/logger';
+import localforage from 'localforage';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 解锁cg
+ * @param sentence
+ */
+export const unlockCg = (sentence: ISentence): IPerform => {
+ const url = sentence.content;
+ let name = sentence.content;
+ let series = 'default';
+ sentence.args.forEach((e) => {
+ if (e.key === 'name') {
+ name = e.value.toString();
+ }
+ if (e.key === 'series') {
+ series = e.value.toString();
+ }
+ });
+ logger.info(`解锁CG:${name},路径:${url},所属系列:${series}`);
+ webgalStore.dispatch(unlockCgInUserData({ name, url, series }));
+ const userDataState = webgalStore.getState().userData;
+ localforage.setItem(WebGAL.gameKey, userDataState).then(() => {});
+ return {
+ performName: 'none',
+ duration: 0,
+ isHoldOn: false,
+ stopFunction: () => {},
+ blockingNext: () => false,
+ blockingAuto: () => true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/vocal/conentsCash.ts b/packages/webgal/src/Core/gameScripts/vocal/conentsCash.ts
new file mode 100644
index 000000000..d4be3828f
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/vocal/conentsCash.ts
@@ -0,0 +1,30 @@
+export class FigureConentsCash {
+ private _content = '';
+
+ // public constructor() {}
+ public push(sentenceContent: string) {
+ this._content = sentenceContent;
+ }
+ public pop(): string {
+ const constContent = this._content;
+ this._content = '';
+ return constContent;
+ }
+}
+
+export class VoiceConentsCash {
+ private _content = '';
+
+ // public constructor() {}
+ public push(sentenceContent: string) {
+ this._content = sentenceContent;
+ }
+ public pop(): string {
+ const constContent = this._content;
+ this._content = '';
+ return constContent;
+ }
+}
+
+export const figureCash = new FigureConentsCash();
+export const voiceCash = new VoiceConentsCash();
diff --git a/packages/webgal/src/Core/gameScripts/vocal/index.ts b/packages/webgal/src/Core/gameScripts/vocal/index.ts
new file mode 100644
index 000000000..e4e34661d
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/vocal/index.ts
@@ -0,0 +1,199 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+import { logger } from '@/Core/util/logger';
+import { webgalStore } from '@/store/store';
+import { setStage } from '@/store/stageReducer';
+import { getSentenceArgByKey } from '@/Core/util/getSentenceArg';
+import { IStageState } from '@/store/stageInterface';
+import {
+ audioContextWrapper,
+ getAudioLevel,
+ performBlinkAnimation,
+ performMouthAnimation,
+ updateThresholds,
+} from '@/Core/gameScripts/vocal/vocalAnimation';
+import { match } from '../../util/match';
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 播放一段语音
+ * @param sentence 语句
+ */
+export const playVocal = (sentence: ISentence) => {
+ logger.debug('play vocal');
+ const performInitName = 'vocal-play';
+ const url = getSentenceArgByKey(sentence, 'vocal'); // 获取语音的url
+ const volume = getSentenceArgByKey(sentence, 'volume'); // 获取语音的音量比
+ let currentStageState: IStageState;
+ currentStageState = webgalStore.getState().stage;
+ let pos = '';
+ let key = '';
+ const freeFigure = currentStageState.freeFigure;
+ const figureAssociatedAnimation = currentStageState.figureAssociatedAnimation;
+ let bufferLength = 0;
+ let currentMouthValue = 0;
+ const lerpSpeed = 1;
+
+ // 先停止之前的语音
+ let VocalControl: any = document.getElementById('currentVocal');
+ WebGAL.gameplay.performController.unmountPerform('vocal-play', true);
+ if (VocalControl !== null) {
+ VocalControl.currentTime = 0;
+ VocalControl.pause();
+ }
+
+ for (const e of sentence.args) {
+ if (e.value === true) {
+ match(e.key)
+ .with('left', () => {
+ pos = 'left';
+ })
+ .with('right', () => {
+ pos = 'right';
+ })
+ .endsWith('center', () => {
+ pos = 'center';
+ });
+ }
+ if (e.key === 'figureId') {
+ key = `${e.value.toString()}`;
+ }
+ }
+
+ // 获得舞台状态
+ webgalStore.dispatch(setStage({ key: 'playVocal', value: url }));
+ webgalStore.dispatch(setStage({ key: 'vocal', value: url }));
+
+ let isOver = false;
+
+ /**
+ * 嘴型同步
+ */
+
+ return {
+ arrangePerformPromise: new Promise((resolve) => {
+ // 播放语音
+ setTimeout(() => {
+ let VocalControl: any = document.getElementById('currentVocal');
+ // 设置语音音量
+ typeof volume === 'number' && volume >= 0 && volume <= 100
+ ? webgalStore.dispatch(setStage({ key: 'vocalVolume', value: volume }))
+ : webgalStore.dispatch(setStage({ key: 'vocalVolume', value: 100 }));
+ // 设置语音
+ if (VocalControl !== null) {
+ VocalControl.currentTime = 0;
+ // 播放并作为一个特别演出加入
+ const perform = {
+ performName: performInitName,
+ duration: 1000 * 60 * 60,
+ isOver: false,
+ isHoldOn: false,
+ stopFunction: () => {
+ clearInterval(audioContextWrapper.audioLevelInterval);
+ VocalControl.pause();
+ key = key ? key : `fig-${pos}`;
+ const animationItem = figureAssociatedAnimation.find((tid) => tid.targetId === key);
+ performMouthAnimation({
+ audioLevel: 0,
+ OPEN_THRESHOLD: 1,
+ HALF_OPEN_THRESHOLD: 1,
+ currentMouthValue,
+ lerpSpeed,
+ key,
+ animationItem,
+ pos,
+ });
+ clearTimeout(audioContextWrapper.blinkTimerID);
+ },
+ blockingNext: () => false,
+ blockingAuto: () => {
+ return !isOver;
+ },
+ skipNextCollect: true,
+ stopTimeout: undefined, // 暂时不用,后面会交给自动清除
+ };
+ WebGAL.gameplay.performController.arrangeNewPerform(perform, sentence, false);
+ key = key ? key : `fig-${pos}`;
+ const animationItem = figureAssociatedAnimation.find((tid) => tid.targetId === key);
+ if (animationItem) {
+ let maxAudioLevel = 0;
+
+ const foundFigure = freeFigure.find((figure) => figure.key === key);
+
+ if (foundFigure) {
+ pos = foundFigure.basePosition;
+ }
+
+ if (!audioContextWrapper.audioContext) {
+ let audioContext: AudioContext | null;
+ audioContext = new AudioContext();
+ audioContextWrapper.analyser = audioContext.createAnalyser();
+ audioContextWrapper.analyser.fftSize = 256;
+ audioContextWrapper.dataArray = new Uint8Array(audioContextWrapper.analyser.frequencyBinCount);
+ }
+
+ if (!audioContextWrapper.analyser) {
+ audioContextWrapper.analyser = audioContextWrapper.audioContext.createAnalyser();
+ audioContextWrapper.analyser.fftSize = 256;
+ }
+
+ bufferLength = audioContextWrapper.analyser.frequencyBinCount;
+ audioContextWrapper.dataArray = new Uint8Array(bufferLength);
+ let vocalControl = document.getElementById('currentVocal') as HTMLMediaElement;
+
+ if (!audioContextWrapper.source) {
+ audioContextWrapper.source = audioContextWrapper.audioContext.createMediaElementSource(vocalControl);
+ audioContextWrapper.source.connect(audioContextWrapper.analyser);
+ }
+
+ audioContextWrapper.analyser.connect(audioContextWrapper.audioContext.destination);
+
+ // Lip-snc Animation
+ audioContextWrapper.audioLevelInterval = setInterval(() => {
+ const audioLevel = getAudioLevel(
+ audioContextWrapper.analyser!,
+ audioContextWrapper.dataArray!,
+ bufferLength,
+ );
+ const { OPEN_THRESHOLD, HALF_OPEN_THRESHOLD } = updateThresholds(audioLevel);
+
+ performMouthAnimation({
+ audioLevel,
+ OPEN_THRESHOLD,
+ HALF_OPEN_THRESHOLD,
+ currentMouthValue,
+ lerpSpeed,
+ key,
+ animationItem,
+ pos,
+ });
+ }, 50);
+
+ // blinkAnimation
+ let animationEndTime: number;
+
+ // 10sec
+ animationEndTime = Date.now() + 10000;
+ performBlinkAnimation({ key, animationItem, pos, animationEndTime });
+
+ // 10sec
+ setTimeout(() => {
+ clearTimeout(audioContextWrapper.blinkTimerID);
+ }, 10000);
+ }
+
+ VocalControl?.play();
+
+ VocalControl.onended = () => {
+ for (const e of WebGAL.gameplay.performController.performList) {
+ if (e.performName === performInitName) {
+ isOver = true;
+ e.stopFunction();
+ WebGAL.gameplay.performController.unmountPerform(e.performName);
+ }
+ }
+ };
+ }
+ }, 1);
+ }),
+ };
+};
diff --git a/packages/webgal/src/Core/gameScripts/vocal/vocalAnimation.ts b/packages/webgal/src/Core/gameScripts/vocal/vocalAnimation.ts
new file mode 100644
index 000000000..e46637006
--- /dev/null
+++ b/packages/webgal/src/Core/gameScripts/vocal/vocalAnimation.ts
@@ -0,0 +1,100 @@
+import { WebGAL } from '@/Core/WebGAL';
+
+interface IAudioContextWrapper {
+ audioContext: AudioContext;
+ source: MediaElementAudioSourceNode | null;
+ analyser: AnalyserNode | undefined;
+ dataArray: Uint8Array | undefined;
+ audioLevelInterval: ReturnType;
+ blinkTimerID: ReturnType;
+ maxAudioLevel: number;
+}
+
+// Initialize the object based on the interface
+export const audioContextWrapper: IAudioContextWrapper = {
+ audioContext: new AudioContext(),
+ source: null,
+ analyser: undefined,
+ dataArray: undefined,
+ audioLevelInterval: setInterval(() => {}, 0), // dummy interval
+ blinkTimerID: setTimeout(() => {}, 0), // dummy timeout
+ maxAudioLevel: 0,
+};
+
+export const updateThresholds = (audioLevel: number) => {
+ audioContextWrapper.maxAudioLevel = Math.max(audioLevel, audioContextWrapper.maxAudioLevel);
+ return {
+ OPEN_THRESHOLD: audioContextWrapper.maxAudioLevel * 0.75,
+ HALF_OPEN_THRESHOLD: audioContextWrapper.maxAudioLevel * 0.5,
+ };
+};
+
+export const performBlinkAnimation = (params: {
+ key: string;
+ animationItem: any;
+ pos: string;
+ animationEndTime: number;
+}) => {
+ let isBlinking = false;
+
+ function blink() {
+ if (isBlinking || (params.animationEndTime && Date.now() > params.animationEndTime)) return;
+ isBlinking = true;
+ WebGAL.gameplay.pixiStage?.performBlinkAnimation(params.key, params.animationItem, 'closed', params.pos);
+ audioContextWrapper.blinkTimerID = setTimeout(() => {
+ WebGAL.gameplay.pixiStage?.performBlinkAnimation(params.key, params.animationItem, 'open', params.pos);
+ isBlinking = false;
+ const nextBlinkTime = Math.random() * 300 + 3500;
+ audioContextWrapper.blinkTimerID = setTimeout(blink, nextBlinkTime);
+ }, 200);
+ }
+ blink();
+};
+
+// Updated getAudioLevel function
+export const getAudioLevel = (analyser: AnalyserNode, dataArray: Uint8Array, bufferLength: number): number => {
+ analyser.getByteFrequencyData(dataArray);
+ let sum = 0;
+ for (let i = 0; i < bufferLength; i++) {
+ sum += dataArray[i];
+ }
+ return sum / bufferLength;
+};
+
+export const performMouthAnimation = (params: {
+ audioLevel: number;
+ OPEN_THRESHOLD: number;
+ HALF_OPEN_THRESHOLD: number;
+ currentMouthValue: number;
+ lerpSpeed: number;
+ key: string;
+ animationItem: any;
+ pos: string;
+}) => {
+ const { audioLevel, OPEN_THRESHOLD, HALF_OPEN_THRESHOLD, currentMouthValue, lerpSpeed, key, animationItem, pos } =
+ params;
+
+ let targetValue;
+ if (audioLevel > OPEN_THRESHOLD) {
+ targetValue = 1; // open
+ } else if (audioLevel > HALF_OPEN_THRESHOLD) {
+ targetValue = 0.5; // half_open
+ } else {
+ targetValue = 0; // closed
+ }
+ // Lerp
+ const mouthValue = currentMouthValue + (targetValue - currentMouthValue) * lerpSpeed;
+ WebGAL.gameplay.pixiStage?.setModelMouthY(key, audioLevel);
+
+ let mouthState;
+ if (mouthValue > 0.75) {
+ mouthState = 'open';
+ } else if (mouthValue > 0.25) {
+ mouthState = 'half_open';
+ } else {
+ mouthState = 'closed';
+ }
+ if (animationItem !== undefined) {
+ WebGAL.gameplay.pixiStage?.performMouthSyncAnimation(key, animationItem, mouthState, pos);
+ }
+};
diff --git a/packages/webgal/src/Core/initializeScript.ts b/packages/webgal/src/Core/initializeScript.ts
new file mode 100644
index 000000000..51f8c3c02
--- /dev/null
+++ b/packages/webgal/src/Core/initializeScript.ts
@@ -0,0 +1,108 @@
+/**
+ * @file 引擎初始化时会执行的脚本,包括获取游戏信息,初始化运行时变量,初始化用户数据存储
+ */
+import { logger } from './util/logger';
+import { infoFetcher } from './util/coreInitialFunction/infoFetcher';
+import { assetSetter, fileType } from './util/gameAssetsAccess/assetSetter';
+import { sceneFetcher } from './controller/scene/sceneFetcher';
+import { sceneParser } from './parser/sceneParser';
+import { bindExtraFunc } from '@/Core/util/coreInitialFunction/bindExtraFunc';
+import { webSocketFunc } from '@/Core/util/syncWithEditor/webSocketFunc';
+import uniqWith from 'lodash/uniqWith';
+import { scenePrefetcher } from './util/prefetcher/scenePrefetcher';
+import PixiStage from '@/Core/controller/stage/pixi/PixiController';
+import axios from 'axios';
+import { __INFO } from '@/config/info';
+import { WebGAL } from '@/Core/WebGAL';
+
+const u = navigator.userAgent;
+export const isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); // 判断是否是 iOS终端
+
+/**
+ * 引擎初始化函数
+ */
+export const initializeScript = (): void => {
+ // 打印初始log信息
+ logger.info(__INFO.version);
+ logger.info('Github: https://github.com/OpenWebGAL/WebGAL ');
+ logger.info('Made with ❤ by OpenWebGAL');
+ // 激活强制缩放
+ // 在调整窗口大小时重新计算宽高,设计稿按照 1600*900。
+ if (isIOS) {
+ /**
+ * iOS
+ */
+ alert(
+ `iOS 用户请横屏使用以获得最佳体验
+| Please use landscape mode on iOS for the best experience
+| iOS ユーザーは横画面での使用をお勧めします`,
+ );
+ }
+
+ // 获得 userAnimation
+ loadStyle('./game/userStyleSheet.css');
+ // 获得 user Animation
+ getUserAnimation();
+ // 获取游戏信息
+ infoFetcher('./game/config.txt');
+ // 获取start场景
+ const sceneUrl: string = assetSetter('start.txt', fileType.scene);
+ // 场景写入到运行时
+ sceneFetcher(sceneUrl).then((rawScene) => {
+ WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, 'start.txt', sceneUrl);
+ // 开始场景的预加载
+ const subSceneList = WebGAL.sceneManager.sceneData.currentScene.subSceneList;
+ WebGAL.sceneManager.settledScenes.push(sceneUrl); // 放入已加载场景列表,避免递归加载相同场景
+ const subSceneListUniq = uniqWith(subSceneList); // 去重
+ scenePrefetcher(subSceneListUniq);
+ });
+ /**
+ * 启动Pixi
+ */
+ WebGAL.gameplay.pixiStage = new PixiStage();
+
+ /**
+ * iOS 设备 卸载所有 Service Worker
+ */
+ // if ('serviceWorker' in navigator && isIOS) {
+ // navigator.serviceWorker.getRegistrations().then((registrations) => {
+ // for (const registration of registrations) {
+ // registration.unregister().then(() => {
+ // logger.info('已卸载 Service Worker');
+ // });
+ // }
+ // });
+ // }
+
+ /**
+ * 绑定工具函数
+ */
+ bindExtraFunc();
+ webSocketFunc();
+};
+
+function loadStyle(url: string) {
+ const link = document.createElement('link');
+ link.type = 'text/css';
+ link.rel = 'stylesheet';
+ link.href = url;
+ const head = document.getElementsByTagName('head')[0];
+ head.appendChild(link);
+}
+
+function getUserAnimation() {
+ axios.get('./game/animation/animationTable.json').then((res) => {
+ const animations: Array = res.data;
+ for (const animationName of animations) {
+ axios.get(`./game/animation/${animationName}.json`).then((res) => {
+ if (res.data) {
+ const userAnimation = {
+ name: animationName,
+ effects: res.data,
+ };
+ WebGAL.animationManager.addAnimation(userAnimation);
+ }
+ });
+ }
+ });
+}
diff --git a/packages/webgal/src/Core/parser/sceneParser.ts b/packages/webgal/src/Core/parser/sceneParser.ts
new file mode 100644
index 000000000..74a19f419
--- /dev/null
+++ b/packages/webgal/src/Core/parser/sceneParser.ts
@@ -0,0 +1,93 @@
+import { assetSetter } from '@/Core/util/gameAssetsAccess/assetSetter';
+import { assetsPrefetcher } from '@/Core/util/prefetcher/assetsPrefetcher';
+import SceneParser from 'webgal-parser';
+import { commandType, IScene } from '../controller/scene/sceneInterface';
+import { logger } from '../util/logger';
+import { bgm } from '@/Core/gameScripts/bgm';
+import { callSceneScript } from '@/Core/gameScripts/callSceneScript';
+import { changeBg } from '@/Core/gameScripts/changeBg';
+import { changeFigure } from '@/Core/gameScripts/changeFigure';
+import { changeSceneScript } from '@/Core/gameScripts/changeSceneScript';
+import { choose } from '@/Core/gameScripts/choose';
+import { comment } from '@/Core/gameScripts/comment';
+import { filmMode } from '@/Core/gameScripts/filmMode';
+import { getUserInput } from '@/Core/gameScripts/getUserInput';
+import { intro } from '@/Core/gameScripts/intro';
+import { label } from '@/Core/gameScripts/label';
+import { miniAvatar } from '@/Core/gameScripts/miniAvatar';
+import { pixi } from '@/Core/gameScripts/pixi';
+import { playEffect } from '@/Core/gameScripts/playEffect';
+import { playVideo } from '@/Core/gameScripts/playVideo';
+import { setAnimation } from '@/Core/gameScripts/setAnimation';
+import { setComplexAnimation } from '@/Core/gameScripts/setComplexAnimation';
+import { setFilter } from '@/Core/gameScripts/setFilter';
+import { setTempAnimation } from '@/Core/gameScripts/setTempAnimation';
+import { setTextbox } from '@/Core/gameScripts/setTextbox';
+import { setTransform } from '@/Core/gameScripts/setTransform';
+import { setTransition } from '@/Core/gameScripts/setTransition';
+import { unlockBgm } from '@/Core/gameScripts/unlockBgm';
+import { unlockCg } from '@/Core/gameScripts/unlockCg';
+import { end } from '../gameScripts/end';
+import { jumpLabel } from '../gameScripts/jumpLabel';
+import { pixiInit } from '../gameScripts/pixi/pixiInit';
+import { say } from '../gameScripts/say';
+import { setVar } from '../gameScripts/setVar';
+import { showVars } from '../gameScripts/showVars';
+import { defineScripts, IConfigInterface, ScriptConfig, ScriptFunction, scriptRegistry } from './utils';
+import { applyStyle } from '@/Core/gameScripts/applyStyle';
+
+export const SCRIPT_TAG_MAP = defineScripts({
+ intro: ScriptConfig(commandType.intro, intro),
+ changeBg: ScriptConfig(commandType.changeBg, changeBg),
+ changeFigure: ScriptConfig(commandType.changeFigure, changeFigure),
+ miniAvatar: ScriptConfig(commandType.miniAvatar, miniAvatar, { next: true }),
+ changeScene: ScriptConfig(commandType.changeScene, changeSceneScript),
+ choose: ScriptConfig(commandType.choose, choose),
+ end: ScriptConfig(commandType.end, end),
+ bgm: ScriptConfig(commandType.bgm, bgm, { next: true }),
+ playVideo: ScriptConfig(commandType.video, playVideo),
+ setComplexAnimation: ScriptConfig(commandType.setComplexAnimation, setComplexAnimation),
+ setFilter: ScriptConfig(commandType.setFilter, setFilter),
+ pixiInit: ScriptConfig(commandType.pixiInit, pixiInit, { next: true }),
+ pixiPerform: ScriptConfig(commandType.pixi, pixi, { next: true }),
+ label: ScriptConfig(commandType.label, label, { next: true }),
+ jumpLabel: ScriptConfig(commandType.jumpLabel, jumpLabel),
+ setVar: ScriptConfig(commandType.setVar, setVar, { next: true }),
+ showVars: ScriptConfig(commandType.showVars, showVars),
+ unlockCg: ScriptConfig(commandType.unlockCg, unlockCg, { next: true }),
+ unlockBgm: ScriptConfig(commandType.unlockBgm, unlockBgm, { next: true }),
+ say: ScriptConfig(commandType.say, say),
+ filmMode: ScriptConfig(commandType.filmMode, filmMode, { next: true }),
+ callScene: ScriptConfig(commandType.callScene, callSceneScript),
+ setTextbox: ScriptConfig(commandType.setTextbox, setTextbox),
+ setAnimation: ScriptConfig(commandType.setAnimation, setAnimation),
+ playEffect: ScriptConfig(commandType.playEffect, playEffect, { next: true }),
+ setTempAnimation: ScriptConfig(commandType.setTempAnimation, setTempAnimation),
+ __commment: ScriptConfig(commandType.comment, comment, { next: true }),
+ setTransform: ScriptConfig(commandType.setTransform, setTransform),
+ setTransition: ScriptConfig(commandType.setTransition, setTransition, { next: true }),
+ getUserInput: ScriptConfig(commandType.getUserInput, getUserInput),
+ applyStyle: ScriptConfig(commandType.applyStyle, applyStyle, { next: true }),
+ // if: ScriptConfig(commandType.if, undefined, { next: true }),
+});
+
+export const SCRIPT_CONFIG: IConfigInterface[] = Object.values(SCRIPT_TAG_MAP);
+
+export const ADD_NEXT_ARG_LIST = SCRIPT_CONFIG.filter((config) => config.next).map((config) => config.scriptType);
+
+/**
+ * 场景解析器
+ * @param rawScene 原始场景
+ * @param sceneName 场景名称
+ * @param sceneUrl 场景url
+ * @return {IScene} 解析后的场景
+ */
+export const WebgalParser = new SceneParser(assetsPrefetcher, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG);
+
+export const sceneParser = (rawScene: string, sceneName: string, sceneUrl: string): IScene => {
+ const parsedScene = WebgalParser.parse(rawScene, sceneName, sceneUrl);
+ logger.info(`解析场景:${sceneName},数据为:`, parsedScene);
+ return parsedScene;
+};
+
+export { scriptRegistry, type ScriptFunction };
diff --git a/packages/webgal/src/Core/parser/utils.ts b/packages/webgal/src/Core/parser/utils.ts
new file mode 100644
index 000000000..2c3ee5b3e
--- /dev/null
+++ b/packages/webgal/src/Core/parser/utils.ts
@@ -0,0 +1,41 @@
+import { commandType, ISentence } from '@/Core/controller/scene/sceneInterface';
+import { IPerform } from '@/Core/Modules/perform/performInterface';
+
+/**
+ * 规范函数的类型
+ * @type {(sentence: ISentence) => IPerform}
+ */
+export type ScriptFunction = (sentence: ISentence) => IPerform;
+
+export interface ScriptConfig {
+ scriptType: commandType;
+ scriptFunction: ScriptFunction;
+ next?: boolean;
+}
+
+export interface IConfigInterface extends ScriptConfig {
+ scriptString: string;
+}
+
+export function ScriptConfig(
+ scriptType: commandType,
+ scriptFunction: ScriptFunction,
+ config?: Omit,
+): ScriptConfig {
+ return { scriptType, scriptFunction, ...config };
+}
+
+export const scriptRegistry: Record = {} as any;
+
+export function defineScripts>>(
+ record: R,
+): {
+ [K in keyof R]: IConfigInterface;
+} {
+ // eslint-disable-next-line
+ const result = {} as Record;
+ for (const [scriptString, config] of Object.entries(record)) {
+ result[scriptString as keyof R] = scriptRegistry[config.scriptType] = { scriptString, ...config };
+ }
+ return result;
+}
diff --git a/packages/webgal/src/Core/util/constants.scss b/packages/webgal/src/Core/util/constants.scss
new file mode 100644
index 000000000..1020b9e78
--- /dev/null
+++ b/packages/webgal/src/Core/util/constants.scss
@@ -0,0 +1,2 @@
+$screenWidth: 2560px;
+$screenHeight: 1440px;
diff --git a/packages/webgal/src/Core/util/constants.ts b/packages/webgal/src/Core/util/constants.ts
new file mode 100644
index 000000000..cbd32bc73
--- /dev/null
+++ b/packages/webgal/src/Core/util/constants.ts
@@ -0,0 +1,4 @@
+export const SCREEN_CONSTANTS = {
+ height: 1440,
+ width: 2560,
+};
diff --git a/packages/webgal/src/Core/util/coreInitialFunction/bindExtraFunc.ts b/packages/webgal/src/Core/util/coreInitialFunction/bindExtraFunc.ts
new file mode 100644
index 000000000..6aa3c40d8
--- /dev/null
+++ b/packages/webgal/src/Core/util/coreInitialFunction/bindExtraFunc.ts
@@ -0,0 +1,5 @@
+import { syncFast } from '@/Core/util/syncWithEditor/syncWithOrigine';
+
+export const bindExtraFunc = () => {
+ (window as any).JMP = syncFast;
+};
diff --git a/packages/webgal/src/Core/util/coreInitialFunction/infoFetcher.ts b/packages/webgal/src/Core/util/coreInitialFunction/infoFetcher.ts
new file mode 100644
index 000000000..3d6dad14c
--- /dev/null
+++ b/packages/webgal/src/Core/util/coreInitialFunction/infoFetcher.ts
@@ -0,0 +1,66 @@
+import axios from 'axios';
+import { logger } from '../logger';
+import { getStorage, getStorageAsync, setStorage } from '../../controller/storage/storageController';
+import { webgalStore } from '@/store/store';
+import { initKey } from '@/Core/controller/storage/fastSaveLoad';
+import { WebgalParser } from '@/Core/parser/sceneParser';
+import { WebGAL } from '@/Core/WebGAL';
+import { getFastSaveFromStorage, getSavesFromStorage } from '@/Core/controller/storage/savesController';
+import { setGlobalVar } from '@/store/userDataReducer';
+
+declare global {
+ interface Window {
+ renderPromise?: Function;
+ }
+}
+/**
+ * 获取游戏信息
+ * @param url 游戏信息路径
+ */
+export const infoFetcher = (url: string) => {
+ const dispatch = webgalStore.dispatch;
+ axios.get(url).then(async (r) => {
+ let gameConfigRaw: string = r.data;
+ let gameConfig = WebgalParser.parseConfig(gameConfigRaw);
+ logger.info('获取到游戏信息', gameConfig);
+ // 先把 key 找到并设置了
+ const keyItem = gameConfig.find((e) => e.command === 'Game_key');
+ WebGAL.gameKey = (keyItem?.args?.[0] as string) ?? '';
+ initKey();
+ await getStorageAsync();
+ getFastSaveFromStorage();
+ getSavesFromStorage(0, 0);
+ // 按照游戏的配置开始设置对应的状态
+ gameConfig.forEach((e) => {
+ const { command, args } = e;
+ if (args.length > 0) {
+ if (args.length > 1) {
+ dispatch(
+ setGlobalVar({
+ key: command,
+ value: args.join('|'),
+ }),
+ );
+ } else {
+ let res: any = args[0].trim();
+ if (/^(true|false)$/g.test(args[0])) {
+ res = res === 'true';
+ } else if (/^[0-9]+\.?[0-9]+$/g.test(args[0])) {
+ res = Number(res);
+ }
+
+ dispatch(
+ setGlobalVar({
+ key: command,
+ value: res,
+ }),
+ );
+ }
+ }
+ });
+
+ window?.renderPromise?.();
+ delete window.renderPromise;
+ setStorage();
+ });
+};
diff --git a/packages/webgal/src/Core/util/gameAssetsAccess/assetSetter.ts b/packages/webgal/src/Core/util/gameAssetsAccess/assetSetter.ts
new file mode 100644
index 000000000..985a6be39
--- /dev/null
+++ b/packages/webgal/src/Core/util/gameAssetsAccess/assetSetter.ts
@@ -0,0 +1,56 @@
+/**
+ * @file 资源的引入可能是绝对链接,也可能是文件名,必须做必要的处理。
+ */
+
+/**
+ * 内置资源类型的枚举
+ */
+export enum fileType {
+ background,
+ bgm,
+ figure,
+ scene,
+ tex,
+ vocal,
+ video,
+}
+
+/**
+ * 获取资源路径
+ * @param fileName 资源的名称或地址
+ * @param assetType 资源类型
+ * @return {string} 处理后的资源路径(绝对或相对)
+ */
+export const assetSetter = (fileName: string, assetType: fileType): string => {
+ // 是绝对链接,直接返回
+ if (fileName.match('http://') || fileName.match('https://')) {
+ return fileName;
+ } else {
+ // 根据类型拼接资源的相对路径
+ let returnFilePath: string;
+ switch (assetType) {
+ case fileType.background:
+ returnFilePath = `./game/background/${fileName}`;
+ break;
+ case fileType.scene:
+ returnFilePath = `./game/scene/${fileName}`;
+ break;
+ case fileType.vocal:
+ returnFilePath = `./game/vocal/${fileName}`;
+ break;
+ case fileType.figure:
+ returnFilePath = `./game/figure/${fileName}`;
+ break;
+ case fileType.bgm:
+ returnFilePath = `./game/bgm/${fileName}`;
+ break;
+ case fileType.video:
+ returnFilePath = `./game/video/${fileName}`;
+ break;
+ default:
+ returnFilePath = ``;
+ break;
+ }
+ return returnFilePath;
+ }
+};
diff --git a/packages/webgal/src/Core/util/getSentenceArg.ts b/packages/webgal/src/Core/util/getSentenceArg.ts
new file mode 100644
index 000000000..20e3ac709
--- /dev/null
+++ b/packages/webgal/src/Core/util/getSentenceArg.ts
@@ -0,0 +1,9 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+
+export function getSentenceArgByKey(sentnece: ISentence, argk: string): null | string | boolean | number {
+ const args = sentnece.args;
+ const result = args.find((arg) => arg.key === argk);
+ if (result) {
+ return result.value;
+ } else return null;
+}
diff --git a/packages/webgal/src/Core/util/logger.ts b/packages/webgal/src/Core/util/logger.ts
new file mode 100644
index 000000000..fb6695a84
--- /dev/null
+++ b/packages/webgal/src/Core/util/logger.ts
@@ -0,0 +1,9 @@
+import Cloudlog from 'cloudlogjs';
+
+/**
+ * 日志打印工具
+ */
+export const logger = new Cloudlog();
+if (process.env.NODE_ENV === 'production') {
+ logger.setLevel('INFO');
+}
diff --git a/packages/webgal/src/Core/util/match.ts b/packages/webgal/src/Core/util/match.ts
new file mode 100644
index 000000000..b83f66f83
--- /dev/null
+++ b/packages/webgal/src/Core/util/match.ts
@@ -0,0 +1,38 @@
+class Matcher {
+ private subject: T;
+ private result: R | undefined;
+ private isEnd = false;
+
+ public constructor(subject: T) {
+ this.subject = subject;
+ }
+
+ public with(pattern: T, fn: () => R): this {
+ if (!this.isEnd && this.subject === pattern) {
+ this.result = fn();
+ this.isEnd = true;
+ }
+ return this;
+ }
+
+ public endsWith(pattern: T, fn: () => R) {
+ if (!this.isEnd && this.subject === pattern) {
+ this.result = fn();
+ this.isEnd = true;
+ }
+ return this.evaluate();
+ }
+
+ public default(fn: () => R) {
+ if (!this.isEnd) this.result = fn();
+ return this.evaluate();
+ }
+
+ private evaluate(): R | undefined {
+ return this.result;
+ }
+}
+
+export function match(subject: T): Matcher {
+ return new Matcher(subject);
+}
diff --git a/packages/webgal/src/Core/util/pixiPerformManager/initRegister.ts b/packages/webgal/src/Core/util/pixiPerformManager/initRegister.ts
new file mode 100644
index 000000000..2003b782f
--- /dev/null
+++ b/packages/webgal/src/Core/util/pixiPerformManager/initRegister.ts
@@ -0,0 +1,3 @@
+import '../../gameScripts/pixi/performs/cherryBlossoms';
+import '../../gameScripts/pixi/performs/rain';
+import '../../gameScripts/pixi/performs/snow';
diff --git a/packages/webgal/src/Core/util/pixiPerformManager/pixiPerformManager.ts b/packages/webgal/src/Core/util/pixiPerformManager/pixiPerformManager.ts
new file mode 100644
index 000000000..23b69a71f
--- /dev/null
+++ b/packages/webgal/src/Core/util/pixiPerformManager/pixiPerformManager.ts
@@ -0,0 +1,83 @@
+import type { Container } from 'pixi.js';
+import { logger } from '../logger';
+
+/**
+ * 特效执行返回的结果
+ */
+export type IResult, 'container' | 'tickerKey'> = {}> = {
+ container: Container;
+ tickerKey: string;
+} & R;
+
+type IName = string | (() => string);
+type IPerformCallback = () => IResult;
+
+const performs = new Map();
+
+/**
+ * 获取名称, 可能报空
+ * @param name 名称或者返回名称的函数
+ * @returns {string}
+ */
+function getName(name: IName): string | null {
+ if (!name) return null;
+ if (typeof name === 'string') return name;
+ return name();
+}
+
+/**
+ * 获取名称, 不会报空
+ * @param name 名称或者返回名称的函数
+ * @returns {string}
+ */
+function getKey(name: IName): string {
+ const key = getName(name);
+ if (!key) {
+ logger.error('Get name of perform failed. There no name of the perform.');
+ return '';
+ }
+ return key;
+}
+
+/**
+ * 注册特效, 注意, 同名会注销旧特效
+ * @param name 特效名
+ * @param callback 调用特效的函数
+ */
+export function registerPerform(name: IName, callback: IPerformCallback): void {
+ if (!callback || typeof callback !== 'function') throw new Error(`"${name}" is not a callback.`);
+ performs.set(getKey(name), callback);
+}
+
+/**
+ * 调用特效
+ * @param name 特效名
+ * @param args 自定义的参数
+ * @returns {IResult}
+ */
+export function call(name: IName, args: unknown[] = []): IResult {
+ const callback = performs.get(getKey(name));
+
+ if (!callback || !(callback instanceof Function)) {
+ logger.error(`Can\'t call the perform named "${name}"`);
+ throw new Error(`"${name}" don't have the pixiPerform callback.`);
+ }
+ return (callback as IPerformCallback)(...(args as []));
+}
+
+/**
+ * 注销特效
+ * @param name 特效名
+ */
+export function unregisterPerform(name: IName) {
+ performs.delete(getKey(name));
+}
+
+/**
+ * 获取全部可调用的特效特效名
+ */
+export function getPerforms(): string[] {
+ return [...performs.keys()];
+}
+
+import('./initRegister');
diff --git a/packages/webgal/src/Core/util/prefetcher/assetsPrefetcher.ts b/packages/webgal/src/Core/util/prefetcher/assetsPrefetcher.ts
new file mode 100644
index 000000000..f3176f0cd
--- /dev/null
+++ b/packages/webgal/src/Core/util/prefetcher/assetsPrefetcher.ts
@@ -0,0 +1,37 @@
+import { IAsset } from '@/Core/controller/scene/sceneInterface';
+import { logger } from '../logger';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+/**
+ * 预加载函数
+ * @param assetList 场景资源列表
+ */
+export const assetsPrefetcher = (assetList: Array) => {
+ // @ts-ignore
+ // 未必要移除,加载到内存里也有用
+ // if (window?.isElectron) {
+ // return;
+ // }
+
+ for (const asset of assetList) {
+ // 判断是否已经存在
+ const hasPrefetch = WebGAL.sceneManager.settledAssets.includes(asset.url);
+ if (hasPrefetch) {
+ logger.debug(`该资源${asset.url}已在预加载列表中,无需重复加载`);
+ } else {
+ const newLink = document.createElement('link');
+ newLink.setAttribute('rel', 'prefetch');
+ newLink.setAttribute('href', asset.url);
+ const head = document.getElementsByTagName('head');
+ if (head.length) {
+ try {
+ head[0].appendChild(newLink);
+ } catch (e) {
+ console.log('预加载出错', e);
+ }
+ }
+ WebGAL.sceneManager.settledAssets.push(asset.url);
+ }
+ }
+};
diff --git a/packages/webgal/src/Core/util/prefetcher/scenePrefetcher.ts b/packages/webgal/src/Core/util/prefetcher/scenePrefetcher.ts
new file mode 100644
index 000000000..a09822b7d
--- /dev/null
+++ b/packages/webgal/src/Core/util/prefetcher/scenePrefetcher.ts
@@ -0,0 +1,22 @@
+/**
+ * 场景预加载
+ * @param sceneList 需要预加载的场景文件列表
+ */
+import { sceneFetcher } from '../../controller/scene/sceneFetcher';
+import { sceneParser } from '../../parser/sceneParser';
+import { logger } from '@/Core/util/logger';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+export const scenePrefetcher = (sceneList: Array): void => {
+ for (const e of sceneList) {
+ if (!WebGAL.sceneManager.settledScenes.includes(e)) {
+ logger.info(`现在预加载场景${e}`);
+ sceneFetcher(e).then((r) => {
+ sceneParser(r, e, e);
+ });
+ } else {
+ logger.warn(`场景${e}已经加载过,无需再次加载`);
+ }
+ }
+};
diff --git a/packages/webgal/src/Core/util/syncWithEditor/syncWithOrigine.ts b/packages/webgal/src/Core/util/syncWithEditor/syncWithOrigine.ts
new file mode 100644
index 000000000..9e430c7b3
--- /dev/null
+++ b/packages/webgal/src/Core/util/syncWithEditor/syncWithOrigine.ts
@@ -0,0 +1,88 @@
+import { resetStage } from '@/Core/controller/stage/resetStage';
+import { assetSetter, fileType } from '@/Core/util/gameAssetsAccess/assetSetter';
+import { sceneFetcher } from '@/Core/controller/scene/sceneFetcher';
+import { sceneParser } from '@/Core/parser/sceneParser';
+import { logger } from '../logger';
+import { webgalStore } from '@/store/store';
+import { setVisibility } from '@/store/GUIReducer';
+import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
+
+import { WebGAL } from '@/Core/WebGAL';
+import cloneDeep from 'lodash/cloneDeep';
+import { IScene } from '@/Core/controller/scene/sceneInterface';
+import { jumpFromBacklog } from '@/Core/controller/storage/jumpFromBacklog';
+
+let syncFastTimeout: ReturnType | undefined;
+
+export const syncWithOrigine = (sceneName: string, sentenceId: number, expermental = false) => {
+ logger.warn('正在跳转到' + sceneName + ':' + sentenceId);
+ const dispatch = webgalStore.dispatch;
+ dispatch(setVisibility({ component: 'showTitle', visibility: false }));
+ dispatch(setVisibility({ component: 'showMenuPanel', visibility: false }));
+ dispatch(setVisibility({ component: 'isShowLogo', visibility: false }));
+ const title = document.getElementById('Title_enter_page');
+ if (title) {
+ title.style.display = 'none';
+ }
+ const pastScene = cloneDeep(WebGAL.sceneManager.sceneData.currentScene);
+ // 重新获取场景
+ const sceneUrl: string = assetSetter(sceneName, fileType.scene);
+ // 场景写入到运行时
+ sceneFetcher(sceneUrl).then((rawScene) => {
+ // 等等,先检查一下能不能恢复场景
+ const lastSameSentence = findLastSameSentence(pastScene, WebGAL.sceneManager.sceneData.currentScene, sentenceId);
+ const lastRecoverySentenceId = Math.min(sentenceId, lastSameSentence);
+ const recId = findLastAvailableBacklog(lastRecoverySentenceId, sceneName);
+ const isCanRec = recId >= 0 && expermental;
+ resetStage(!isCanRec);
+ WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, sceneName, sceneUrl);
+ // 开始快进到指定语句
+ const currentSceneName = WebGAL.sceneManager.sceneData.currentScene.sceneName;
+ WebGAL.gameplay.isFast = true;
+ if (isCanRec) {
+ jumpFromBacklog(recId, false);
+ }
+ if (syncFastTimeout) {
+ // 之前发生的跳转要清理掉
+ clearTimeout(syncFastTimeout);
+ }
+ syncFast(sentenceId, currentSceneName);
+ });
+};
+
+export function syncFast(sentenceId: number, currentSceneName: string) {
+ if (
+ WebGAL.sceneManager.sceneData.currentSentenceId < sentenceId &&
+ WebGAL.sceneManager.sceneData.currentScene.sceneName === currentSceneName
+ ) {
+ nextSentence();
+ syncFastTimeout = setTimeout(() => syncFast(sentenceId, currentSceneName), 2);
+ } else {
+ WebGAL.gameplay.isFast = false;
+ }
+}
+
+function findLastSameSentence(oldScene: IScene, newScene: IScene, sentenceId: number): number {
+ let lastSameSentence = 0;
+ for (let i = 0; i < sentenceId && i < oldScene.sentenceList.length; i++) {
+ const oldSentenceStr = JSON.stringify(oldScene.sentenceList[i]);
+ const newSentenceStr = JSON.stringify(newScene.sentenceList[i]);
+ if (oldSentenceStr !== newSentenceStr) {
+ break;
+ }
+ lastSameSentence = i;
+ }
+ return lastSameSentence;
+}
+
+function findLastAvailableBacklog(targetSentence: number, sceneName: string) {
+ let lastAvailable = -1;
+ WebGAL.backlogManager.getBacklog().forEach((e, i) => {
+ const recSentenceId = e.saveScene.currentSentenceId;
+ const recSceneName = e.saveScene.sceneName;
+ if (recSentenceId <= targetSentence && recSceneName === sceneName) {
+ lastAvailable = i;
+ }
+ });
+ return lastAvailable;
+}
diff --git a/packages/webgal/src/Core/util/syncWithEditor/webSocketFunc.ts b/packages/webgal/src/Core/util/syncWithEditor/webSocketFunc.ts
new file mode 100644
index 000000000..73f5342d2
--- /dev/null
+++ b/packages/webgal/src/Core/util/syncWithEditor/webSocketFunc.ts
@@ -0,0 +1,77 @@
+import { logger } from '../logger';
+import { syncWithOrigine } from '@/Core/util/syncWithEditor/syncWithOrigine';
+import { DebugCommand, IDebugMessage } from '@/types/debugProtocol';
+import { WebGAL } from '@/Core/WebGAL';
+import { webgalStore } from '@/store/store';
+import { WebgalParser } from '@/Core/parser/sceneParser';
+import { runScript } from '@/Core/controller/gamePlay/runScript';
+
+export const webSocketFunc = () => {
+ const loc: string = window.location.hostname;
+ const protocol: string = window.location.protocol;
+ const port: string = window.location.port; // 获取端口号
+
+ // 默认情况下,不需要在URL中明确指定标准HTTP(80)和HTTPS(443)端口
+ let defaultPort = '';
+ if (port && port !== '80' && port !== '443') {
+ // 如果存在非标准端口号,将其包含在URL中
+ defaultPort = `:${port}`;
+ }
+
+ if (protocol !== 'http:' && protocol !== 'https:') {
+ return;
+ }
+ // 根据当前协议构建WebSocket URL,并包括端口号(如果有)
+ let wsUrl = `ws://${loc}${defaultPort}/api/webgalsync`;
+ if (protocol === 'https:') {
+ wsUrl = `wss://${loc}${defaultPort}/api/webgalsync`;
+ }
+ logger.info('正在启动socket连接位于:' + wsUrl);
+ const socket = new WebSocket(wsUrl);
+ socket.onopen = () => {
+ logger.info('socket已连接');
+ function sendStageSyncMessage() {
+ const message: IDebugMessage = {
+ event: 'message',
+ data: {
+ command: DebugCommand.SYNCFC,
+ sceneMsg: {
+ scene: WebGAL.sceneManager.sceneData.currentScene.sceneName,
+ sentence: WebGAL.sceneManager.sceneData.currentSentenceId,
+ },
+ stageSyncMsg: webgalStore.getState().stage,
+ message: 'sync',
+ },
+ };
+ socket.send(JSON.stringify(message));
+ // logger.debug('传送信息', message);
+ setTimeout(sendStageSyncMessage, 1000);
+ }
+ sendStageSyncMessage();
+ };
+ socket.onmessage = (e) => {
+ // logger.info('收到信息', e.data);
+ const str: string = e.data;
+ const data: IDebugMessage = JSON.parse(str);
+ const message = data.data;
+ if (message.command === DebugCommand.JUMP) {
+ syncWithOrigine(message.sceneMsg.scene, message.sceneMsg.sentence, message.message === 'exp');
+ }
+ if (message.command === DebugCommand.EXE_COMMAND) {
+ const command = message.message;
+ const scene = WebgalParser.parse(command, 'temp.txt', 'temp.txt');
+ const sentence = scene.sentenceList[0];
+ runScript(sentence);
+ }
+ if (message.command === DebugCommand.REFETCH_TEMPLATE_FILES) {
+ const title = document.getElementById('Title_enter_page');
+ if (title) {
+ title.style.display = 'none';
+ }
+ WebGAL.events.styleUpdate.emit();
+ }
+ };
+ socket.onerror = (e) => {
+ logger.info('当前没有连接到 Terre 编辑器');
+ };
+};
diff --git a/packages/webgal/src/Core/webgalCore.ts b/packages/webgal/src/Core/webgalCore.ts
new file mode 100644
index 000000000..f1becd487
--- /dev/null
+++ b/packages/webgal/src/Core/webgalCore.ts
@@ -0,0 +1,16 @@
+import { BacklogManager } from '@/Core/Modules/backlog';
+import mitt from 'mitt';
+import { SceneManager } from '@/Core/Modules/scene';
+import { AnimationManager } from '@/Core/Modules/animations';
+import { Gameplay } from './Modules/gamePlay';
+import { Events } from '@/Core/Modules/events';
+
+export class WebgalCore {
+ public sceneManager = new SceneManager();
+ public backlogManager = new BacklogManager(this.sceneManager);
+ public animationManager = new AnimationManager();
+ public gameplay = new Gameplay();
+ public gameName = '';
+ public gameKey = '';
+ public events = new Events();
+}
diff --git a/packages/webgal/src/Stage/AudioContainer/AudioContainer.tsx b/packages/webgal/src/Stage/AudioContainer/AudioContainer.tsx
new file mode 100644
index 000000000..81b20dafd
--- /dev/null
+++ b/packages/webgal/src/Stage/AudioContainer/AudioContainer.tsx
@@ -0,0 +1,130 @@
+import { useSelector } from 'react-redux';
+import { RootState, webgalStore } from '@/store/store';
+import { setStage } from '@/store/stageReducer';
+import { useEffect, useState } from 'react';
+import { logger } from '@/Core/util/logger';
+
+export const AudioContainer = () => {
+ const stageStore = useSelector((webgalStore: RootState) => webgalStore.stage);
+ const titleBgm = useSelector((webgalStore: RootState) => webgalStore.GUI.titleBgm);
+ const isShowTitle = useSelector((webgalStore: RootState) => webgalStore.GUI.showTitle);
+ const userDataState = useSelector((state: RootState) => state.userData);
+ const mainVol = userDataState.optionData.volumeMain;
+ const vocalVol = mainVol * 0.01 * userDataState.optionData.vocalVolume * 0.01 * stageStore.vocalVolume * 0.01;
+ const bgmVol = mainVol * 0.01 * userDataState.optionData.bgmVolume * 0.01 * stageStore.bgm.volume * 0.01;
+ const bgmEnter = stageStore.bgm.enter;
+ const uiSoundEffects = stageStore.uiSe;
+ const seVol = mainVol * 0.01 * (userDataState.optionData?.seVolume ?? 100) * 0.01;
+ const uiSeVol = mainVol * 0.01 * (userDataState.optionData.uiSeVolume ?? 50) * 0.01;
+ const isEnterGame = useSelector((state: RootState) => state.GUI.isEnterGame);
+
+ // 淡入淡出定时器
+ const [fadeTimer, setFadeTimer] = useState(setTimeout(() => {}, 0));
+
+ /**
+ * 淡入BGM
+ * @param bgm 背景音乐
+ * @param maxVol 最大音量
+ * @param time 淡入时间
+ */
+ const bgmFadeIn = (bgm: HTMLAudioElement, maxVol: number, time: number) => {
+ // 设置初始音量
+ time >= 0 ? (bgm.volume = 0) : (bgm.volume = maxVol);
+ // 设置音量递增时间间隔
+ const duration = 10;
+ // 计算每duration的音量增量
+ const volumeStep = (maxVol / time) * duration;
+ // 基于递归调用实现淡入淡出效果
+ const fade = () => {
+ const timer = setTimeout(() => {
+ if (bgm.volume + volumeStep >= maxVol) {
+ // 如果音量接近或达到最大值,则设置最终音量(淡入)
+ bgm.volume = maxVol;
+ } else if (bgm.volume + volumeStep <= 0) {
+ // 如果音量接近或达到最小值,则设置最终音量(淡出)
+ bgm.volume = 0;
+ // 淡出效果结束后,将 bgm 置空
+ webgalStore.dispatch(setStage({ key: 'bgm', value: { src: '', enter: 0, volume: 100 } }));
+ } else {
+ // 否则增加音量,并递归调用
+ bgm.volume += volumeStep;
+ fade();
+ }
+ }, duration);
+ // 将定时器引用存储到 fadeTimer 中
+ setFadeTimer(timer);
+ };
+ // 调用淡入淡出函数
+ fade();
+ };
+
+ useEffect(() => {
+ // 清除之前的淡入淡出定时器
+ clearTimeout(fadeTimer);
+ // 获取当前背景音乐元素
+ const bgmElement = document.getElementById('currentBgm') as HTMLAudioElement;
+ // 如果当前背景音乐元素存在,则淡入淡出
+ if (bgmElement) {
+ bgmEnter === 0 ? (bgmElement.volume = bgmVol) : bgmFadeIn(bgmElement, bgmVol, bgmEnter);
+ }
+ }, [isShowTitle, titleBgm, stageStore.bgm.src, bgmVol, bgmEnter]);
+
+ useEffect(() => {
+ logger.debug(`设置背景音量:${bgmVol}`);
+ }, [bgmVol]);
+
+ useEffect(() => {
+ logger.debug(`设置背景音量淡入时间: ${bgmEnter}`);
+ }, [bgmEnter]);
+
+ useEffect(() => {
+ logger.debug(`设置语音音量:${vocalVol}`);
+ const vocalElement: any = document.getElementById('currentVocal');
+ if (vocalElement) {
+ vocalElement.volume = vocalVol.toString();
+ }
+ }, [vocalVol]);
+
+ useEffect(() => {
+ if (uiSoundEffects === '') return;
+ const uiSeAudioElement = document.createElement('audio');
+ uiSeAudioElement.src = uiSoundEffects;
+ uiSeAudioElement.loop = false;
+ // 设置音量
+ if (!isNaN(uiSeVol)) {
+ uiSeAudioElement.volume = uiSeVol;
+ } else {
+ // 针对原来使用 WebGAL version <= 4.4.2 的用户数据中不存在UI音效音量的情况
+ logger.error('UI SE Vol is NaN');
+ uiSeAudioElement.volume = isNaN(seVol) ? mainVol / 100 : seVol / 100;
+ }
+ // 播放UI音效
+ uiSeAudioElement.play();
+ uiSeAudioElement.addEventListener('ended', () => {
+ // Processing after sound effects are played
+ uiSeAudioElement.remove();
+ });
+ webgalStore.dispatch(setStage({ key: 'uiSe', value: '' }));
+ }, [uiSoundEffects]);
+
+ useEffect(() => {
+ logger.debug(`设置音效音量: ${seVol}`);
+ }, [seVol]);
+
+ useEffect(() => {
+ logger.debug(`设置用户界面音效音量: ${uiSeVol}`);
+ }, [uiSeVol]);
+
+ return (
+
+ );
+};
diff --git a/packages/webgal/src/Stage/FigureContainer/FigureContainer.tsx b/packages/webgal/src/Stage/FigureContainer/FigureContainer.tsx
new file mode 100644
index 000000000..d2f30d6ca
--- /dev/null
+++ b/packages/webgal/src/Stage/FigureContainer/FigureContainer.tsx
@@ -0,0 +1,24 @@
+import styles from './figureContainer.module.scss';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+
+export const FigureContainer = () => {
+ const stageState = useSelector((state: RootState) => state.stage);
+ return (
+
+
+ {stageState.figNameLeft !== '' && (
+
+ )}
+
+
+ {stageState.figName !== '' &&
}
+
+
+ {stageState.figNameRight !== '' && (
+
+ )}
+
+
+ );
+};
diff --git a/packages/webgal/src/Stage/FigureContainer/figureContainer.module.scss b/packages/webgal/src/Stage/FigureContainer/figureContainer.module.scss
new file mode 100644
index 000000000..32a6d0875
--- /dev/null
+++ b/packages/webgal/src/Stage/FigureContainer/figureContainer.module.scss
@@ -0,0 +1,53 @@
+.FigureContainer_main {
+ z-index: 4;
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ display: flex;
+}
+
+.figurePic {
+ max-height: 100%;
+ max-width: 100%;
+ width: auto;
+ height: auto;
+ object-fit: cover;
+ animation: centerIn 0.5s ease-in-out forwards;
+}
+
+.figContainer {
+ width: 98%;
+ left: 1%;
+ right: 1%;
+ position: absolute;
+ display: flex;
+ top: 5px;
+ bottom: 0;
+ z-index: 4;
+ animation: fig_default_animation 0.3s;
+}
+
+.figContainerLeft {
+ justify-content: left;
+}
+
+.figContainerCenter {
+ justify-content: center;
+}
+
+.figContainerRight {
+ justify-content: right;
+}
+
+@keyframes fig_default_animation {
+ 0% {
+ opacity: 0;
+ }
+ 30% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
diff --git a/packages/webgal/src/Stage/FullScreenPerform/FullScreenPerform.tsx b/packages/webgal/src/Stage/FullScreenPerform/FullScreenPerform.tsx
new file mode 100644
index 000000000..6c6de634d
--- /dev/null
+++ b/packages/webgal/src/Stage/FullScreenPerform/FullScreenPerform.tsx
@@ -0,0 +1,19 @@
+import styles from './fullScreenPerform.module.scss';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+
+export const FullScreenPerform = () => {
+ const stageState = useSelector((state: RootState) => state.stage);
+ let stageWidth = '100%';
+ let stageHeight = '100%';
+ let top = '0';
+ if (stageState.enableFilm !== '') {
+ stageHeight = '76%';
+ top = '12%';
+ }
+ return (
+
+ );
+};
diff --git a/packages/webgal/src/Stage/FullScreenPerform/fullScreenPerform.module.scss b/packages/webgal/src/Stage/FullScreenPerform/fullScreenPerform.module.scss
new file mode 100644
index 000000000..e3039f6cf
--- /dev/null
+++ b/packages/webgal/src/Stage/FullScreenPerform/fullScreenPerform.module.scss
@@ -0,0 +1,125 @@
+.FullScreenPerform_main {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+}
+
+.FullScreenPerform_element {
+ position: absolute;
+ display: none;
+ width: 100%;
+ height: 100%;
+ z-index: 11;
+}
+
+.fullScreen_video {
+ position: absolute;
+ display: block;
+ width: 100%;
+ height: 100%;
+ z-index: 11;
+}
+
+@keyframes slideIn {
+ 0% {
+ transform: translateX(-100%);
+ opacity: 0;
+ }
+ 100% {
+ transform: translateX(0);
+ opacity: 1;
+ }
+}
+
+@keyframes typing {
+ from {
+ width: 0;
+ height: 0;
+ }
+ to {
+ width: 100%;
+ height: auto;
+ border-right:1px solid;
+ }
+}
+
+@keyframes blinkCursor {
+ 50% { border-color: transparent; }
+}
+
+@keyframes pixelateAnimation {
+ 0% {
+ filter: none;
+ opacity: 0;
+ }
+ 50% {
+ filter: blur(5px);
+ }
+ 100% {
+ filter: none;
+ opacity: 1;
+ }
+}
+
+@keyframes revealAnimation {
+ 0% {
+ opacity: 0;
+ clip-path: polygon(0% 0%, 0% 0%, 0% 100%, 0% 100%);
+ }
+ 100% {
+ opacity: 1;
+ clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
+ }
+}
+
+.fadeIn {
+ opacity: 0;
+ animation: intro_showSoftly 1.5s ease-out forwards;
+ font-family: "思源宋体", serif;
+}
+
+.slideIn {
+ opacity: 0;
+ animation: slideIn 1.5s forwards;
+ font-family: "思源宋体", serif;
+}
+
+.typingEffect {
+ display: block;
+ overflow: hidden;
+ white-space: pre-line;
+ width: 0;
+ animation: typing 1.5s forwards, blinkCursor 1s infinite 1.5s;
+ font-family: "思源宋体", serif;
+}
+
+.pixelateEffect {
+ opacity: 0;
+ animation: pixelateAnimation 3s forwards;
+ font-family: "思源宋体", serif;
+}
+
+.revealAnimation {
+ opacity: 0;
+ animation: revealAnimation 3s forwards;
+ font-family: "思源宋体", serif;
+}
+
+@keyframes intro_showSoftly {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+.videoContainer {
+ position: absolute;
+ display: block;
+ width: 100%;
+ height: 100%;
+ z-index: 11;
+ background: rgba(0, 0, 0, 1);
+}
diff --git a/packages/webgal/src/Stage/MainStage/MainStage.tsx b/packages/webgal/src/Stage/MainStage/MainStage.tsx
new file mode 100644
index 000000000..ac2c6eb0f
--- /dev/null
+++ b/packages/webgal/src/Stage/MainStage/MainStage.tsx
@@ -0,0 +1,13 @@
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { useSetBg } from '@/Stage/MainStage/useSetBg';
+import { useSetFigure } from '@/Stage/MainStage/useSetFigure';
+import { setStageObjectEffects } from '@/Stage/MainStage/useSetEffects';
+
+export function MainStage() {
+ const stageState = useSelector((state: RootState) => state.stage);
+ useSetBg(stageState);
+ useSetFigure(stageState);
+ setStageObjectEffects(stageState);
+ return
;
+}
diff --git a/packages/webgal/src/Stage/MainStage/useSetBg.ts b/packages/webgal/src/Stage/MainStage/useSetBg.ts
new file mode 100644
index 000000000..a292c0dd4
--- /dev/null
+++ b/packages/webgal/src/Stage/MainStage/useSetBg.ts
@@ -0,0 +1,64 @@
+import { IStageState } from '@/store/stageInterface';
+import { useEffect } from 'react';
+import { logger } from '@/Core/util/logger';
+import { IStageObject } from '@/Core/controller/stage/pixi/PixiController';
+import { setEbg } from '@/Core/gameScripts/changeBg/setEbg';
+
+import { getEnterExitAnimation } from '@/Core/Modules/animationFunctions';
+import { WebGAL } from '@/Core/WebGAL';
+
+export function useSetBg(stageState: IStageState) {
+ const bgName = stageState.bgName;
+
+ /**
+ * 设置背景
+ */
+ useEffect(() => {
+ const thisBgKey = 'bg-main';
+ if (bgName !== '') {
+ const currentBg = WebGAL.gameplay.pixiStage?.getStageObjByKey(thisBgKey);
+ if (currentBg) {
+ if (currentBg.sourceUrl !== bgName) {
+ removeBg(currentBg);
+ }
+ }
+ addBg(undefined, thisBgKey, bgName);
+ setEbg(bgName);
+ logger.debug('重设背景');
+ const { duration, animation } = getEnterExitAnimation('bg-main', 'enter', true);
+ WebGAL.gameplay.pixiStage!.registerPresetAnimation(animation, 'bg-main-softin', thisBgKey, stageState.effects);
+ setTimeout(() => WebGAL.gameplay.pixiStage!.removeAnimationWithSetEffects('bg-main-softin'), duration);
+ } else {
+ const currentBg = WebGAL.gameplay.pixiStage?.getStageObjByKey(thisBgKey);
+ if (currentBg) {
+ removeBg(currentBg);
+ }
+ }
+ }, [bgName]);
+}
+
+function removeBg(bgObject: IStageObject) {
+ WebGAL.gameplay.pixiStage?.removeAnimationWithSetEffects('bg-main-softin');
+ const oldBgKey = bgObject.key;
+ bgObject.key = 'bg-main-off' + String(new Date().getTime());
+ const bgKey = bgObject.key;
+ const bgAniKey = bgObject.key + '-softoff';
+ WebGAL.gameplay.pixiStage?.removeStageObjectByKey(oldBgKey);
+ const { duration, animation } = getEnterExitAnimation('bg-main-off', 'exit', true, bgKey);
+ WebGAL.gameplay.pixiStage!.registerAnimation(animation, bgAniKey, bgKey);
+ setTimeout(() => {
+ WebGAL.gameplay.pixiStage?.removeAnimation(bgAniKey);
+ WebGAL.gameplay.pixiStage?.removeStageObjectByKey(bgKey);
+ }, duration);
+}
+
+function addBg(type?: 'image' | 'spine', ...args: any[]) {
+ const url = args[1];
+ if (url.endsWith('.skel')) {
+ // @ts-ignore
+ return WebGAL.gameplay.pixiStage?.addSpineBg(...args);
+ } else {
+ // @ts-ignore
+ return WebGAL.gameplay.pixiStage?.addBg(...args);
+ }
+}
diff --git a/packages/webgal/src/Stage/MainStage/useSetEffects.ts b/packages/webgal/src/Stage/MainStage/useSetEffects.ts
new file mode 100644
index 000000000..796b461cd
--- /dev/null
+++ b/packages/webgal/src/Stage/MainStage/useSetEffects.ts
@@ -0,0 +1,43 @@
+import { baseTransform, IEffect, IStageState, ITransform } from '@/store/stageInterface';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+export function setStageObjectEffects(stageState: IStageState) {
+ const effects = stageState.effects;
+ setTimeout(() => {
+ setStageEffects(effects);
+ }, 10);
+}
+
+export function setStageEffects(effects: IEffect[]) {
+ const stageObjects = WebGAL.gameplay.pixiStage?.getAllStageObj() ?? [];
+ for (const stageObj of stageObjects) {
+ const key = stageObj.key;
+ const effect = effects.find((effect) => effect.target === key);
+ const lockedStageTargets = WebGAL.gameplay.pixiStage?.getAllLockedObject() ?? [];
+ if (!lockedStageTargets.includes(key)) {
+ if (effect) {
+ // logger.debug('应用effects', key);
+ const targetPixiContainer = WebGAL.gameplay.pixiStage?.getStageObjByKey(key);
+ if (targetPixiContainer) {
+ const container = targetPixiContainer.pixiContainer;
+ Object.assign(container, convertTransform(effect.transform));
+ }
+ } else {
+ const targetPixiContainer = WebGAL.gameplay.pixiStage?.getStageObjByKey(key);
+ if (targetPixiContainer) {
+ const container = targetPixiContainer.pixiContainer;
+ Object.assign(container, convertTransform(baseTransform));
+ }
+ }
+ }
+ }
+}
+
+function convertTransform(transform: ITransform | undefined) {
+ if (!transform) {
+ return {};
+ }
+ const { position, alpha, ...rest } = transform;
+ return { ...rest, x: position.x, y: position.y, alphaFilterVal: alpha };
+}
diff --git a/packages/webgal/src/Stage/MainStage/useSetFigure.ts b/packages/webgal/src/Stage/MainStage/useSetFigure.ts
new file mode 100644
index 000000000..425323a93
--- /dev/null
+++ b/packages/webgal/src/Stage/MainStage/useSetFigure.ts
@@ -0,0 +1,228 @@
+import { IEffect, IStageState } from '@/store/stageInterface';
+import { useEffect } from 'react';
+import { logger } from '@/Core/util/logger';
+import { generateUniversalSoftInAnimationObj } from '@/Core/controller/stage/pixi/animations/universalSoftIn';
+import { IStageObject } from '@/Core/controller/stage/pixi/PixiController';
+import { generateUniversalSoftOffAnimationObj } from '@/Core/controller/stage/pixi/animations/universalSoftOff';
+
+import { getEnterExitAnimation } from '@/Core/Modules/animationFunctions';
+import { WebGAL } from '@/Core/WebGAL';
+
+export function useSetFigure(stageState: IStageState) {
+ const { figNameLeft, figName, figNameRight, freeFigure, live2dMotion, live2dExpression } = stageState;
+
+ /**
+ * 同步 motion
+ */
+ useEffect(() => {
+ for (const motion of live2dMotion) {
+ WebGAL.gameplay.pixiStage?.changeModelMotionByKey(motion.target, motion.motion);
+ }
+ }, [live2dMotion]);
+
+ /**
+ * 同步 expression
+ */
+ useEffect(() => {
+ for (const expression of live2dExpression) {
+ WebGAL.gameplay.pixiStage?.changeModelExpressionByKey(expression.target, expression.expression);
+ }
+ }, [live2dExpression]);
+
+ /**
+ * 设置立绘
+ */
+ useEffect(() => {
+ /**
+ * 特殊处理:中间立绘
+ */
+ const thisFigKey = 'fig-center';
+ const softInAniKey = 'fig-center-softin';
+ if (figName !== '') {
+ const currentFigCenter = WebGAL.gameplay.pixiStage?.getStageObjByKey(thisFigKey);
+ if (currentFigCenter) {
+ if (currentFigCenter.sourceUrl !== figName) {
+ removeFig(currentFigCenter, softInAniKey, stageState.effects);
+ }
+ }
+ addFigure(undefined, thisFigKey, figName, 'center');
+ logger.debug('中立绘已重设');
+ const { duration, animation } = getEnterExitAnimation(thisFigKey, 'enter');
+ WebGAL.gameplay.pixiStage!.registerPresetAnimation(animation, softInAniKey, thisFigKey, stageState.effects);
+ setTimeout(() => WebGAL.gameplay.pixiStage!.removeAnimationWithSetEffects(softInAniKey), duration);
+ } else {
+ logger.debug('移除中立绘');
+ const currentFigCenter = WebGAL.gameplay.pixiStage?.getStageObjByKey(thisFigKey);
+ if (currentFigCenter) {
+ if (currentFigCenter.sourceUrl !== figName) {
+ removeFig(currentFigCenter, softInAniKey, stageState.effects);
+ }
+ }
+ }
+ }, [figName]);
+
+ useEffect(() => {
+ /**
+ * 特殊处理:左侧立绘
+ */
+ const thisFigKey = 'fig-left';
+ const softInAniKey = 'fig-left-softin';
+ if (figNameLeft !== '') {
+ const currentFigLeft = WebGAL.gameplay.pixiStage?.getStageObjByKey(thisFigKey);
+ if (currentFigLeft) {
+ if (currentFigLeft.sourceUrl !== figNameLeft) {
+ removeFig(currentFigLeft, softInAniKey, stageState.effects);
+ }
+ }
+ addFigure(undefined, thisFigKey, figNameLeft, 'left');
+ logger.debug('左立绘已重设');
+ const { duration, animation } = getEnterExitAnimation(thisFigKey, 'enter');
+ WebGAL.gameplay.pixiStage!.registerPresetAnimation(animation, softInAniKey, thisFigKey, stageState.effects);
+ setTimeout(() => WebGAL.gameplay.pixiStage!.removeAnimationWithSetEffects(softInAniKey), duration);
+ } else {
+ logger.debug('移除左立绘');
+ const currentFigLeft = WebGAL.gameplay.pixiStage?.getStageObjByKey(thisFigKey);
+ if (currentFigLeft) {
+ if (currentFigLeft.sourceUrl !== figNameLeft) {
+ removeFig(currentFigLeft, softInAniKey, stageState.effects);
+ }
+ }
+ }
+ }, [figNameLeft]);
+
+ useEffect(() => {
+ /**
+ * 特殊处理:右侧立绘
+ */
+ const thisFigKey = 'fig-right';
+ const softInAniKey = 'fig-right-softin';
+ if (figNameRight !== '') {
+ const currentFigRight = WebGAL.gameplay.pixiStage?.getStageObjByKey(thisFigKey);
+ if (currentFigRight) {
+ if (currentFigRight.sourceUrl !== figNameRight) {
+ removeFig(currentFigRight, softInAniKey, stageState.effects);
+ }
+ }
+ addFigure(undefined, thisFigKey, figNameRight, 'right');
+ logger.debug('右立绘已重设');
+ const { duration, animation } = getEnterExitAnimation(thisFigKey, 'enter');
+ WebGAL.gameplay.pixiStage!.registerPresetAnimation(animation, softInAniKey, thisFigKey, stageState.effects);
+ setTimeout(() => WebGAL.gameplay.pixiStage!.removeAnimationWithSetEffects(softInAniKey), duration);
+ } else {
+ const currentFigRight = WebGAL.gameplay.pixiStage?.getStageObjByKey(thisFigKey);
+ if (currentFigRight) {
+ if (currentFigRight.sourceUrl !== figNameRight) {
+ removeFig(currentFigRight, softInAniKey, stageState.effects);
+ }
+ }
+ }
+ }, [figNameRight]);
+
+ useEffect(() => {
+ // 自由立绘
+ for (const fig of freeFigure) {
+ /**
+ * 特殊处理:自由立绘
+ */
+ const thisFigKey = `${fig.key}`;
+ const softInAniKey = `${fig.key}-softin`;
+ /**
+ * 非空
+ */
+ if (fig.name !== '') {
+ const currentFigThisKey = WebGAL.gameplay.pixiStage?.getStageObjByKey(thisFigKey);
+ if (currentFigThisKey) {
+ if (currentFigThisKey.sourceUrl !== fig.name) {
+ removeFig(currentFigThisKey, softInAniKey, stageState.effects);
+ addFigure(undefined, thisFigKey, fig.name, fig.basePosition);
+ logger.debug(`${fig.key}立绘已重设`);
+ const { duration, animation } = getEnterExitAnimation(thisFigKey, 'enter');
+ WebGAL.gameplay.pixiStage!.registerPresetAnimation(animation, softInAniKey, thisFigKey, stageState.effects);
+ setTimeout(() => WebGAL.gameplay.pixiStage!.removeAnimationWithSetEffects(softInAniKey), duration);
+ }
+ } else {
+ addFigure(undefined, thisFigKey, fig.name, fig.basePosition);
+ logger.debug(`${fig.key}立绘已重设`);
+ const { duration, animation } = getEnterExitAnimation(thisFigKey, 'enter');
+ WebGAL.gameplay.pixiStage!.registerPresetAnimation(animation, softInAniKey, thisFigKey, stageState.effects);
+ setTimeout(() => WebGAL.gameplay.pixiStage!.removeAnimationWithSetEffects(softInAniKey), duration);
+ }
+ } else {
+ const currentFigThisKey = WebGAL.gameplay.pixiStage?.getStageObjByKey(thisFigKey);
+ if (currentFigThisKey) {
+ if (currentFigThisKey.sourceUrl !== fig.name) {
+ removeFig(currentFigThisKey, softInAniKey, stageState.effects);
+ }
+ }
+ }
+ }
+
+ /**
+ * 移除不在状态表中的立绘
+ */
+ const currentFigures = WebGAL.gameplay.pixiStage?.getFigureObjects();
+ if (currentFigures) {
+ for (const existFigure of currentFigures) {
+ if (
+ existFigure.key === 'fig-left' ||
+ existFigure.key === 'fig-center' ||
+ existFigure.key === 'fig-right' ||
+ existFigure.key.endsWith('-off')
+ ) {
+ // 什么也不做
+ } else {
+ const existKey = existFigure.key;
+ const existFigInState = freeFigure.findIndex((fig) => fig.key === existKey);
+ if (existFigInState < 0) {
+ const softInAniKey = `${existFigure.key}-softin`;
+ removeFig(existFigure, softInAniKey, stageState.effects);
+ }
+ }
+ }
+ }
+ }, [freeFigure]);
+}
+
+function removeFig(figObj: IStageObject, enterTikerKey: string, effects: IEffect[]) {
+ WebGAL.gameplay.pixiStage?.removeAnimationWithSetEffects(enterTikerKey);
+ // 快进,跳过退出动画
+ if (WebGAL.gameplay.isFast) {
+ logger.debug('快速模式,立刻关闭立绘');
+ WebGAL.gameplay.pixiStage?.removeStageObjectByKey(figObj.key);
+ return;
+ }
+ const oldFigKey = figObj.key;
+ const figLeaveAniKey = oldFigKey + '-off';
+ figObj.key = oldFigKey + String(new Date().getTime()) + '-off';
+ const figKey = figObj.key;
+ WebGAL.gameplay.pixiStage?.removeStageObjectByKey(oldFigKey);
+ const leaveKey = figKey + '-softoff';
+ const { duration, animation } = getEnterExitAnimation(figLeaveAniKey, 'exit', false, figKey);
+ WebGAL.gameplay.pixiStage!.registerPresetAnimation(animation, leaveKey, figKey, effects);
+ setTimeout(() => {
+ WebGAL.gameplay.pixiStage?.removeAnimation(leaveKey);
+ WebGAL.gameplay.pixiStage?.removeStageObjectByKey(figKey);
+ }, duration);
+}
+
+function addFigure(type?: 'image' | 'live2D' | 'spine', ...args: any[]) {
+ const url = args[1];
+ if (url.endsWith('.json')) {
+ return addLive2dFigure(...args);
+ } else if (url.endsWith('.skel')) {
+ // @ts-ignore
+ return WebGAL.gameplay.pixiStage?.addSpineFigure(...args);
+ } else {
+ // @ts-ignore
+ return WebGAL.gameplay.pixiStage?.addFigure(...args);
+ }
+}
+
+/**
+ * 如果要使用 Live2D,取消这里的注释
+ * @param args
+ */
+function addLive2dFigure(...args: any[]) {
+ // @ts-ignore
+ // return WebGAL.gameplay.pixiStage?.addLive2dFigure(...args);
+}
diff --git a/packages/webgal/src/Stage/OldStage/OldStage.tsx b/packages/webgal/src/Stage/OldStage/OldStage.tsx
new file mode 100644
index 000000000..fd3b0ccd9
--- /dev/null
+++ b/packages/webgal/src/Stage/OldStage/OldStage.tsx
@@ -0,0 +1,66 @@
+// import styles from '@/Components/Stage/stage.module.scss';
+// import { FigureContainer } from '@/Components/Stage/FigureContainer/FigureContainer';
+// import { useEffect } from 'react';
+// import { IEffect } from '@/store/stageInterface';
+// import { useSelector } from 'react-redux';
+// import { RootState } from '@/store/store';
+
+export default function OldStage() {
+ // const stageState = useSelector((state: RootState) => state.stage);
+ // const oldBg = useSelector((state: RootState) => state.stageTemp.oldBg);
+ // const oldBgKey = useSelector((state: RootState) => state.stageTemp.oldBgKey);
+ //
+ // /**
+ // * 设置效果
+ // */
+ // useEffect(() => {
+ // const effectList: Array = stageState.effects;
+ // setTimeout(() => {
+ // effectList.forEach((effect) => {
+ // const target = document.getElementById(effect.target);
+ // if (target) {
+ // if (effect.filter !== '') {
+ // target.style.filter = effect.filter;
+ // }
+ // if (effect.transform !== '') {
+ // target.style.transform = effect.transform;
+ // }
+ // }
+ // });
+ // }, 100);
+ // });
+ //
+ // let stageWidth = '100%';
+ // let stageHeight = '100%';
+ // let top = '0';
+ // if (stageState.enableFilm !== '') {
+ // stageHeight = '76%';
+ // top = '12%';
+ // }
+ //
+ // return (
+ //
+ // {oldBg !== '' && (
+ //
+ // )}
+ //
+ //
+ //
+ // );
+}
diff --git a/packages/webgal/src/Stage/Stage.tsx b/packages/webgal/src/Stage/Stage.tsx
new file mode 100644
index 000000000..b782a7777
--- /dev/null
+++ b/packages/webgal/src/Stage/Stage.tsx
@@ -0,0 +1,138 @@
+import React, { FC } from 'react';
+import styles from './stage.module.scss';
+import { TextBox } from './TextBox/TextBox';
+import { AudioContainer } from './AudioContainer/AudioContainer';
+import { FullScreenPerform } from './FullScreenPerform/FullScreenPerform';
+import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
+import { stopAll } from '@/Core/controller/gamePlay/fastSkip';
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { setVisibility } from '@/store/GUIReducer';
+import { TextBoxFilm } from '@/Stage/TextBox/TextBoxFilm';
+import { useHotkey } from '@/hooks/useHotkey';
+import { MainStage } from '@/Stage/MainStage/MainStage';
+import IntroContainer from '@/Stage/introContainer/IntroContainer';
+import { isIOS } from '@/Core/initializeScript';
+import { WebGAL } from '@/Core/WebGAL';
+import { IGuiState } from '@/store/guiInterface';
+import { IStageState } from '@/store/stageInterface';
+// import OldStage from '@/Components/Stage/OldStage/OldStage';
+
+function inTextBox(event: React.MouseEvent) {
+ const tb = document.getElementById('textBoxMain');
+ if (!tb) {
+ return false;
+ }
+ let bounds = tb.getBoundingClientRect();
+ return (
+ event.clientX > bounds.left &&
+ event.clientX < bounds.right &&
+ event.clientY > bounds.top &&
+ event.clientY < bounds.bottom
+ );
+}
+
+function checkMousePosition(event: React.MouseEvent, GUIState: IGuiState, dispatch: ReturnType) {
+ if (!GUIState.controlsVisibility && inTextBox(event)) {
+ dispatch(setVisibility({ component: 'controlsVisibility', visibility: true }));
+ }
+ if (GUIState.controlsVisibility && !inTextBox(event)) {
+ dispatch(setVisibility({ component: 'controlsVisibility', visibility: false }));
+ }
+}
+
+function isTextboxHidden(stageState: IStageState, GUIState: IGuiState) {
+ if (!GUIState.showTextBox) {
+ return true;
+ }
+
+ if (stageState.isDisableTextbox) {
+ return true;
+ }
+
+ const isText = stageState.showText !== '' || stageState.showName !== '';
+ if (!isText) {
+ return true;
+ }
+
+ const isInIntro = document.getElementById('introContainer')?.style.display === 'block';
+ if (isInIntro) {
+ return true;
+ }
+
+ return false;
+}
+
+let timeoutEventHandle: ReturnType | null = null;
+
+/**
+ * 检查并更新控制可见性
+ * @param event 鼠标移动事件
+ * @param stageState 场景状态
+ * @param GUIState GUI状态
+ * @param dispatch Redux dispatch函数
+ */
+// eslint-disable-next-line max-params
+function updateControlsVisibility(
+ event: React.MouseEvent,
+ stageState: IStageState,
+ GUIState: IGuiState,
+ dispatch: ReturnType,
+) {
+ if (isTextboxHidden(stageState, GUIState)) {
+ // 当文本框被隐藏时
+ // 逻辑:鼠标移动时显示,一段时间(默认:1秒)后隐藏
+ if (timeoutEventHandle) {
+ clearTimeout(timeoutEventHandle);
+ }
+
+ dispatch(setVisibility({ component: 'controlsVisibility', visibility: true }));
+ timeoutEventHandle = setTimeout(() => {
+ dispatch(setVisibility({ component: 'controlsVisibility', visibility: false }));
+ }, 1000);
+ } else {
+ // 当文本框正常显示时
+ // 逻辑:鼠标位置在文本框内时显示
+ checkMousePosition(event, GUIState, dispatch);
+ }
+}
+
+export const Stage: FC = () => {
+ const stageState = useSelector((state: RootState) => state.stage);
+ const GUIState = useSelector((state: RootState) => state.GUI);
+ const dispatch = useDispatch();
+
+ useHotkey();
+
+ return (
+
+
+ {/* 已弃用旧的立绘与背景舞台 */}
+ {/*
*/}
+
+
+
+ {GUIState.showTextBox && stageState.enableFilm === '' && !stageState.isDisableTextbox &&
}
+ {GUIState.showTextBox && stageState.enableFilm !== '' &&
}
+
+
{
+ // 如果文本框没有显示,则显示文本框
+ if (!GUIState.showTextBox) {
+ dispatch(setVisibility({ component: 'showTextBox', visibility: true }));
+ return;
+ }
+ stopAll();
+ nextSentence();
+ }}
+ onDoubleClick={() => {
+ WebGAL.events.fullscreenDbClick.emit();
+ }}
+ id="FullScreenClick"
+ style={{ width: '100%', height: '100%', position: 'absolute', zIndex: '12', top: '0' }}
+ onMouseMove={(e) => !GUIState.showControls && updateControlsVisibility(e, stageState, GUIState, dispatch)}
+ />
+
+
+ );
+};
diff --git a/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx b/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx
new file mode 100644
index 000000000..9381649cc
--- /dev/null
+++ b/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx
@@ -0,0 +1,266 @@
+import styles from './textbox.module.scss';
+import { ReactNode, useEffect } from 'react';
+import { WebGAL } from '@/Core/WebGAL';
+import { ITextboxProps } from './types';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import { css } from '@emotion/css';
+import { textSize } from '@/store/userDataInterface';
+
+export default function IMSSTextbox(props: ITextboxProps) {
+ const {
+ textArray,
+ textDelay,
+ currentConcatDialogPrev,
+ currentDialogKey,
+ isText,
+ isSafari,
+ isFirefox: boolean,
+ fontSize,
+ miniAvatar,
+ isHasName,
+ showName,
+ font,
+ textDuration,
+ isUseStroke,
+ textboxOpacity,
+ textSizeState,
+ } = props;
+
+ const applyStyle = useApplyStyle('Stage/TextBox/textbox.scss');
+
+ useEffect(() => {
+ function settleText() {
+ const textElements = document.querySelectorAll('.Textelement_start');
+ const textArray = [...textElements];
+ textArray.forEach((e) => {
+ e.className = applyStyle('TextBox_textElement_Settled', styles.TextBox_textElement_Settled);
+ });
+ }
+
+ WebGAL.events.textSettle.on(settleText);
+ return () => {
+ WebGAL.events.textSettle.off(settleText);
+ };
+ }, []);
+ let allTextIndex = 0;
+ const nameElementList = showName.map((line, index) => {
+ const textline = line.map((en, index) => {
+ const e = en.reactNode;
+ let style = '';
+ let tips = '';
+ let style_alltext = '';
+ let isEnhanced = false;
+ if (en.enhancedValue) {
+ isEnhanced = true;
+ const data = en.enhancedValue;
+ for (const dataElem of data) {
+ const { key, value } = dataElem;
+ switch (key) {
+ case 'style':
+ style = value;
+ break;
+ case 'tips':
+ tips = value;
+ break;
+ case 'style-alltext':
+ style_alltext = value;
+ break;
+ }
+ }
+ }
+ const styleClassName = ' ' + css(style, { label: 'showname' });
+ const styleAllText = ' ' + css(style_alltext, { label: 'showname' });
+ if (isEnhanced) {
+ return (
+
+
+ {e}
+ {e}
+ {isUseStroke && {e} }
+
+
+ );
+ }
+ return (
+
+
+ {e}
+ {e}
+ {isUseStroke && {e} }
+
+
+ );
+ });
+ return (
+
+ {textline}
+
+ );
+ });
+ const textElementList = textArray.map((line, index) => {
+ const textLine = line.map((en, index) => {
+ const e = en.reactNode;
+ let style = '';
+ let tips = '';
+ let style_alltext = '';
+ if (en.enhancedValue) {
+ const data = en.enhancedValue;
+ for (const dataElem of data) {
+ const { key, value } = dataElem;
+ switch (key) {
+ case 'style':
+ style = value;
+ break;
+ case 'tips':
+ tips = value;
+ break;
+ case 'style-alltext':
+ style_alltext = value;
+ break;
+ }
+ }
+ }
+ // if (e === '
') {
+ // return
;
+ // }
+ let delay = allTextIndex * textDelay;
+ allTextIndex++;
+ let prevLength = currentConcatDialogPrev.length;
+ if (currentConcatDialogPrev !== '' && allTextIndex >= prevLength) {
+ delay = delay - prevLength * textDelay;
+ }
+ const styleClassName = ' ' + css(style);
+ const styleAllText = ' ' + css(style_alltext);
+ if (allTextIndex < prevLength) {
+ return (
+
+
+ {e}
+ {e}
+ {isUseStroke && {e} }
+
+
+ );
+ }
+ return (
+
+
+ {e}
+ {e}
+ {isUseStroke && {e} }
+
+
+ );
+ });
+ return (
+
+ {textLine}
+
+ );
+ });
+
+ return (
+ <>
+ {isText && (
+
+
+
+
+ {miniAvatar !== '' && (
+
+ )}
+
+ {isHasName && (
+ <>
+
+ {nameElementList}
+
+
+ {nameElementList}
+
+ >
+ )}
+
+ {textElementList}
+
+
+
+ )}
+ >
+ );
+}
diff --git a/packages/webgal/src/Stage/TextBox/TextBox.tsx b/packages/webgal/src/Stage/TextBox/TextBox.tsx
new file mode 100644
index 000000000..9e33b072d
--- /dev/null
+++ b/packages/webgal/src/Stage/TextBox/TextBox.tsx
@@ -0,0 +1,277 @@
+import { ReactNode, useEffect, useState } from 'react';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { useFontFamily } from '@/hooks/useFontFamily';
+import { useTextAnimationDuration, useTextDelay } from '@/hooks/useTextOptions';
+import { getTextSize } from '@/UI/getTextSize';
+import { match } from '@/Core/util/match';
+import { textSize } from '@/store/userDataInterface';
+import IMSSTextbox from '@/Stage/TextBox/IMSSTextbox';
+import { SCREEN_CONSTANTS } from '@/Core/util/constants';
+import useEscape from '@/hooks/useEscape';
+
+const userAgent = navigator.userAgent;
+const isFirefox = /firefox/i.test(userAgent);
+const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
+
+export interface EnhancedNode {
+ reactNode: ReactNode;
+ enhancedValue?: { key: string; value: string }[];
+}
+
+export const TextBox = () => {
+ const [isShowStroke, setIsShowStroke] = useState(true);
+
+ useEffect(() => {
+ const handleResize = () => {
+ const targetHeight = SCREEN_CONSTANTS.height;
+ const targetWidth = SCREEN_CONSTANTS.width;
+
+ const h = window.innerHeight; // 窗口高度
+ const w = window.innerWidth; // 窗口宽度
+ const zoomH = h / targetHeight; // 以窗口高度为基准的变换比
+ const zoomW = w / targetWidth; // 以窗口宽度为基准的变换比
+ const zoomH2 = w / targetHeight; // 竖屏时以窗口高度为基础的变换比
+ const zoomW2 = h / targetWidth; // 竖屏时以窗口宽度为基础的变换比
+ [zoomH, zoomW, zoomH2, zoomW2].forEach((e) => {
+ if (e <= 0.2) {
+ setIsShowStroke(false);
+ } else {
+ setIsShowStroke(true);
+ }
+ });
+ };
+ window.addEventListener('resize', handleResize);
+ handleResize();
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ };
+ }, []);
+
+ const stageState = useSelector((state: RootState) => state.stage);
+ const userDataState = useSelector((state: RootState) => state.userData);
+ const textDelay = useTextDelay(userDataState.optionData.textSpeed);
+ const textDuration = useTextAnimationDuration(userDataState.optionData.textSpeed);
+ let size = getTextSize(userDataState.optionData.textSize) + '%';
+ const font = useFontFamily();
+ const isText = stageState.showText !== '' || stageState.showName !== '';
+ let textSizeState = userDataState.optionData.textSize;
+ if (isText && stageState.showTextSize !== -1) {
+ size = getTextSize(stageState.showTextSize) + '%';
+ textSizeState = stageState.showTextSize;
+ }
+ const lineLimit = match(textSizeState)
+ .with(textSize.small, () => 3)
+ .with(textSize.medium, () => 2)
+ .with(textSize.large, () => 2)
+ .default(() => 2);
+ // 拆字
+ const textArray = compileSentence(stageState.showText, lineLimit);
+ const isHasName = stageState.showName !== '';
+ const showName = compileSentence(stageState.showName, lineLimit);
+ const currentConcatDialogPrev = stageState.currentConcatDialogPrev;
+ const currentDialogKey = stageState.currentDialogKey;
+ const miniAvatar = stageState.miniAvatar;
+ const textboxOpacity = userDataState.optionData.textboxOpacity;
+ const Textbox = IMSSTextbox;
+ return (
+
+ );
+};
+
+function isCJK(character: string) {
+ return !!character.match(/[\u4e00-\u9fa5]|[\u0800-\u4e00]|[\uac00-\ud7ff]/);
+}
+
+export function compileSentence(sentence: string, lineLimit: number, ignoreLineLimit?: boolean): EnhancedNode[][] {
+ // 先拆行
+ const lines = sentence.split(/(? useEscape(val));
+ // 对每一行进行注音处理
+ const rubyLines = lines.map((line) => parseString(line));
+ const nodeLines = rubyLines.map((line) => {
+ const ln: EnhancedNode[] = [];
+ line.forEach((node, index) => {
+ match(node.type)
+ .with(SegmentType.String, () => {
+ const chars = splitChars(node.value as string);
+ // eslint-disable-next-line max-nested-callbacks
+ ln.push(...chars.map((c) => ({ reactNode: c })));
+ })
+ .endsWith(SegmentType.Link, () => {
+ const val = node.value as EnhancedValue;
+ const enhancedNode = (
+
+
+ {val.text}
+ {val.ruby}
+
+
+ );
+ ln.push({ reactNode: enhancedNode, enhancedValue: val.values });
+ });
+ });
+ return ln;
+ });
+ return nodeLines.slice(0, ignoreLineLimit ? undefined : lineLimit);
+}
+
+/**
+ * @param sentence
+ */
+export function splitChars(sentence: string) {
+ if (!sentence) return [''];
+ const words: string[] = [];
+ let word = '';
+ let cjkFlag = isCJK(sentence[0]);
+
+ const isPunctuation = (ch: string): boolean => {
+ const regex = /[!-\/:-@\[-`{-~\u2000-\u206F\u3000-\u303F\uff00-\uffef]/g;
+ return regex.test(ch);
+ };
+
+ for (const character of sentence) {
+ // if (character === '|') {
+ // if (word) {
+ // words.push(word);
+ // word = '';
+ // }
+ // words.push('
');
+ // cjkFlag = false;
+ // continue;
+ // }
+ if (character === ' ') {
+ // Space
+ if (word) {
+ words.push(word);
+ word = '';
+ }
+ words.push('\u00a0');
+ cjkFlag = false;
+ } else if (isCJK(character) && !isPunctuation(character)) {
+ if (!cjkFlag && word) {
+ words.push(word);
+ word = '';
+ }
+ words.push(character);
+ cjkFlag = true;
+ } else {
+ if (isPunctuation(character)) {
+ if (word) {
+ // If it is a punctuation and there is a preceding word, add it to the word
+ word += character;
+ words.push(word);
+ word = '';
+ } else if (words.length > 0) {
+ // If no preceding word in the current iteration, but there are already words in the array, append to the last word
+ words[words.length - 1] += character;
+ } else {
+ // If no preceding word, still add the punctuation as a new word
+ words.push(character);
+ }
+ continue;
+ }
+
+ if (cjkFlag && word) {
+ words.push(word);
+ word = '';
+ }
+ word += character;
+ cjkFlag = false;
+ }
+ }
+
+ if (word) {
+ words.push(word);
+ }
+
+ return words;
+}
+
+enum SegmentType {
+ String = 'SegmentType.String',
+ Link = 'SegmentType.Link',
+}
+
+interface EnhancedValue {
+ text: string;
+ ruby: string;
+ values: { key: string; value: string }[];
+}
+
+interface Segment {
+ type: SegmentType;
+ value?: string | EnhancedValue;
+}
+
+function parseString(input: string): Segment[] {
+ const regex = /(\[(.*?)\]\((.*?)\))|([^\[\]]+)/g;
+ const result: Segment[] = [];
+ let match: RegExpExecArray | null;
+
+ while ((match = regex.exec(input)) !== null) {
+ if (match[1]) {
+ // 链接部分
+ const text = match[2];
+ const enhance = match[3];
+ let parsedEnhanced: KeyValuePair[] = [];
+ let ruby = '';
+ if (enhance.match(/style=|tips=|ruby=|style-alltext=/)) {
+ parsedEnhanced = parseEnhancedString(enhance);
+ const rubyKvPair = parsedEnhanced.find((e) => e.key === 'ruby');
+ if (rubyKvPair) {
+ ruby = rubyKvPair.value;
+ }
+ } else {
+ ruby = enhance;
+ }
+ result.push({ type: SegmentType.Link, value: { text, ruby, values: parsedEnhanced } });
+ } else {
+ // 普通文本
+ const text = match[0];
+ result.push({ type: SegmentType.String, value: text });
+ }
+ }
+
+ // 我也不知道为什么,不加这个就会导致在 Enhanced Value 处于行首时故障
+ // 你可以认为这个代码不明所以,但是不要删除
+ result.unshift({ type: SegmentType.String, value: '' });
+ return result;
+}
+
+interface KeyValuePair {
+ key: string;
+ value: string;
+}
+
+function parseEnhancedString(enhanced: string): KeyValuePair[] {
+ const result: KeyValuePair[] = [];
+ const regex = /(\S+)=(.*?)(?=\s+\S+=|\s*$)/g;
+ let match: RegExpExecArray | null;
+
+ while ((match = regex.exec(enhanced)) !== null) {
+ result.push({
+ key: match[1],
+ value: match[2].replace(/~/g, ':').trim(),
+ });
+ }
+
+ return result;
+}
diff --git a/packages/webgal/src/Stage/TextBox/TextBoxFilm.tsx b/packages/webgal/src/Stage/TextBox/TextBoxFilm.tsx
new file mode 100644
index 000000000..6d14d9921
--- /dev/null
+++ b/packages/webgal/src/Stage/TextBox/TextBoxFilm.tsx
@@ -0,0 +1,57 @@
+import styles from './textboxFilm.module.scss';
+import { useEffect } from 'react';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+
+import { PERFORM_CONFIG } from '@/config';
+
+export const TextBoxFilm = () => {
+ const stageState = useSelector((state: RootState) => state.stage);
+ const userDataState = useSelector((state: RootState) => state.userData);
+ useEffect(() => {});
+ const textDelay = PERFORM_CONFIG.textInitialDelay - 20 * userDataState.optionData.textSpeed;
+ const size = userDataState.optionData.textSize * 50 + 200 + '%';
+
+ // 拆字
+ const textArray: Array
= stageState.showText.split('');
+ const textElementList = textArray.map((e, index) => {
+ let delay = index * textDelay;
+ let prevLength = stageState.currentConcatDialogPrev.length;
+ if (stageState.currentConcatDialogPrev !== '' && index >= prevLength) {
+ delay = delay - prevLength * textDelay;
+ }
+ if (index < prevLength) {
+ return (
+
+ {e}
+
+ );
+ }
+ return (
+
+ {e}
+
+ );
+ });
+ return (
+
+ {/*
*/}
+ {/* {stageState.miniAvatar !== '' && */}
+ {/*
} */}
+ {/*
*/}
+ {/* {stageState.showName !== '' && */}
+ {/*
{stageState.showName}
} */}
+
{textElementList}
+
+ );
+};
diff --git a/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/StandardTextbox.tsx b/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/StandardTextbox.tsx
new file mode 100644
index 000000000..465b918bb
--- /dev/null
+++ b/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/StandardTextbox.tsx
@@ -0,0 +1,158 @@
+import styles from './standard.module.scss';
+import { textSize } from '@/store/userDataInterface';
+import { ReactNode, useEffect } from 'react';
+import { WebGAL } from '@/Core/WebGAL';
+import { ITextboxProps } from '@/Stage/TextBox/types';
+
+export default function StandardTextbox(props: ITextboxProps) {
+ const {
+ textArray,
+ textDelay,
+ currentConcatDialogPrev,
+ currentDialogKey,
+ isText,
+ isSafari,
+ isFirefox,
+ fontSize,
+ miniAvatar,
+ isHasName,
+ showName,
+ font,
+ textDuration,
+ textSizeState,
+ isUseStroke,
+ textboxOpacity,
+ } = props;
+
+ const isHasMiniAvatar = miniAvatar !== '';
+
+ useEffect(() => {
+ function settleText() {
+ const textElements = document.querySelectorAll('.Textelement_start');
+ const textArray = [...textElements];
+ textArray.forEach((e) => {
+ e.className = styles.TextBox_textElement_Settled;
+ });
+ }
+ WebGAL.events.textSettle.on(settleText);
+ return () => {
+ WebGAL.events.textSettle.off(settleText);
+ };
+ }, []);
+ const nameElementList = showName.map((e,index)=>{
+ let prevLength = currentConcatDialogPrev.length;
+ if (index < prevLength) {
+ return (
+
+ {e}
+ {e}
+ {isUseStroke && {e} }
+
+ );
+ }
+ return (
+
+ {e}
+ {e}
+ {isUseStroke && {e} }
+
+ );
+ });
+ const textElementList = textArray.map((e, index) => {
+ // if (e === ' ') {
+ // return ;
+ // }
+ let delay = index * textDelay;
+ let prevLength = currentConcatDialogPrev.length;
+ if (currentConcatDialogPrev !== '' && index >= prevLength) {
+ delay = delay - prevLength * textDelay;
+ }
+ if (index < prevLength) {
+ return (
+
+
+ {e}
+ {e}
+ {isUseStroke && {e} }
+
+
+ );
+ }
+ return (
+
+
+ {e}
+ {e}
+ {isUseStroke && {e} }
+
+
+ );
+ });
+
+ const padding = isHasMiniAvatar ? 500 : undefined;
+ let paddingTop = isHasName ? undefined : 15;
+ if (textSizeState === textSize.small && !isHasName) {
+ paddingTop = 35;
+ }
+
+ return (
+ <>
+ {isText && (
+
+ {/*
{stageState.showName !== ''}
*/}
+
+ {miniAvatar !== '' &&
}
+
+ {isHasName && (
+
+ {nameElementList}
+
+ )}
+
+ {textElementList}
+
+
+ )}
+ >
+ );
+}
diff --git a/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/standard.module.scss b/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/standard.module.scss
new file mode 100644
index 000000000..128271be0
--- /dev/null
+++ b/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/standard.module.scss
@@ -0,0 +1,160 @@
+.TextBox_EventHandler {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ z-index: 6;
+ top: 0;
+}
+
+@mixin text_shadow_textElement {
+ //text-shadow: 0 0 3px rgba(81,168,221,1);
+}
+
+$height: 430px;
+
+.TextBox_main {
+ position: absolute;
+ z-index: 6;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ min-height: $height;
+ max-height: $height;
+ // background: linear-gradient(transparent, rgba(0, 0, 0, .25) 25%, rgba(0, 0, 0, .35) 75%, rgba(0, 0, 0, .6)),
+ // linear-gradient(90deg, transparent 0, rgba(0, 0, 0, .35) 25%, rgba(0, 0, 0, .35) 75%, transparent);
+ //background-blend-mode: darken;
+ font-weight: bold;
+ padding: 30px 50px 100px 200px;
+ box-sizing: border-box;
+ display: flex;
+ flex-flow: column;
+ align-items: flex-start;
+ animation: showSoftly 0.7s ease-out forwards;
+ letter-spacing: 0.2em;
+ //backdrop-filter: blur(2px);
+ transition: left 0.33s, padding-left 0.33s;
+}
+
+.TextBox_showName {
+ @include text_shadow_textElement;
+ font-size: 85%;
+ min-width: 25%;
+ height: 70px;
+ //display: flex;
+ //align-items: center;
+ transition: left 0.33s;
+ border-bottom: 3px solid rgba(255, 255, 255, 0.8);
+}
+
+@keyframes showSoftly {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+
+.TextBox_textElement_start {
+ @include text_shadow_textElement;
+ position: relative;
+ animation: TextDelayShow 1000ms ease-out forwards;
+ opacity: 0;
+}
+
+.outer {
+ position: absolute;
+ white-space: nowrap;
+ left: 0;
+ top: 0;
+ background-image: linear-gradient( //135deg,
+ #ffffff 0%,
+ #f5f7fa 45%,
+ #c3cfe2 100%);
+ background-clip: text;
+ -webkit-background-clip: text;
+ color: transparent;
+ z-index: 2;
+}
+
+.inner {
+ white-space: nowrap;
+ position: absolute;
+ left: 0;
+ top: 0;
+ -webkit-text-stroke: 0.085em rgba(0, 0, 0, 0.35);
+ z-index: 1;
+}
+
+.zhanwei {
+ color: transparent;
+ white-space: nowrap;
+}
+
+
+.TextBox_textElement_Settled {
+ position: relative;
+ @include text_shadow_textElement;
+ opacity: 1;
+}
+
+@keyframes TextDelayShow {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+
+.miniAvatarContainer {
+ position: absolute;
+ height: 450px;
+ width: 450px;
+ bottom: 0;
+ left: 0;
+ //border-radius: 100% 0 0 100%;
+ overflow: hidden;
+}
+
+.miniAvatarImg {
+ max-height: 100%;
+ max-width: 100%;
+ position: absolute;
+ bottom: 0;
+ filter: drop-shadow(15px 0 3px rgba(0, 0, 0, 0.5));
+}
+
+.nameContainer {
+ position: absolute;
+ left: 2em;
+ top: -3.5em;
+}
+
+.outerName {
+ position: absolute;
+ left: 0;
+ top: 0;
+ background: linear-gradient(150deg, rgb(255, 255, 255) 0%, rgb(255, 255, 255) 35%, rgb(165, 212, 228) 100%);
+ background-clip: text;
+ -webkit-background-clip: text;
+ color: transparent;
+ z-index: 2;
+}
+
+.innerName {
+ position: absolute;
+ left: 0;
+ top: 0;
+ z-index: 1;
+}
+
+.text {
+ line-height: 1.9em;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ //-webkit-line-clamp: 2;
+ overflow: hidden;
+}
\ No newline at end of file
diff --git a/packages/webgal/src/Stage/TextBox/textbox.module.scss b/packages/webgal/src/Stage/TextBox/textbox.module.scss
new file mode 100644
index 000000000..392918d7a
--- /dev/null
+++ b/packages/webgal/src/Stage/TextBox/textbox.module.scss
@@ -0,0 +1,236 @@
+.TextBox_EventHandler {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ z-index: 6;
+ top: 0;
+}
+
+@mixin text_shadow_textElement {
+ //text-shadow: 0 0 3px rgba(81,168,221,1);
+}
+
+$height: 330px;
+
+.TextBox_Container {
+ position: absolute;
+ z-index: 6;
+ bottom: 0;
+ width: 100%;
+ animation: showSoftly 0.7s ease-out forwards;
+}
+
+.TextBox_main {
+ z-index: 3;
+ position: absolute;
+ right: 25px;
+ min-height: $height;
+ max-height: $height;
+ background-blend-mode: darken;
+ border-radius: calc($height / 2) 20px 20px calc($height / 2);
+ bottom: 20px;
+ left: 275px;
+ font-weight: bold;
+ color: white;
+ padding: 1em 50px 70px 200px;
+ box-sizing: border-box;
+ display: flex;
+ flex-flow: column;
+ align-items: flex-start;
+ letter-spacing: 0.2em;
+ transition: left 0.33s;
+}
+
+.TextBox_main_miniavatarOff {
+ left: 25px;
+}
+
+.TextBox_Background {
+ z-index: 2;
+ background: linear-gradient(rgba(245, 247, 250, 1) 0%, rgba(189, 198, 222, 1) 100%);
+}
+
+@keyframes showSoftly {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+
+//.TextBox_textElement {
+// opacity: 0;
+// animation: showSoftly 1000ms forwards;
+//}
+
+
+.TextBox_textElement_start {
+ @include text_shadow_textElement;
+ position: relative;
+ animation: TextDelayShow 1000ms ease-out forwards;
+ opacity: 0;
+ display: inline-flex;
+ align-content: space-between
+}
+
+
+.outer {
+ position: absolute;
+ left:0;
+ top:0;
+ white-space: nowrap;
+ //background-image: linear-gradient(rgba(255, 255, 255, 1) 0%, rgb(225, 237, 255) 100%);
+ background-image: linear-gradient(#0B346E 0%,
+ //#f5f7fa 45%,
+ #141423 100%);
+ //background: rgba(255, 255, 255, 1);
+ background-clip: text;
+ -webkit-background-clip: text;
+ color: transparent;
+ z-index: 2;
+ display: inline-block; /* 与文本对齐 */
+ vertical-align: middle; /* 确保注音整体的垂直居中 */
+}
+
+.inner {
+ white-space: nowrap;
+ position: absolute;
+ left:0;
+ top:0;
+ -webkit-text-stroke: 0.1em rgba(255, 255, 255, 1);
+ z-index: 1;
+ //text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.75);
+ display: inline-block; /* 内层元素同样行内块布局 */
+ vertical-align: middle; /* 确保与外层的对齐一致 */
+}
+
+.zhanwei {
+ color: transparent;
+ white-space: nowrap;
+ display: inline-block; /* 保持行内块布局 */
+ vertical-align: baseline; /* 确保文本基线对齐 */
+ position: relative; /* 保持相对定位 */
+}
+
+//
+//.TextBox_textElement_start::before{
+// animation: TextDelayShow 700ms ease-out forwards;
+// opacity: 0;
+// content:attr(data-text);
+// position: absolute;
+//}
+
+.TextBox_textElement_Settled {
+ position: relative;
+ @include text_shadow_textElement;
+ opacity: 1;
+ display: inline-flex;
+ align-content: space-between
+}
+
+//
+//.TextBox_textElement_Settled::before{
+// content:attr(data-text);
+// position: absolute;
+// opacity: 1;
+// -webkit-text-stroke: 1px red;
+// z-index: 1;
+//}
+
+.TextBox_showName {
+ @include text_shadow_textElement;
+ font-size: 85%;
+ //border-bottom: 3px solid rgb(176, 176, 176);
+ //min-width: 25%;
+ padding: 0 2em 0 2em;
+ //margin: 0 0 0 0;
+ position: absolute;
+ left: 150px;
+ top: -68px;
+ height: 80px;
+ line-height: 68px;
+ //display: flex;
+ //align-items: center;
+ // background: rgba(11, 52, 110, 0.9);
+ border-radius: 40px;
+ // border: 4px solid rgba(255, 255, 255, 0.75);
+ // box-shadow: 3px 3px 10px rgba(100, 100, 100, 0.5);
+ z-index: 3;
+ border: 4px solid rgba(255, 255, 255, 0);
+}
+
+.TextBox_ShowName_Background {
+ z-index: 2;
+ background: rgba(11, 52, 110, 1);
+ border: 4px solid rgba(255, 255, 255, 0.75);
+ box-shadow: 3px 3px 10px rgba(100, 100, 100, 0.5);
+}
+
+@keyframes TextDelayShow {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+
+.miniAvatarContainer {
+ position: absolute;
+ height: 450px;
+ width: 450px;
+ bottom: 0;
+ left: -250px;
+ border-radius: 100% 0 0 100%;
+ overflow: hidden;
+}
+
+.miniAvatarImg {
+ max-height: 100%;
+ max-width: 100%;
+ position: absolute;
+ bottom: 0;
+ filter: drop-shadow(15px 0 3px rgba(0, 0, 0, 0.5));
+}
+
+.nameContainer {
+ position: absolute;
+ left: 2em;
+ top: -3.5em;
+}
+
+.outerName {
+ position: absolute;
+ left: 0;
+ top: 0;
+ //background-image: linear-gradient(rgba(255, 255, 255, 1) 0%, rgb(225, 237, 255) 100%);
+ //background-image: linear-gradient(
+ // #bfd8ff 0%,
+ // //#f5f7fa 45%,
+ // #bfbfc7 100%
+ //);
+ background: linear-gradient(150deg, rgb(255, 255, 255) 0%, rgb(255, 255, 255) 35%, rgb(165, 212, 228) 100%);
+ //background: rgba(255, 255, 255, 1);
+ background-clip: text;
+ -webkit-background-clip: text;
+ color: transparent;
+ z-index: 2;
+}
+
+.innerName {
+ position: absolute;
+ left: 0;
+ top: 0;
+ //-webkit-text-stroke: 0.1em rgba(0, 0, 0, 0.25);
+ //-webkit-text-stroke: 0.1em rgba(255, 255, 255, 1);
+ z-index: 1;
+ //text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.75);
+}
+
+.text {
+ line-height: 1.9em;
+ overflow: hidden;
+}
diff --git a/packages/webgal/src/Stage/TextBox/textboxFilm.module.scss b/packages/webgal/src/Stage/TextBox/textboxFilm.module.scss
new file mode 100644
index 000000000..da3cac663
--- /dev/null
+++ b/packages/webgal/src/Stage/TextBox/textboxFilm.module.scss
@@ -0,0 +1,91 @@
+.TextBox_EventHandler {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ z-index: 6;
+ top: 0;
+}
+
+.TextBox_main {
+ font-family: "思源宋体", serif;
+ font-style: italic;
+ position: absolute;
+ z-index: 6;
+ width: 100%;
+ height: 12%;
+ //background: linear-gradient(transparent,
+ // rgba(0, 0, 0, .25) 25%,
+ // rgba(0, 0, 0, .35) 75%,
+ // rgba(0, 0, 0, .6)),
+ //linear-gradient(90deg, transparent 0,
+ // rgba(0, 0, 0, .35) 25%,
+ // rgba(0, 0, 0, .35) 75%,
+ // transparent);
+ background-color: black;
+ bottom: 0;
+ color: white;
+ //padding: 1em 18em 2em 18em;
+ box-sizing: border-box;
+ overflow: hidden;
+ display: flex;
+ flex-flow: column;
+ align-items: center;
+ animation: showSoftly 0.7s ease-out forwards;
+ letter-spacing: 0.2em;
+ justify-content: center;
+}
+
+@keyframes showSoftly {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+.TextBox_textElement {
+ opacity: 0;
+ animation: showSoftly 1000ms forwards;
+}
+
+.TextBox_textElement_start {
+ animation: TextDelayShow 700ms ease-out forwards;
+ opacity: 0;
+}
+
+.TextBox_textElement_Settled {
+ opacity: 1;
+}
+
+.TextBox_showName {
+ font-size: 85%;
+ border-bottom: 2px solid rgba(255, 255, 255, 0.3);
+ min-width: 50%;
+ padding: 0 0.2em 0.2em 0.3em;
+ margin: 0 0 0.2em 0;
+}
+
+@keyframes TextDelayShow {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+.miniAvatarContainer {
+ position: absolute;
+ height: 80%;
+ width: 17%;
+ bottom: 0;
+ left: 0.5em;
+}
+
+.miniAvatarImg {
+ max-height: 100%;
+ max-width: 100%;
+ position: absolute;
+ bottom: 0;
+}
diff --git a/packages/webgal/src/Stage/TextBox/types.ts b/packages/webgal/src/Stage/TextBox/types.ts
new file mode 100644
index 000000000..59e41e126
--- /dev/null
+++ b/packages/webgal/src/Stage/TextBox/types.ts
@@ -0,0 +1,21 @@
+import { EnhancedNode } from '@/Stage/TextBox/TextBox';
+
+export interface ITextboxProps {
+ textArray: EnhancedNode[][];
+ textDelay: number;
+ currentConcatDialogPrev: string;
+ currentDialogKey: string;
+ isText: boolean;
+ isSafari: boolean;
+ isFirefox: boolean;
+ fontSize: string;
+ miniAvatar: string;
+ showName: EnhancedNode[][];
+ isHasName: boolean;
+ font: string;
+ textDuration: number;
+ textSizeState: number;
+ lineLimit: number;
+ isUseStroke: boolean;
+ textboxOpacity: number;
+}
diff --git a/packages/webgal/src/Stage/introContainer/IntroContainer.tsx b/packages/webgal/src/Stage/introContainer/IntroContainer.tsx
new file mode 100644
index 000000000..16b7dfb5b
--- /dev/null
+++ b/packages/webgal/src/Stage/introContainer/IntroContainer.tsx
@@ -0,0 +1,5 @@
+import styles from './introContainer.module.scss';
+
+export default function IntroContainer() {
+ return
;
+}
diff --git a/packages/webgal/src/Stage/introContainer/introContainer.module.scss b/packages/webgal/src/Stage/introContainer/introContainer.module.scss
new file mode 100644
index 000000000..034ae238f
--- /dev/null
+++ b/packages/webgal/src/Stage/introContainer/introContainer.module.scss
@@ -0,0 +1,10 @@
+.introContainer {
+ box-sizing: border-box;
+ position: absolute;
+ z-index: 11;
+ width: 100%;
+ height: 100%;
+ color: white;
+ display: none;
+ //z-index: 13;
+}
diff --git a/packages/webgal/src/Stage/stage.module.scss b/packages/webgal/src/Stage/stage.module.scss
new file mode 100644
index 000000000..ffa0e6db5
--- /dev/null
+++ b/packages/webgal/src/Stage/stage.module.scss
@@ -0,0 +1,82 @@
+.MainStage_main {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ z-index: 1;
+ //animation: MainStage_showBgSoftly 100ms forwards;
+ opacity: 1;
+ overflow: hidden;
+}
+
+.MainStage_main_container {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ overflow: hidden;
+}
+
+.MainStage_bgContainer {
+ top: 0;
+ position: absolute;
+ background-size: cover;
+ width: 100%;
+ height: 100%;
+ z-index: 1;
+ animation: MainStage_showBgSoftly 1s forwards ease-in-out;
+}
+
+.MainStage_bgContainer_Settled {
+ top: 0;
+ position: absolute;
+ background-size: cover;
+ width: 100%;
+ height: 100%;
+ animation: MainStage_showBgSoftly 1ms forwards;
+ z-index: 1;
+}
+
+.MainStage_oldBgContainer {
+ background-size: cover;
+ top: 0;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ z-index: 0;
+ animation: MainStage_oldBgFadeout 3s forwards;
+}
+
+.MainStage_oldBgContainer_Settled {
+ background-size: cover;
+ top: 0;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ opacity: 0;
+}
+
+@keyframes MainStage_showBgSoftly {
+ 0% {
+ opacity: 0.15;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+@keyframes MainStage_oldBgFadeout {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
+
+.pixiContainer{
+ position: absolute;
+ z-index: 5;
+}
+
+.chooseContainer{
+ z-index: 8;
+}
diff --git a/packages/webgal/src/Stage/themeInterface.ts b/packages/webgal/src/Stage/themeInterface.ts
new file mode 100644
index 000000000..f3e8d8733
--- /dev/null
+++ b/packages/webgal/src/Stage/themeInterface.ts
@@ -0,0 +1 @@
+export type IWebGalTextBoxTheme = 'standard' | 'imss';
diff --git a/packages/webgal/src/UI/Backlog/Backlog.tsx b/packages/webgal/src/UI/Backlog/Backlog.tsx
new file mode 100644
index 000000000..c79093eaa
--- /dev/null
+++ b/packages/webgal/src/UI/Backlog/Backlog.tsx
@@ -0,0 +1,224 @@
+import styles from './backlog.module.scss';
+import { CloseSmall, Return, VolumeNotice } from '@icon-park/react';
+import { jumpFromBacklog } from '@/Core/controller/storage/jumpFromBacklog';
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState, webgalStore } from '@/store/store';
+import { setVisibility } from '@/store/GUIReducer';
+import { logger } from '@/Core/util/logger';
+import { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
+import useTrans from '@/hooks/useTrans';
+import { compileSentence, EnhancedNode, splitChars } from '@/Stage/TextBox/TextBox';
+import useSoundEffect from '@/hooks/useSoundEffect';
+import { WebGAL } from '@/Core/WebGAL';
+
+export const Backlog = () => {
+ const t = useTrans('gaming.');
+ // logger.info('Backlog render');
+ const { playSeEnter, playSeClick } = useSoundEffect();
+ const GUIStore = useSelector((state: RootState) => state.GUI);
+ const dispatch = useDispatch();
+ const iconSize = '0.8em';
+ const [indexHide, setIndexHide] = useState(false);
+ const [isDisableScroll, setIsDisableScroll] = useState(false);
+ let timeRef = useRef>();
+ // 缓存一下vdom
+ const backlogList = useMemo(() => {
+ let backlogs = [];
+ // logger.info('backlogList render');
+ for (let i = 0; i < WebGAL.backlogManager.getBacklog().length; i++) {
+ const backlogItem = WebGAL.backlogManager.getBacklog()[i];
+ const showTextArray = compileSentence(backlogItem.currentStageState.showText, 3, true);
+ const showTextArray2 = showTextArray.map((line) => {
+ return line.map((c) => {
+ return c.reactNode;
+ });
+ });
+ const showTextArrayReduced = mergeStringsAndKeepObjects(showTextArray2);
+ const showTextElementList = showTextArrayReduced.map((line, index) => {
+ return (
+
+ {line.map((e, index) => {
+ if (e === ' ') {
+ return ;
+ } else {
+ return e;
+ }
+ })}
+
+ );
+ });
+ const showNameArray = compileSentence(backlogItem.currentStageState.showName, 3, true);
+ const showNameArray2 = showNameArray.map((line)=>{
+ return line.map((c) => {
+ return c.reactNode;
+ });
+ });
+ const showNameArrayReduced = mergeStringsAndKeepObjects(showNameArray2);
+ const nameElementList = showNameArrayReduced.map((line,index)=>{
+ return (
+
+ {line.map((e, index) => {
+ if (e === ' ') {
+ return ;
+ } else {
+ return e;
+ }
+ })}
+
+ );
+ });
+ const singleBacklogView = (
+
+
+
+
{
+ playSeClick();
+ jumpFromBacklog(i);
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ onMouseEnter={playSeEnter}
+ className={styles.backlog_item_button_element}
+ >
+
+
+ {backlogItem.currentStageState.vocal ? (
+
{
+ playSeClick();
+ // 获取到播放 backlog 语音的元素
+ const backlog_audio_element: any = document.getElementById('backlog_audio_play_element_' + i);
+ if (backlog_audio_element) {
+ backlog_audio_element.currentTime = 0;
+ const userDataStore = webgalStore.getState().userData;
+ const mainVol = userDataStore.optionData.volumeMain;
+ backlog_audio_element.volume = mainVol * 0.01 * userDataStore.optionData.vocalVolume * 0.01;
+ backlog_audio_element?.play();
+ }
+ }}
+ onMouseEnter={playSeEnter}
+ className={styles.backlog_item_button_element}
+ >
+
+
+ ) : null}
+
+
{nameElementList}
+
+
+ {showTextElementList}
+
+
+
+ );
+ backlogs.unshift(singleBacklogView);
+ }
+ return backlogs;
+ }, [
+ WebGAL.backlogManager.getBacklog()[WebGAL.backlogManager.getBacklog().length - 1]?.saveScene?.currentSentenceId ??
+ 0,
+ ]);
+ useEffect(() => {
+ /* 切换为展示历史记录时触发 */
+ if (GUIStore.showBacklog) {
+ // logger.info('展示backlog');
+ // 立即清除 防止来回滚动时可能导致的错乱
+ if (timeRef.current) {
+ clearTimeout(timeRef.current);
+ }
+ // setIsDisableScroll(false);
+ // 重新把index调回正数
+ setIndexHide(false);
+ // 向上滑动触发回想时会带着backlog一起滑一下 我也不知道为什么,可能是我的鼠标问题 所以先ban掉滚动
+ setIsDisableScroll(true);
+ // nextTick开启滚动
+ setTimeout(() => {
+ setIsDisableScroll(false);
+ }, 0);
+ } else {
+ /* 隐藏历史记录触发 */
+ // 这里是为了让backlog的z-index降低
+ timeRef.current = setTimeout(() => {
+ setIndexHide(true);
+ // setIsDisableScroll(false);
+ // setIsDisableScroll(true);
+ timeRef.current = undefined;
+ // 700是和动画一样的延时 保险起见多个80ms
+ // 不加也没啥 问题不大
+ }, 700 + 80);
+ }
+ }, [GUIStore.showBacklog]);
+ return (
+ <>
+ {
+ // ${indexHide ? styles.Backlog_main_out_IndexHide : ''}
+
+
+
{
+ playSeClick();
+ dispatch(setVisibility({ component: 'showBacklog', visibility: false }));
+ dispatch(setVisibility({ component: 'showTextBox', visibility: true }));
+ }}
+ onMouseEnter={playSeEnter}
+ theme="outline"
+ size="4em"
+ fill="#ffffff"
+ strokeWidth={3}
+ />
+ {
+ logger.info('Rua! Testing');
+ }}
+ >
+ {t('buttons.backlog')}
+
+
+ {GUIStore.showBacklog && (
+
+ {backlogList}
+
+ )}
+
+ }
+ >
+ );
+};
+
+function mergeStringsAndKeepObjects(arr: ReactNode[]): ReactNode[][] {
+ let result = [];
+ let currentString = '';
+
+ // eslint-disable-next-line @typescript-eslint/prefer-for-of
+ for (let i = 0; i < arr.length; i++) {
+ const currentItem = arr[i];
+
+ if (typeof currentItem === 'string') {
+ currentString += currentItem;
+ } else {
+ if (currentString !== '') {
+ result.push(currentString);
+ currentString = '';
+ }
+ result.push(currentItem);
+ }
+ }
+
+ if (currentString !== '') {
+ result.push(currentString);
+ }
+
+ return result as ReactNode[][];
+}
diff --git a/packages/webgal/src/UI/Backlog/backlog.module.scss b/packages/webgal/src/UI/Backlog/backlog.module.scss
new file mode 100644
index 000000000..7d3f9a6dd
--- /dev/null
+++ b/packages/webgal/src/UI/Backlog/backlog.module.scss
@@ -0,0 +1,220 @@
+.Backlog_main {
+ font-family: "思源宋体", serif;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 10;
+ background: rgba(0, 0, 0, 0.7);
+ padding: 2em 0 2em 0;
+ animation: backlog_soft_in 0.7s ease-out forwards;
+ box-sizing: border-box;
+}
+
+.Backlog_main_out {
+ font-family: "思源宋体", serif;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 10;
+ background: rgba(0, 0, 0, 0.7);
+ padding: 2em 0 2em 0;
+ animation: backlog_soft_out 0.7s ease-out forwards;
+ box-sizing: border-box;
+}
+
+// 把z-index置为负数 不然会挡住点击层
+.Backlog_main_out_IndexHide {
+ z-index: -10;
+}
+
+// 暂时禁用滚动
+.Backlog_main_DisableScroll {
+ overflow: hidden !important;
+}
+
+
+.backlog_top {
+ padding: 0 0 0 1em;
+ display: flex;
+ height: 10%;
+}
+
+.backlog_top_icon {
+ padding: 0.6em 0.6em 0 0.6em;
+ border-radius: 1000px;
+ transform: translate(0, -13px);
+ cursor: pointer;
+}
+
+.backlog_top_icon:hover {
+ background: rgba(255, 255, 255, 0.25);
+ animation: backlog_icon_softin 0.25s ease-out forwards;
+}
+
+@keyframes backlog_icon_softin {
+ 0% {
+ background: rgba(255, 255, 255, 0);
+ }
+ 100% {
+ background: rgba(255, 255, 255, 0.25);
+ }
+}
+
+
+.backlog_title {
+ height: 100%;
+ line-height: 100%;
+ font-size: 360%;
+ font-weight: bold;
+ color: transparent;
+ background: linear-gradient(150deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 1) 35%, rgba(165, 212, 228, 1) 100%);
+ -webkit-background-clip: text;
+}
+
+.backlog_content {
+ position: absolute;
+ height: 80%;
+ padding: 1em 10em 1em 10em;
+ overflow: auto;
+ display: flex;
+ flex-flow: column-reverse;
+ font-weight: normal;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+
+.backlog_item {
+ display: flex;
+ color: white;
+ font-size: 165%;
+ opacity: 0;
+ animation: backlog_item_in 0.5s ease-out forwards;
+ margin: 1.25em 0 0 0;
+ width: 100%;
+}
+
+.backlog_item_out {
+ display: flex;
+ color: white;
+ font-size: 165%;
+ opacity: 0;
+ animation: backlog_item_out 0.5s ease-out forwards;
+ margin: 1.25em 0 0 0;
+ width: 100%;
+}
+
+.backlog_func_area {
+ display: flex;
+ flex-flow: row;
+ align-items: flex-start;
+ width: 30%;
+ max-width: 30%;
+ min-width: 30%;
+}
+
+.backlog_item_content_name {
+ font-weight: bold;
+ color: transparent;
+ //background: linear-gradient(150deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 1) 55%, rgb(210, 243, 255) 100%);
+ background: linear-gradient(150deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 1) 35%, rgba(165, 212, 228, 1) 100%);
+ -webkit-background-clip: text;
+ //width: 20%;
+ margin: 0 0 0 auto;
+ overflow-wrap: break-word;
+ box-sizing: border-box;
+ //background: rgba(255, 255, 255, 0.175);
+ border-radius: 7px;
+ padding: 0.2em 0.5em 0.2em 0.5em;
+ font-size: 115%;
+ width: 50%;
+ text-align: left;
+ //font-family: WebgalUI, serif;
+ letter-spacing: 0.1em;
+}
+
+.backlog_item_content {
+ //display: flex;
+ font-size: 115%;
+ width: 82.5%;
+ box-sizing: border-box;
+ padding: 0.2em 0 0 1em;
+ //font-family: WebgalUI, serif;
+ letter-spacing: 0.05em;
+}
+
+.backlog_item_button_list {
+ display: flex;
+ //padding: 0 2em 0 0.3em;
+ flex-flow: row;
+ align-items: flex-start;
+ margin: 0.35em 0 0 0;
+}
+
+.backlog_item_button_element {
+ cursor: pointer;
+ padding: 0.01em 0.75em 0 0.75em;
+ margin: 0 0 0 0.5em;
+ background: rgba(255, 255, 255, 0.075);
+ border-radius: 7px;
+ display: flex;
+ //border: 1px solid rgba(255, 255, 255, 0.15);
+ //box-shadow: 0 0 15px rgba(255, 255, 255, 0.25);
+}
+
+.backlog_item_button_element:hover {
+ background: rgba(255, 255, 255, 0.25);
+ //border: 1px solid rgba(255, 255, 255, 0);
+ //box-shadow: 0 0 15px rgba(255, 255, 255, 0.3);
+}
+
+
+.backlog_item_content_text {
+ //width: 80%;
+ box-sizing: border-box;
+}
+
+
+@keyframes backlog_soft_in {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+@keyframes backlog_soft_out {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
+
+@keyframes backlog_item_in {
+ 0% {
+ opacity: 0;
+ transform: scale(1.05, 1.05) translate(-15px, 10px) rotateX(-5deg) rotateY(-5deg);
+ //background-color: rgba(255, 255, 255, 0.2);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1, 1) translate(0, 0);
+ }
+}
+
+@keyframes backlog_item_out {
+ 0% {
+ opacity: 1;
+ transform: scale(1, 1) translate(0, 0);
+ }
+ 100% {
+ opacity: 0;
+ transform: scale(1.05, 1.05) translate(-15px, 10px) rotateX(-5deg) rotateY(-5deg);
+ background-color: rgba(255, 255, 255, 0.2);
+ }
+}
diff --git a/packages/webgal/src/UI/BottomControlPanel/BottomControlPanel.tsx b/packages/webgal/src/UI/BottomControlPanel/BottomControlPanel.tsx
new file mode 100644
index 000000000..d60325bb9
--- /dev/null
+++ b/packages/webgal/src/UI/BottomControlPanel/BottomControlPanel.tsx
@@ -0,0 +1,320 @@
+import {
+ AlignTextLeftOne,
+ DoubleRight,
+ FolderOpen,
+ Home,
+ PlayOne,
+ PreviewCloseOne,
+ PreviewOpen,
+ ReplayMusic,
+ Save,
+ SettingTwo,
+ DoubleDown,
+ DoubleUp,
+ Lock,
+ Unlock,
+} from '@icon-park/react';
+import styles from './bottomControlPanel.module.scss';
+import { switchAuto } from '@/Core/controller/gamePlay/autoPlay';
+import { switchFast } from '@/Core/controller/gamePlay/fastSkip';
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { setMenuPanelTag, setVisibility } from '@/store/GUIReducer';
+import { componentsVisibility, MenuPanelTag } from '@/store/guiInterface';
+import { backToTitle } from '@/Core/controller/gamePlay/backToTitle';
+import { saveGame } from '@/Core/controller/storage/saveGame';
+import { loadGame } from '@/Core/controller/storage/loadGame';
+import useTrans from '@/hooks/useTrans';
+import { useTranslation } from 'react-i18next';
+import useSoundEffect from '@/hooks/useSoundEffect';
+import { showGlogalDialog, switchControls } from '@/UI/GlobalDialog/GlobalDialog';
+import { useEffect } from 'react';
+import { getSavesFromStorage } from '@/Core/controller/storage/savesController';
+
+export const BottomControlPanel = () => {
+ const t = useTrans('gaming.');
+ const strokeWidth = 2.5;
+ const { i18n } = useTranslation();
+ const { playSeEnter, playSeClick, playSeDialogOpen } = useSoundEffect();
+ const lang = i18n.language;
+ const isFr = lang === 'fr';
+ let size = 42;
+ let fontSize = '150%';
+ if (isFr) {
+ fontSize = '125%';
+ size = 40;
+ }
+ const GUIStore = useSelector((state: RootState) => state.GUI);
+ const stageState = useSelector((state: RootState) => state.stage);
+ const dispatch = useDispatch();
+ const setComponentVisibility = (component: keyof componentsVisibility, visibility: boolean) => {
+ dispatch(setVisibility({ component, visibility }));
+ };
+ const setMenuPanel = (menuPanel: MenuPanelTag) => {
+ dispatch(setMenuPanelTag(menuPanel));
+ };
+
+ const saveData = useSelector((state: RootState) => state.saveData.saveData);
+ let fastSlPreview = (
+
+ );
+ if (saveData[0]) {
+ const data = saveData[0];
+ fastSlPreview = (
+
+
+
+
+
+
{data.nowStageState.showName}
+
{data.nowStageState.showText}
+
+
+ );
+ }
+
+ return (
+ //
+ <>
+ {GUIStore.showTextBox && stageState.enableFilm === '' && (
+
+ {GUIStore.showTextBox && (
+
{
+ setComponentVisibility('showTextBox', false);
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
+ {t('buttons.hide')}
+
+ )}
+ {!GUIStore.showTextBox && (
+
{
+ setComponentVisibility('showTextBox', true);
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
+ {t('buttons.show')}
+
+ )}
+
{
+ setComponentVisibility('showBacklog', true);
+ setComponentVisibility('showTextBox', false);
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
+ {t('buttons.backlog')}
+
+
{
+ let VocalControl: any = document.getElementById('currentVocal');
+ if (VocalControl !== null) {
+ VocalControl.currentTime = 0;
+ VocalControl.pause();
+ VocalControl?.play();
+ }
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
+ {t('buttons.replay')}
+
+
{
+ switchAuto();
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
+ {t('buttons.auto')}
+
+
{
+ switchFast();
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
+ {t('buttons.forward')}
+
+
{
+ saveGame(0);
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
+ {t('buttons.quicklySave')}
+ {fastSlPreview}
+
+
{
+ loadGame(0);
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
+ {t('buttons.quicklyLoad')}
+ {fastSlPreview}
+
+
{
+ setMenuPanel(MenuPanelTag.Save);
+ setComponentVisibility('showMenuPanel', true);
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
+ {t('buttons.save')}
+
+
{
+ setMenuPanel(MenuPanelTag.Load);
+ setComponentVisibility('showMenuPanel', true);
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
+ {t('buttons.load')}
+
+
{
+ setMenuPanel(MenuPanelTag.Option);
+ setComponentVisibility('showMenuPanel', true);
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
+ {t('buttons.options')}
+
+
{
+ playSeDialogOpen();
+ showGlogalDialog({
+ title: t('buttons.titleTips'),
+ leftText: t('$common.yes'),
+ rightText: t('$common.no'),
+ leftFunc: () => {
+ backToTitle();
+ },
+ rightFunc: () => {},
+ });
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
+ {t('buttons.title')}
+
+
{
+ switchControls();
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+ {GUIStore.showControls ? (
+
+ ) : (
+
+ )}
+
+
+ )}
+ >
+ //
+ );
+};
diff --git a/packages/webgal/src/UI/BottomControlPanel/BottomControlPanelFilm.tsx b/packages/webgal/src/UI/BottomControlPanel/BottomControlPanelFilm.tsx
new file mode 100644
index 000000000..b6ed7b2e4
--- /dev/null
+++ b/packages/webgal/src/UI/BottomControlPanel/BottomControlPanelFilm.tsx
@@ -0,0 +1,125 @@
+import styles from './bottomControlPanelFilm.module.scss';
+import { switchAuto } from '@/Core/controller/gamePlay/autoPlay';
+import { switchFast } from '@/Core/controller/gamePlay/fastSkip';
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { setMenuPanelTag, setVisibility } from '@/store/GUIReducer';
+import { componentsVisibility, MenuPanelTag } from '@/store/guiInterface';
+import { backToTitle } from '@/Core/controller/gamePlay/backToTitle';
+import { useValue } from '@/hooks/useValue';
+import { HamburgerButton } from '@icon-park/react';
+
+export const BottomControlPanelFilm = () => {
+ const showPanel = useValue(false);
+ const stageState = useSelector((state: RootState) => state.stage);
+ const dispatch = useDispatch();
+ const setComponentVisibility = (component: keyof componentsVisibility, visibility: boolean) => {
+ dispatch(setVisibility({ component, visibility }));
+ };
+ const setMenuPanel = (menuPanel: MenuPanelTag) => {
+ dispatch(setMenuPanelTag(menuPanel));
+ };
+ return (
+ <>
+ {stageState.enableFilm !== '' && (
+ <>
+ {
+ showPanel.set(!showPanel.value);
+ }}
+ >
+
+
+ {showPanel.value && (
+
+ {
+ setComponentVisibility('showBacklog', true);
+ setComponentVisibility('showTextBox', false);
+ showPanel.set(!showPanel.value);
+ }}
+ >
+ 剧情回想 / BACKLOG
+
+ {
+ showPanel.set(!showPanel.value);
+ let VocalControl: any = document.getElementById('currentVocal');
+ if (VocalControl !== null) {
+ VocalControl.currentTime = 0;
+ VocalControl.pause();
+ VocalControl?.play();
+ }
+ }}
+ >
+ 重播语音 / REPLAY VOICE
+
+ {
+ switchAuto();
+ showPanel.set(!showPanel.value);
+ }}
+ >
+ 自动模式 / AUTO
+
+ {
+ switchFast();
+ showPanel.set(!showPanel.value);
+ }}
+ >
+ 快进 / FAST
+
+ {
+ showPanel.set(!showPanel.value);
+ setMenuPanel(MenuPanelTag.Save);
+ setComponentVisibility('showMenuPanel', true);
+ }}
+ >
+ 存档 / SAVE
+
+ {
+ showPanel.set(!showPanel.value);
+ setMenuPanel(MenuPanelTag.Load);
+ setComponentVisibility('showMenuPanel', true);
+ }}
+ >
+ 读档 / LOAD
+
+ {
+ showPanel.set(!showPanel.value);
+ setMenuPanel(MenuPanelTag.Option);
+ setComponentVisibility('showMenuPanel', true);
+ }}
+ >
+ 选项 / OPTIONS
+
+ {
+ showPanel.set(!showPanel.value);
+ backToTitle();
+ }}
+ >
+ 标题 / TITLE
+
+
+ )}
+ >
+ )}
+ >
+ );
+};
diff --git a/packages/webgal/src/UI/BottomControlPanel/bottomControlPanel.module.scss b/packages/webgal/src/UI/BottomControlPanel/bottomControlPanel.module.scss
new file mode 100644
index 000000000..e790bc398
--- /dev/null
+++ b/packages/webgal/src/UI/BottomControlPanel/bottomControlPanel.module.scss
@@ -0,0 +1,128 @@
+//.ToCenter {
+// display: flex;
+// justify-content: center;
+// position: absolute;
+// bottom: 0;
+// left: 0;
+// width: 100%;
+// z-index: 9;
+//}
+
+.main {
+ //border-bottom: 2px solid rgba(255,255,255,0.25);
+ position: absolute;
+ bottom: 20px;
+ //will-change: z-index;
+ z-index: 9;
+ display: flex;
+ //background-image: linear-gradient(rgba(245, 247, 250, 0.7) 0%, rgba(195, 207, 226, 0.7) 100%);
+ flex-flow: row;
+ justify-content: center;
+ align-items: center;
+ //width: 100%;
+ height: 70px;
+ right: 20px;
+ border-radius: 35px;
+ //backdrop-filter: blur(5px);
+ padding: 0.15em 1.75em 0.15em 1.75em;
+ //background: linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.25) 100%);
+ //border-radius: 10px;
+ font-size: 80%;
+}
+
+.button {
+ position: relative;
+ top: 2px;
+ padding: 0 0 0 0;
+ filter: drop-shadow(1px 1px 5px rgba(0, 0, 0, 1));
+}
+
+.button_text {
+ position: relative;
+ bottom: 8px;
+ color: white;
+ text-shadow: 1px 1px 5px rgba(0, 0, 0, 1);
+ padding-left: 3px;
+}
+
+.button_on {
+ height: 100%;
+ display: inline-block;
+ font-size: 150%;
+ padding: 0.25em 0.3em 0 0.15em;
+ transition: background-color 0.5s;
+ background: rgba(255, 255, 255, 0.3);
+ border-radius: 4px;
+ margin: 0 0.1em 0 0.1em;
+}
+
+.singleButton {
+ //border-bottom: 1px solid rgba(255, 255, 255, 0.5);
+ height: 100%;
+ display: inline-block;
+ color: white;
+ font-size: 150%;
+ padding: 0.3em 0.3em 0 0.15em;
+ transition: background-color 0.5s;
+ cursor: pointer;
+ border-radius: 4px;
+ margin: 0 0.1em 0 0.1em;
+ position: relative;
+}
+
+.singleButton:hover {
+ background-color: rgba(255, 255, 255, 0.3);
+}
+
+.fastSlPreview {
+ position: absolute;
+ top: -250px;
+ right: 0;
+ background: linear-gradient(315deg, rgba(253, 251, 251, 0.9) 0%, rgba(235, 237, 238, 0.85) 100%);
+ width: 900px;
+ height: 230px;
+ color: #005caf;
+ border-radius: 5px;
+ display: none;
+ animation: fastSlEnter 0.33s;
+ transition: opacity 0.33s;
+}
+
+.fastsave:hover .fastSPreview {
+ display: block;
+}
+
+.fastload:hover .fastLPreview {
+ display: block;
+}
+
+@keyframes fastSlEnter {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+.slPreviewMain {
+ padding: 0.5em 0.5em 0.5em 0.5em;
+ display: flex;
+ box-sizing: border-box;
+ height: 100%;
+ width: 100%;
+}
+
+.imgContainer {
+ display: flex;
+ overflow: hidden;
+ border-radius: 5px;
+ //outline: 4px solid #005caf;
+ flex-shrink: 0;
+ height: 100%;
+}
+
+.textContainer {
+ overflow: hidden;
+ padding: 0 0 0 0.5em;
+}
diff --git a/packages/webgal/src/UI/BottomControlPanel/bottomControlPanelFilm.module.scss b/packages/webgal/src/UI/BottomControlPanel/bottomControlPanelFilm.module.scss
new file mode 100644
index 000000000..541765e54
--- /dev/null
+++ b/packages/webgal/src/UI/BottomControlPanel/bottomControlPanelFilm.module.scss
@@ -0,0 +1,54 @@
+.tag {
+ position: absolute;
+ top: 2.5%;
+ left: 2.5%;
+ color: white;
+ z-index: 10;
+ padding: 10px 10px 5px 10px;
+ border-radius: 100px;
+ transition: background-color 0.33s;
+}
+
+.tag:hover {
+ background-color: rgba(255, 255, 255, 0.5);
+}
+
+.container {
+ color: white;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-flow: column;
+ background-color: rgba(0, 0, 0, 0.7);
+ z-index: 9;
+ padding: 7em 5em 5em 10em;
+ opacity: 0;
+ animation: showContainer 1s forwards;
+ transition: background-color 0.33s;
+}
+
+.singleButton {
+ padding: 0.5em 0 0.5em 0;
+}
+
+.button_text {
+ font-family: "思源宋体", serif;
+ font-size: 250%;
+ letter-spacing: 0.07em;
+ transition: text-shadow 0.33s;
+}
+
+.button_text:hover {
+ text-shadow: 0 0 15px rgba(255, 255, 255, 1);
+}
+
+@keyframes showContainer {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
diff --git a/packages/webgal/src/UI/DevPanel/DevPanel.tsx b/packages/webgal/src/UI/DevPanel/DevPanel.tsx
new file mode 100644
index 000000000..d47c2d112
--- /dev/null
+++ b/packages/webgal/src/UI/DevPanel/DevPanel.tsx
@@ -0,0 +1,61 @@
+import styles from './devPanel.module.scss';
+import { useValue } from '@/hooks/useValue';
+import { getPixiSscreenshot } from '@/UI/DevPanel/devFunctions/getPixiSscreenshot';
+import { useEffect } from 'react';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { useTranslation } from 'react-i18next';
+
+import { WebGAL } from '@/Core/WebGAL';
+
+export default function DevPanel() {
+ // 控制显隐
+ function isShowDevPanel() {
+ const hash = window.location.hash;
+ return !!hash.match(/dev/);
+ }
+ const isOpenDevPanel = useValue(false);
+ const hash = useValue(window.location.hash);
+ const stageState = useSelector((state: RootState) => state.stage);
+ useEffect(() => {
+ window.onhashchange = () => {
+ hash.set(window.location.hash);
+ };
+ }, []);
+ const isShow = isShowDevPanel();
+
+ const { t, i18n } = useTranslation();
+
+ const devMainArea = (
+ <>
+ getPixiSscreenshot()}>Save PIXI Screenshot
+ Current Language:{i18n.language}
+ WebGAL.gameplay.pixiStage?.removeAnimation('snow-Ticker')}>Remove Snow Ticker
+ Stage State
+ {JSON.stringify(stageState, null, ' ')}
+ >
+ );
+ return (
+ <>
+ {isShow && isOpenDevPanel.value && (
+
+
+
isOpenDevPanel.set(false)}
+ style={{ fontSize: '150%', padding: '0 0 0 15px', cursor: 'pointer' }}
+ >
+ ×
+
+
WebGAL DEV PANEL
+
+
{devMainArea}
+
+ )}
+ {!isOpenDevPanel.value && isShow && (
+ isOpenDevPanel.set(true)} className={styles.devPanelOpener}>
+ Open Dev Panel
+
+ )}
+ >
+ );
+}
diff --git a/packages/webgal/src/UI/DevPanel/devFunctions/getPixiSscreenshot.ts b/packages/webgal/src/UI/DevPanel/devFunctions/getPixiSscreenshot.ts
new file mode 100644
index 000000000..f1361c3a0
--- /dev/null
+++ b/packages/webgal/src/UI/DevPanel/devFunctions/getPixiSscreenshot.ts
@@ -0,0 +1,13 @@
+export function getPixiSscreenshot() {
+ const canvas: HTMLCanvasElement = document.getElementById('pixiCanvas')! as HTMLCanvasElement;
+ canvas.toBlob((b) => {
+ if (b) {
+ const a = document.createElement('a');
+ document.body.append(a);
+ a.download = 'screenshot';
+ a.href = URL.createObjectURL(b);
+ a.click();
+ a.remove();
+ }
+ }, 'image/png');
+}
diff --git a/packages/webgal/src/UI/DevPanel/devPanel.module.scss b/packages/webgal/src/UI/DevPanel/devPanel.module.scss
new file mode 100644
index 000000000..a5fa4d350
--- /dev/null
+++ b/packages/webgal/src/UI/DevPanel/devPanel.module.scss
@@ -0,0 +1,22 @@
+.devPanelMain {
+ font-size: 150%;
+ position: absolute;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ width: 35%;
+ background: rgba(255, 255, 255, 0.75);
+ z-index: 99;
+ overflow: auto;
+}
+
+.devPanelOpener {
+ position: absolute;
+ right: 5px;
+ top: 5px;
+ background: rgba(255, 255, 255, 0.75);
+ z-index: 100;
+ padding: 3px 7px 3px 7px;
+ border-radius: 4px;
+ cursor: pointer;
+}
diff --git a/packages/webgal/src/UI/Extra/Extra.tsx b/packages/webgal/src/UI/Extra/Extra.tsx
new file mode 100644
index 000000000..8a544eb3b
--- /dev/null
+++ b/packages/webgal/src/UI/Extra/Extra.tsx
@@ -0,0 +1,44 @@
+import styles from './extra.module.scss';
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { setVisibility } from '@/store/GUIReducer';
+import { CloseSmall } from '@icon-park/react';
+import { ExtraBgm } from '@/UI/Extra/ExtraBgm';
+import { ExtraCg } from './ExtraCg';
+import useTrans from '@/hooks/useTrans';
+import useSoundEffect from '@/hooks/useSoundEffect';
+
+export function Extra() {
+ const { playSeClick } = useSoundEffect();
+ const showExtra = useSelector((state: RootState) => state.GUI.showExtra);
+ const dispatch = useDispatch();
+
+ const t = useTrans('extra.');
+ return (
+ <>
+ {showExtra && (
+
+
+
{
+ dispatch(setVisibility({ component: 'showExtra', visibility: false }));
+ playSeClick();
+ }}
+ onMouseEnter={playSeClick}
+ theme="outline"
+ size="4em"
+ fill="#fff"
+ strokeWidth={3}
+ />
+ {t('title')}
+
+
+
+
+
+
+ )}
+ >
+ );
+}
diff --git a/packages/webgal/src/UI/Extra/ExtraBgm.tsx b/packages/webgal/src/UI/Extra/ExtraBgm.tsx
new file mode 100644
index 000000000..49d189d73
--- /dev/null
+++ b/packages/webgal/src/UI/Extra/ExtraBgm.tsx
@@ -0,0 +1,134 @@
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import React from 'react';
+import styles from '@/UI/Extra/extra.module.scss';
+import { useValue } from '@/hooks/useValue';
+import { setStage } from '@/store/stageReducer';
+import { GoEnd, GoStart, MusicList, PlayOne, SquareSmall } from '@icon-park/react';
+import useSoundEffect from '@/hooks/useSoundEffect';
+import { setGuiAsset } from '@/store/GUIReducer';
+
+export function ExtraBgm() {
+ const { playSeClick, playSeEnter } = useSoundEffect();
+ // 检查当前正在播放的bgm是否在bgm列表内
+ const currentBgmSrc = useSelector((state: RootState) => state.GUI.titleBgm);
+ const extraState = useSelector((state: RootState) => state.userData.appreciationData);
+ const initName = 'Title_BGM';
+ // 是否展示 bgm 列表
+ const isShowBgmList = useValue(false);
+ let foundCurrentBgmName = initName;
+ let foundCurrentBgmIndex = -1;
+ const iconSize = 39;
+ const bgmPlayerHeight = isShowBgmList.value ? '80%' : '10%';
+ const bgmListLen = extraState.bgm.length;
+ extraState.bgm.forEach((e, i) => {
+ if (e.url === currentBgmSrc) {
+ foundCurrentBgmName = e.name;
+ foundCurrentBgmIndex = i;
+ }
+ });
+ const currentPlayingBgmName = useValue('');
+ if (foundCurrentBgmName !== initName && foundCurrentBgmName !== currentPlayingBgmName.value) {
+ currentPlayingBgmName.set(foundCurrentBgmName);
+ }
+ const dispatch = useDispatch();
+
+ function setBgmByIndex(index: number) {
+ const e = extraState.bgm[index];
+ currentPlayingBgmName.set(e.name);
+ dispatch(setGuiAsset({ asset: 'titleBgm', value: e.url }));
+ }
+
+ const showBgmList = extraState.bgm.map((e, i) => {
+ let className = styles.bgmElement;
+ if (e.name === currentPlayingBgmName.value) {
+ className = className + ' ' + styles.bgmElement_active;
+ }
+ return (
+ {
+ playSeClick();
+ currentPlayingBgmName.set(e.name);
+ dispatch(setGuiAsset({ asset: 'titleBgm', value: e.url }));
+ }}
+ key={e.name}
+ className={className}
+ style={{
+ animationDelay: `${i * 150}ms`,
+ }}
+ onMouseEnter={playSeEnter}
+ >
+ {e.name}
+
+ );
+ });
+ return (
+
+
+
{
+ playSeClick();
+ if (foundCurrentBgmIndex <= 0) {
+ setBgmByIndex(bgmListLen - 1);
+ } else {
+ setBgmByIndex(foundCurrentBgmIndex - 1);
+ }
+ }}
+ onMouseEnter={playSeEnter}
+ className={styles.bgmControlButton}
+ >
+
+
+
{
+ playSeClick();
+ const bgmControl: HTMLAudioElement = document.getElementById('currentBgm') as HTMLAudioElement;
+ bgmControl?.play().then();
+ }}
+ onMouseEnter={playSeEnter}
+ className={styles.bgmControlButton}
+ >
+
+
+
{
+ playSeClick();
+ if (foundCurrentBgmIndex >= bgmListLen - 1) {
+ setBgmByIndex(0);
+ } else {
+ setBgmByIndex(foundCurrentBgmIndex + 1);
+ }
+ }}
+ onMouseEnter={playSeEnter}
+ className={styles.bgmControlButton}
+ >
+
+
+
{
+ playSeClick();
+ const bgmControl: HTMLAudioElement = document.getElementById('currentBgm') as HTMLAudioElement;
+ bgmControl.pause();
+ }}
+ onMouseEnter={playSeEnter}
+ className={styles.bgmControlButton}
+ >
+
+
+
{foundCurrentBgmName}
+
{
+ playSeClick();
+ isShowBgmList.set(!isShowBgmList.value);
+ }}
+ onMouseEnter={playSeEnter}
+ className={styles.bgmControlButton}
+ style={{ marginLeft: 'auto' }}
+ >
+
+
+
+ {isShowBgmList.value &&
{showBgmList}
}
+
+ );
+}
diff --git a/packages/webgal/src/UI/Extra/ExtraCg.tsx b/packages/webgal/src/UI/Extra/ExtraCg.tsx
new file mode 100644
index 000000000..3f1ebac5e
--- /dev/null
+++ b/packages/webgal/src/UI/Extra/ExtraCg.tsx
@@ -0,0 +1,75 @@
+import styles from '@/UI/Extra/extra.module.scss';
+import React from 'react';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { useValue } from '@/hooks/useValue';
+import './extraCG_animation_List.scss';
+import { ExtraCgElement } from '@/UI/Extra/ExtraCgElement';
+import useSoundEffect from '@/hooks/useSoundEffect';
+
+export function ExtraCg() {
+ const cgPerPage = 8;
+ const extraState = useSelector((state: RootState) => state.userData.appreciationData);
+ const pageNumber = Math.ceil(extraState.cg.length / cgPerPage);
+ // const pageNumber = 10;
+ const currentPage = useValue(1);
+ const { playSeEnter, playSeClick } = useSoundEffect();
+
+ // 开始生成立绘鉴赏的图片
+ const showCgList = [];
+ const len = extraState.cg.length;
+ for (
+ let i = (currentPage.value - 1) * cgPerPage;
+ i < Math.min(len, (currentPage.value - 1) * cgPerPage + cgPerPage);
+ i++
+ ) {
+ const index = i - (currentPage.value - 1) * cgPerPage;
+ const deg = Random(-5, 5);
+ const temp = (
+
+ );
+ showCgList.push(temp);
+ }
+
+ // 生成cg鉴赏的导航
+ const showNav = [];
+ for (let i = 1; i <= pageNumber; i++) {
+ let className = styles.cgNav;
+ if (currentPage.value === i) {
+ className = className + ' ' + styles.cgNav_active;
+ }
+ const temp = (
+ {
+ currentPage.set(i);
+ playSeClick();
+ }}
+ key={'nav' + i}
+ onMouseEnter={playSeEnter}
+ className={className}
+ >
+ {i}
+
+ );
+ showNav.push(temp);
+ }
+
+ return (
+
+ );
+}
+
+function Random(min: number, max: number) {
+ return Math.round(Math.random() * (max - min)) + min;
+}
diff --git a/packages/webgal/src/UI/Extra/ExtraCgElement.tsx b/packages/webgal/src/UI/Extra/ExtraCgElement.tsx
new file mode 100644
index 000000000..473539e36
--- /dev/null
+++ b/packages/webgal/src/UI/Extra/ExtraCgElement.tsx
@@ -0,0 +1,65 @@
+import { useValue } from '@/hooks/useValue';
+import styles from '@/UI/Extra/extra.module.scss';
+import React from 'react';
+import useSoundEffect from '@/hooks/useSoundEffect';
+
+interface IProps {
+ name: string;
+ imgUrl: string;
+ transformDeg: number;
+ index: number;
+}
+
+export function ExtraCgElement(props: IProps) {
+ const showFull = useValue(false);
+ const { playSeEnter, playSeClick } = useSoundEffect();
+ return (
+ <>
+ {showFull.value && (
+ {
+ showFull.set(!showFull.value);
+ playSeClick();
+ }}
+ className={styles.showFullContainer}
+ onMouseEnter={playSeEnter}
+ >
+
+
+ )}
+ {
+ showFull.set(!showFull.value);
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ style={{
+ // transform: `rotate(${deg}deg)`,
+ animation: `cg_softIn_${props.transformDeg} 1.5s ease-out ${100 + props.index * 100}ms forwards `,
+ }}
+ key={props.name}
+ className={styles.cgElement}
+ >
+
+
+ >
+ );
+}
diff --git a/packages/webgal/src/UI/Extra/extra.module.scss b/packages/webgal/src/UI/Extra/extra.module.scss
new file mode 100644
index 000000000..c50c8c7eb
--- /dev/null
+++ b/packages/webgal/src/UI/Extra/extra.module.scss
@@ -0,0 +1,279 @@
+.extra {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 14;
+ background-image: linear-gradient(135deg, #93a5cf 0%, #e4efe9 100%);;
+ padding: 2em 2em 2em 2em;
+ box-sizing: border-box;
+}
+
+.extra_top {
+ padding: 0 0 0 0;
+ display: flex;
+ height: 10%;
+}
+
+.extra_top_icon {
+ padding: 0.6em 0.6em 0 0.6em;
+ border-radius: 1000px;
+ transform: translate(0, -13px);
+ cursor: pointer;
+}
+
+.extra_top_icon:hover {
+ background: rgba(255, 255, 255, 0.25);
+ animation: extra_icon_softin 0.25s ease-out forwards;
+}
+
+@keyframes extra_icon_softin {
+ 0% {
+ background: rgba(255, 255, 255, 0);
+ }
+ 100% {
+ background: rgba(0, 0, 0, 0.25);
+ }
+}
+
+
+.extra_title {
+ font-family: "思源宋体", serif;
+ height: 100%;
+ line-height: 100%;
+ font-size: 325%;
+ font-weight: bold;
+ color: transparent;
+ background: linear-gradient(150deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 1) 75%, #51A8DD 100%);
+ -webkit-background-clip: text;
+}
+
+.mainContainer {
+ box-sizing: border-box;
+ padding: 0 2em 0 2em;
+ display: flex;
+ height: 92%;
+ flex-flow: column;
+}
+
+.bgmContainer {
+ left: 50px;
+ right: 50px;
+ bottom: 30px;
+ position: absolute;
+ overflow: auto;
+ //width: 100%;
+ //height: 70%;
+ box-sizing: border-box;
+ display: flex;
+ flex-flow: column-reverse;
+ align-content: center;
+ background-image: linear-gradient(315deg, rgba(163, 189, 237, 0.95) 0%, rgba(105, 145, 199, 0.95) 100%);
+ padding: 1em 2em 1em 2em;
+ border-radius: 4px;
+ transition: max-height 0.5s;
+ z-index: 2;
+}
+
+.bgmListContainer {
+ z-index: 2;
+ bottom: 0;
+ //overflow: hidden;
+ width: 100%;
+ box-sizing: border-box;
+ display: flex;
+ flex: 1;
+ flex-flow: row;
+ justify-content: flex-start;
+ align-items: flex-start;
+ margin: 0 0 15px 0;
+ flex-wrap: wrap;
+ overflow: auto;
+}
+
+.bgmPlayerMain {
+ display: flex;
+}
+
+.bgmControlButton {
+ padding: 0.6em 1.2em 0.2em 1.2em;
+ margin: 0 5px 0 5px;
+ box-sizing: border-box;
+ border-radius: 4px;
+ border: 1px solid rgba(255, 255, 255, 0.5);
+ cursor: pointer;
+ transition: background-color 0.33s, color 0.33s;
+ flex-shrink: 0;
+}
+
+.bgmControlButton:hover {
+ box-shadow: 0 0 10px 5px rgba(255, 255, 255, 0.35);
+}
+
+.bgmName {
+ color: rgba(255, 255, 255, 0.8);
+ font-family: "思源宋体", serif;
+ font-size: 155%;
+ margin: 5px 5px 0 15px;
+ overflow: hidden;
+}
+
+.bgmElement {
+ font-family: "思源宋体", serif;
+ padding: 0.5em 1em 0.5em 1em;
+ overflow: hidden;
+ background-color: rgba(0, 0, 0, 0.1);
+ border-radius: 5px;
+ color: white;
+ font-size: 125%;
+ margin: 0.5em 1em 0.5em 0.5em;
+ transition: background-color 1s, color 1s;
+ opacity: 1;
+ //animation: bgmElement_In 0.5s forwards ease-out;
+ cursor: pointer;
+ width: 28%;
+ flex-shrink: 0;
+}
+
+@keyframes bgmElement_In {
+ 0% {
+ opacity: 0.95;
+ //transform: translate(-50px, 0);
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+.bgmElement:hover {
+ background-color: rgba(255, 255, 255, 0.65);
+ color: #666;
+ transition: background-color 0.5s, color 0.5s;
+}
+
+.bgmElement_active {
+ background-color: rgba(255, 255, 255, 0.85) !important;
+ color: #666;
+}
+
+.cgMain {
+ width: 100%;
+ height: 88%;
+}
+
+.cgContainer {
+ width: 100%;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: flex-start;
+ align-items: flex-start;
+ align-content: flex-start;
+ height: 90%;
+ box-sizing: border-box;
+ padding: 4em 0 0 2em;
+}
+
+.cgElement {
+ width: 22.5%;
+ height: 37.5%;
+ background-color: rgba(255, 255, 255, 0.75);
+ box-shadow: 0 0 15px 5px rgba(0, 0, 0, 0.35);
+ box-sizing: border-box;
+ padding: 0.75em 0.75em 0.75em 0.75em;
+ opacity: 0;
+ margin: 1em 1em 1em 1em;
+ animation-delay: 100ms;
+ z-index: 1;
+ position: relative;
+ cursor: pointer;
+}
+
+.cgShowDiv {
+ height: 8%;
+ width: 100%;
+ display: flex;
+ flex-flow: row;
+ justify-content: center;
+ align-items: flex-end;
+}
+
+.cgShowDivWarpper {
+ display: flex;
+ flex-flow: row;
+ justify-content: center;
+ align-items: flex-end;
+ //background: rgba(255, 255, 255, 0.35);
+ border-radius: 7px;
+ padding: 12px 15px;
+}
+
+.cgNav {
+ font-size: 170%;
+ //background-color: rgba(0, 0, 0, 0.2);
+ color: white;
+ padding: 0.12em 1em 0.12em 1em;
+ margin: 0 0.25em 0 0.25em;
+ //width: 20px;
+ text-align: center;
+ cursor: pointer;
+ transition: background-color 0.5s, color 0.5s, font-weight 0.5s;
+ border-radius: 7px;
+}
+
+.cgNav:first-child {
+ margin-left: 0;
+}
+
+.cgNav:last-child {
+ margin-right: 0;
+}
+
+.cgNav_active {
+ background-color: rgba(0, 92, 175, 0.1) !important;
+ color: #005CAF;
+ font-weight: bold;
+}
+
+.cgNav:hover {
+ background-color: rgba(0, 92, 175, 0.05);
+}
+
+
+.showFullContainer {
+ z-index: 13;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.1);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.showFullCgMain {
+ cursor: pointer;
+ width: 80%;
+ height: 80%;
+ box-sizing: border-box;
+ padding: 2em 2em 2em 2em;
+ background: rgba(255, 255, 255, 0.95);
+ animation: fullCgIn 0.5s ease-out forwards;
+ opacity: 0;
+}
+
+$initialTransform: scale(1.05, 1.05) translate(-25px, -50px) rotateX(-10deg) rotateY(10deg);
+$endTransform: scale(1, 1) translate3d(0, 0, 0);
+
+@keyframes fullCgIn {
+ 0% {
+ opacity: 0;
+ transform: $initialTransform;
+ }
+ 100% {
+ opacity: 1;
+ transform: $endTransform;
+ }
+}
diff --git a/packages/webgal/src/UI/Extra/extraCG_animation_List.scss b/packages/webgal/src/UI/Extra/extraCG_animation_List.scss
new file mode 100644
index 000000000..21522f6a2
--- /dev/null
+++ b/packages/webgal/src/UI/Extra/extraCG_animation_List.scss
@@ -0,0 +1,123 @@
+$initialTransform: scale(1.15, 1.15) translate(-50px, -125px) rotateX(-25deg) rotateY(25deg);
+$endTransform: scale(1, 1) translate3d(0, 0, 0);
+
+@keyframes cg_softIn_-5 {
+ 0% {
+ opacity: 0;
+ transform: $initialTransform;
+ }
+ 100% {
+ opacity: 1;
+ transform: $endTransform + rotate(-5deg);
+ }
+}
+
+@keyframes cg_softIn_-4 {
+ 0% {
+ opacity: 0;
+ transform: $initialTransform;
+ }
+ 100% {
+ opacity: 1;
+ transform: $endTransform + rotate(-4deg);
+ }
+}
+
+@keyframes cg_softIn_-3 {
+ 0% {
+ opacity: 0;
+ transform: $initialTransform;
+ }
+ 100% {
+ opacity: 1;
+ transform: $endTransform + rotate(-3deg);
+ }
+}
+
+@keyframes cg_softIn_-2 {
+ 0% {
+ opacity: 0;
+ transform: $initialTransform;
+ }
+ 100% {
+ opacity: 1;
+ transform: $endTransform + rotate(-2deg);
+ }
+}
+
+@keyframes cg_softIn_-1 {
+ 0% {
+ opacity: 0;
+ transform: $initialTransform;
+ }
+ 100% {
+ opacity: 1;
+ transform: $endTransform + rotate(-1deg);
+ }
+}
+
+@keyframes cg_softIn_0 {
+ 0% {
+ opacity: 0;
+ transform: $initialTransform;
+ }
+ 100% {
+ opacity: 1;
+ transform: $endTransform + rotate(0);
+ }
+}
+
+@keyframes cg_softIn_1 {
+ 0% {
+ opacity: 0;
+ transform: $initialTransform;
+ }
+ 100% {
+ opacity: 1;
+ transform: $endTransform + rotate(1deg);
+ }
+}
+
+@keyframes cg_softIn_2 {
+ 0% {
+ opacity: 0;
+ transform: $initialTransform;
+ }
+ 100% {
+ opacity: 1;
+ transform: $endTransform + rotate(2deg);
+ }
+}
+
+@keyframes cg_softIn_3 {
+ 0% {
+ opacity: 0;
+ transform: $initialTransform;
+ }
+ 100% {
+ opacity: 1;
+ transform: $endTransform + rotate(3deg);
+ }
+}
+
+@keyframes cg_softIn_4 {
+ 0% {
+ opacity: 0;
+ transform: $initialTransform;
+ }
+ 100% {
+ opacity: 1;
+ transform: $endTransform + rotate(4deg);
+ }
+}
+
+@keyframes cg_softIn_5 {
+ 0% {
+ opacity: 0;
+ transform: $initialTransform;
+ }
+ 100% {
+ opacity: 1;
+ transform: $endTransform + rotate(5deg);
+ }
+}
diff --git a/packages/webgal/src/UI/GlobalDialog/GlobalDialog.tsx b/packages/webgal/src/UI/GlobalDialog/GlobalDialog.tsx
new file mode 100644
index 000000000..baaf12ac7
--- /dev/null
+++ b/packages/webgal/src/UI/GlobalDialog/GlobalDialog.tsx
@@ -0,0 +1,75 @@
+import styles from './globalDialog.module.scss';
+import ReactDOM from 'react-dom';
+import { useSelector } from 'react-redux';
+import { RootState, webgalStore } from '@/store/store';
+import { setVisibility } from '@/store/GUIReducer';
+import { useSEByWebgalStore } from '@/hooks/useSoundEffect';
+
+export default function GlobalDialog() {
+ const isGlobalDialogShow = useSelector((state: RootState) => state.GUI.showGlobalDialog);
+ return <>{isGlobalDialogShow &&
}>;
+}
+
+interface IShowGlobalDialogProps {
+ title: string;
+ leftText: string;
+ rightText: string;
+ leftFunc: Function;
+ rightFunc: Function;
+}
+
+export function showGlogalDialog(props: IShowGlobalDialogProps) {
+ const { playSeClick, playSeEnter } = useSEByWebgalStore();
+ webgalStore.dispatch(setVisibility({ component: 'showGlobalDialog', visibility: true }));
+ const handleLeft = () => {
+ playSeClick();
+ props.leftFunc();
+ hideGlobalDialog();
+ };
+ const handleRight = () => {
+ playSeClick();
+ props.rightFunc();
+ hideGlobalDialog();
+ };
+ const renderElement = (
+
+
+
+
{props.title}
+
+
+ {props.leftText}
+
+
+ {props.rightText}
+
+
+
+
+
+ );
+ setTimeout(() => {
+ // eslint-disable-next-line react/no-deprecated
+ ReactDOM.render(renderElement, document.getElementById('globalDialogContainer'));
+ }, 100);
+}
+
+export function hideGlobalDialog() {
+ webgalStore.dispatch(setVisibility({ component: 'showGlobalDialog', visibility: false }));
+}
+
+export function showControls() {
+ webgalStore.dispatch(setVisibility({ component: 'showControls', visibility: true }));
+}
+
+export function hideControls() {
+ webgalStore.dispatch(setVisibility({ component: 'showControls', visibility: false }));
+}
+
+export function switchControls() {
+ if (webgalStore.getState().GUI.showControls === true) {
+ hideControls();
+ } else {
+ showControls();
+ }
+}
diff --git a/packages/webgal/src/UI/GlobalDialog/globalDialog.module.scss b/packages/webgal/src/UI/GlobalDialog/globalDialog.module.scss
new file mode 100644
index 000000000..206d4c785
--- /dev/null
+++ b/packages/webgal/src/UI/GlobalDialog/globalDialog.module.scss
@@ -0,0 +1,85 @@
+.GlobalDialog_main {
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ z-index: 20;
+ background: rgba(15, 37, 64, 0.39);
+ color: white;
+ opacity: 0.5;
+ animation: showGlobalDialog 0.33s forwards;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ //font-family: "WebgalUI", -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
+ font-family: "思源宋体", serif;
+}
+
+.glabalDialog_container_inner {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-flow: column;
+ background: linear-gradient(to right,
+ rgba(0, 92, 175, 0) 0%,
+ rgba(0, 92, 175, 0.5) 33%,
+ rgba(0, 92, 175, 0.85) 50%,
+ rgba(0, 92, 175, 0.5) 66%,
+ rgba(0, 92, 175, 0) 100%
+ );
+ padding: 1em 5em 1.5em 5em;
+}
+
+.glabalDialog_container {
+ height: 20%;
+ width: 100%;
+ border-top: 4px solid;
+ border-bottom: 4px solid;
+ border-image: linear-gradient(to right,
+ rgba(255, 255, 255, 0.05) 0%,
+ rgba(255, 255, 255, 0.85) 33%,
+ rgba(255, 255, 255, 1) 50%,
+ rgba(255, 255, 255, 0.85) 66%,
+ rgba(255, 255, 255, 0.05) 100%
+ ) 1;
+ //padding: 1px 1px 1px 1px;
+}
+
+.title {
+ font-size: 300%;
+ text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
+}
+
+.button_list {
+ display: flex;
+ margin: auto 0 0 0;
+}
+
+.button {
+ font-size: 200%;
+ padding: 0.15em 1em 0.15em 1em;
+ margin: 0.2em 1em 0.2em 1em;
+ cursor: pointer;
+ transition: background-color 0.33s, color 0.33s, font-weight 0.33s, transform 0.33s;
+ text-shadow: 0 0 10px rgba(255, 255, 255, 1);
+ border-radius: 5px;
+ //background: rgba(0, 0, 0, 0.05);
+}
+
+.button:hover {
+ font-weight: bold;
+ color: #005caf;
+ transform: scale(1.1, 1.1);
+ text-shadow: 0 0 15px rgba(0, 0, 0, 0);
+ background: rgba(255, 255, 255, 0.85);
+}
+
+@keyframes showGlobalDialog {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
diff --git a/packages/webgal/src/UI/Logo/Logo.tsx b/packages/webgal/src/UI/Logo/Logo.tsx
new file mode 100644
index 000000000..a1b3eb6b8
--- /dev/null
+++ b/packages/webgal/src/UI/Logo/Logo.tsx
@@ -0,0 +1,65 @@
+import { FC, useEffect, useRef } from 'react';
+import styles from './logo.module.scss';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { useValue } from '@/hooks/useValue';
+
+/**
+ * 标识
+ * @constructor
+ */
+const Logo: FC = () => {
+ const GUIState = useSelector((state: RootState) => state.GUI);
+ const logoImage = GUIState.logoImage;
+ const isEnterGame = GUIState.isEnterGame;
+ const currentLogoIndex = useValue(-1);
+ const currentTimeOutId = useValue(-1);
+ const animationDuration = 5000;
+
+ const nextImg = () => {
+ clearTimeout(currentTimeOutId.value);
+ if (currentLogoIndex.value < logoImage.length - 1) {
+ currentLogoIndex.set(currentLogoIndex.value + 1);
+ currentTimeOutId.set(setTimeout(nextImg, animationDuration));
+ } else {
+ currentLogoIndex.set(-1);
+ }
+ };
+
+ useEffect(() => {
+ if (isEnterGame && logoImage.length > 0) {
+ /**
+ * 启动 Enter Logo
+ */
+ currentLogoIndex.set(0);
+ currentTimeOutId.set(setTimeout(nextImg, animationDuration));
+ }
+ }, [isEnterGame]);
+
+ const currentLogoUrl = currentLogoIndex.value === -1 ? '' : logoImage[currentLogoIndex.value];
+ return (
+ <>
+ {currentLogoIndex.value !== -1 && (
+
+ )}
+ {currentLogoUrl !== '' && (
+
+ )}
+ >
+ );
+};
+
+export default Logo;
diff --git a/packages/webgal/src/UI/Logo/logo.module.scss b/packages/webgal/src/UI/Logo/logo.module.scss
new file mode 100644
index 000000000..4005385ee
--- /dev/null
+++ b/packages/webgal/src/UI/Logo/logo.module.scss
@@ -0,0 +1,60 @@
+.Logo_main {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ animation: change-img-anim 5s forwards;
+ background-size: cover;
+ z-index: 14;
+}
+@keyframes change-img-anim {
+ 0%{
+ opacity: 0;
+ }
+ 35%{
+ opacity: 1;
+ }
+ 65%{
+ opacity: 1;
+ }
+ 99%{
+ opacity: 0;
+ }
+ 100%{
+ opacity: 0;
+ display: none;
+ }
+}
+.Logo_Back {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ opacity: 1;
+ bottom: 0;
+ background-size: cover;
+ z-index: 14;
+ background: white;
+}
+
+.animationActive{
+ animation: fadeout 5s forwards;
+}
+
+@keyframes fadeout {
+ 0%{
+ opacity: 1;
+ }
+ 99%{
+ opacity: 0;
+ }
+ 100%{
+ opacity: 0;
+ display: none;
+ }
+}
diff --git a/packages/webgal/src/UI/Menu/Menu.tsx b/packages/webgal/src/UI/Menu/Menu.tsx
new file mode 100644
index 000000000..70e329f54
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Menu.tsx
@@ -0,0 +1,45 @@
+import { FC } from 'react';
+import styles from './menu.module.scss';
+import { MenuPanel } from './MenuPanel/MenuPanel';
+import { Save } from './SaveAndLoad/Save/Save';
+import { Load } from './SaveAndLoad/Load/Load';
+import { Options } from './Options/Options';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { MenuPanelTag } from '@/store/guiInterface';
+
+/**
+ * Menu 页面,包括存读档、选项等
+ * @constructor
+ */
+const Menu: FC = () => {
+ const GUIState = useSelector((state: RootState) => state.GUI);
+ let currentTag;
+ // let menuBgColor = 'linear-gradient(135deg, rgba(253,251,251,0.95) 0%, rgba(235,237,238,1) 100%)';
+ switch (GUIState.currentMenuTag) {
+ case MenuPanelTag.Save:
+ currentTag = ;
+ // menuBgColor = 'linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%)';
+ break;
+ case MenuPanelTag.Load:
+ currentTag = ;
+ // menuBgColor = 'linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%)';
+ break;
+ case MenuPanelTag.Option:
+ currentTag = ;
+ // menuBgColor = 'linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%)';
+ break;
+ }
+ return (
+ <>
+ {GUIState.showMenuPanel && (
+
+ )}
+ >
+ );
+};
+
+export default Menu;
diff --git a/packages/webgal/src/UI/Menu/MenuPanel/MenuIconMap.tsx b/packages/webgal/src/UI/Menu/MenuPanel/MenuIconMap.tsx
new file mode 100644
index 000000000..460c2d2f2
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/MenuPanel/MenuIconMap.tsx
@@ -0,0 +1,32 @@
+import { IMenuPanel } from '@/UI/Menu/MenuPanel/menuPanelInterface';
+import { FolderOpen, Home, Logout, Save, SettingTwo } from '@icon-park/react';
+
+/**
+ * 通过图标名称返回正确的图标
+ * @param props
+ * @constructor
+ */
+export const MenuIconMap = (props: IMenuPanel) => {
+ let returnIcon;
+ switch (props.iconName) {
+ case 'save':
+ returnIcon = ;
+ break;
+ case 'load':
+ returnIcon = ;
+ break;
+ case 'option':
+ returnIcon = ;
+ break;
+ case 'title':
+ returnIcon = ;
+ break;
+ case 'exit':
+ returnIcon = ;
+ break;
+ default:
+ returnIcon =
;
+ }
+
+ return returnIcon;
+};
diff --git a/packages/webgal/src/UI/Menu/MenuPanel/MenuPanel.tsx b/packages/webgal/src/UI/Menu/MenuPanel/MenuPanel.tsx
new file mode 100644
index 000000000..85e1100d6
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/MenuPanel/MenuPanel.tsx
@@ -0,0 +1,116 @@
+import styles from './menuPanel.module.scss';
+import { MenuPanelButton } from './MenuPanelButton';
+import { playBgm } from '@/Core/controller/stage/playBgm';
+import { MenuPanelTag } from '@/store/guiInterface';
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { setMenuPanelTag, setVisibility } from '@/store/GUIReducer';
+import { backToTitle } from '@/Core/controller/gamePlay/backToTitle';
+import useTrans from '@/hooks/useTrans';
+import useSoundEffect from '@/hooks/useSoundEffect';
+import { showGlogalDialog } from '@/UI/GlobalDialog/GlobalDialog';
+
+/**
+ * Menu页的底栏
+ * @constructor
+ */
+export const MenuPanel = () => {
+ // 国际化
+ const t = useTrans('menu.');
+
+ const { playSeClick, playSeDialogOpen, playSePageChange } = useSoundEffect();
+ const GUIState = useSelector((state: RootState) => state.GUI);
+ const dispatch = useDispatch();
+ // 设置Menu按钮的高亮
+ const SaveTagOn = GUIState.currentMenuTag === MenuPanelTag.Save ? ` ${styles.MenuPanel_button_hl}` : ``;
+ const LoadTagOn = GUIState.currentMenuTag === MenuPanelTag.Load ? ` ${styles.MenuPanel_button_hl}` : ``;
+ const OptionTagOn = GUIState.currentMenuTag === MenuPanelTag.Option ? ` ${styles.MenuPanel_button_hl}` : ``;
+
+ // 设置Menu按钮的颜色
+ const SaveTagColor = GUIState.currentMenuTag === MenuPanelTag.Save ? `rgba(74, 34, 93, 0.9)` : `rgba(123,144,169,1)`;
+ const LoadTagColor = GUIState.currentMenuTag === MenuPanelTag.Load ? `rgba(11, 52, 110, 0.9)` : `rgba(123,144,169,1)`;
+ const OptionTagColor =
+ GUIState.currentMenuTag === MenuPanelTag.Option ? `rgba(81, 110, 65, 0.9)` : `rgba(123,144,169,1)`;
+
+ // 设置Menu图标的颜色
+ const SaveIconColor = GUIState.currentMenuTag === MenuPanelTag.Save ? `rgba(74, 34, 93, 0.9)` : `rgba(123,144,169,1)`;
+ const LoadIconColor =
+ GUIState.currentMenuTag === MenuPanelTag.Load ? `rgba(11, 52, 110, 0.9)` : `rgba(123,144,169,1)`;
+ const OptionIconColor =
+ GUIState.currentMenuTag === MenuPanelTag.Option ? `rgba(81, 110, 65, 0.9)` : `rgba(123,144,169,1)`;
+
+ return (
+
+ {
+ playSePageChange();
+ if (GUIState.showTitle) return;
+ dispatch(setMenuPanelTag(MenuPanelTag.Save));
+ }}
+ tagName={t('saving.title')}
+ key="saveButton"
+ />
+ {
+ playSePageChange();
+ dispatch(setMenuPanelTag(MenuPanelTag.Load));
+ }}
+ tagName={t('loadSaving.title')}
+ key="loadButton"
+ />
+ {
+ playSeDialogOpen();
+ showGlogalDialog({
+ title: t('$gaming.buttons.titleTips'),
+ leftText: t('$common.yes'),
+ rightText: t('$common.no'),
+ leftFunc: () => {
+ backToTitle();
+ dispatch(setVisibility({ component: 'showMenuPanel', visibility: false }));
+ },
+ rightFunc: () => {},
+ });
+ }}
+ tagName={t('title.title')}
+ key="titleIcon"
+ />
+ {
+ playSePageChange();
+ dispatch(setMenuPanelTag(MenuPanelTag.Option));
+ }}
+ tagName={t('options.title')}
+ key="optionButton"
+ />
+
+ {
+ playSeClick();
+ dispatch(setVisibility({ component: 'showMenuPanel', visibility: false }));
+ }}
+ tagName={t('exit.title')}
+ key="exitIcon"
+ />
+
+ );
+};
diff --git a/packages/webgal/src/UI/Menu/MenuPanel/MenuPanelButton.tsx b/packages/webgal/src/UI/Menu/MenuPanel/MenuPanelButton.tsx
new file mode 100644
index 000000000..a8d47c11c
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/MenuPanel/MenuPanelButton.tsx
@@ -0,0 +1,33 @@
+import styles from './menuPanel.module.scss';
+import { MenuIconMap } from './MenuIconMap';
+import { IMenuPanel } from '@/UI/Menu/MenuPanel/menuPanelInterface';
+import useSoundEffect from '@/hooks/useSoundEffect';
+
+/**
+ * 菜单标签页切换按钮
+ * @param props
+ * @constructor
+ */
+export const MenuPanelButton = (props: IMenuPanel) => {
+ const { playSePageChange, playSeEnter } = useSoundEffect();
+ let buttonClassName = styles.MenuPanel_button;
+ if (props.hasOwnProperty('buttonOnClassName')) {
+ buttonClassName = buttonClassName + props.buttonOnClassName;
+ }
+ return (
+ {
+ props.clickFunc();
+ // playSePageChange();
+ }}
+ onMouseEnter={playSeEnter}
+ style={{ ...props.style, color: props.tagColor }}
+ >
+
+
+
+ {props.tagName}
+
+ );
+};
diff --git a/packages/webgal/src/UI/Menu/MenuPanel/menuPanel.module.scss b/packages/webgal/src/UI/Menu/MenuPanel/menuPanel.module.scss
new file mode 100644
index 000000000..fc879cbc0
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/MenuPanel/menuPanel.module.scss
@@ -0,0 +1,51 @@
+.MenuPanel_main {
+ width: 100%;
+ height: 10%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ //background-color: rgba(255, 255, 255, 1);
+ //box-shadow: 0 0 45px 15px rgba(0, 0, 0, 0.05);
+ padding: 0 55px;
+}
+
+.MenuPanel_button {
+ padding: 0.25em 15px 0 15px;
+ margin-right: 15px;
+ display: flex;
+ justify-content: center;
+ font-size: 200%;
+ text-align: center;
+ //font-weight: bold;
+ border-radius: 6px;
+ //width: 20%;
+ min-width: 12.5%;
+ cursor: pointer;
+ color: rgba(123,144,169, 1);
+ background: rgba(0, 0, 0, 0);
+ overflow: hidden;
+ //border-right: 1.5px solid rgba(0, 0, 0, 0.15);
+ transition: text-shadow 0.7s, background-color 0.7s;
+}
+
+.MenuPanel_button:last-child{
+ margin-right: 0;
+}
+
+.MenuPanel_button:hover {
+ background-color: rgba(245, 246, 247, 0.15);
+}
+
+.MenuPanel_button:last-child {
+ border-right: none;
+}
+
+.MenuPanel_button_icon {
+ transform: translate(0, 0.125em);
+ padding: 0 0.15em 0 0;
+ margin: 0 0.15em 0 0;
+}
+
+.MenuPanel_button_hl{
+ background-color: rgba(245, 246, 247, 0.35) !important;
+}
diff --git a/packages/webgal/src/UI/Menu/MenuPanel/menuPanelInterface.ts b/packages/webgal/src/UI/Menu/MenuPanel/menuPanelInterface.ts
new file mode 100644
index 000000000..9a38371cb
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/MenuPanel/menuPanelInterface.ts
@@ -0,0 +1,14 @@
+import { CSSProperties } from 'react';
+
+/**
+ * @interface IMenuPanel Menu页面的按钮的参数接口
+ */
+export interface IMenuPanel {
+ clickFunc?: any; // 点击事件触发的函数
+ buttonOnClassName?: string; // 按钮激活(在当前按钮对应页面)时的className
+ tagColor?: string; // 标签颜色
+ iconColor?: string; // 图标颜色
+ tagName?: string; // 标签显示名称
+ iconName: string; // 图标名称
+ style?: CSSProperties;
+}
diff --git a/packages/webgal/src/UI/Menu/Options/Display/Display.tsx b/packages/webgal/src/UI/Menu/Options/Display/Display.tsx
new file mode 100644
index 000000000..b7bc13045
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/Display/Display.tsx
@@ -0,0 +1,113 @@
+import styles from '@/UI/Menu/Options/options.module.scss';
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { NormalOption } from '@/UI/Menu/Options/NormalOption';
+import { NormalButton } from '@/UI/Menu/Options/NormalButton';
+import { setOptionData } from '@/store/userDataReducer';
+import { fullScreenOption, playSpeed, textFont, textSize } from '@/store/userDataInterface';
+import { setStorage } from '@/Core/controller/storage/storageController';
+import { TextPreview } from '@/UI/Menu/Options/TextPreview/TextPreview';
+import useTrans from '@/hooks/useTrans';
+import { OptionSlider } from '../OptionSlider';
+
+export function Display() {
+ const userDataState = useSelector((state: RootState) => state.userData);
+ const dispatch = useDispatch();
+ const t = useTrans('menu.options.pages.display.options.');
+
+ return (
+
+
+ {
+ dispatch(setOptionData({ key: 'fullScreen', value: fullScreenOption.on }));
+ setStorage();
+ },
+ () => {
+ dispatch(setOptionData({ key: 'fullScreen', value: fullScreenOption.off }));
+ setStorage();
+ },
+ ]}
+ currentChecked={userDataState.optionData.fullScreen}
+ />
+
+
+ {
+ dispatch(setOptionData({ key: 'textSpeed', value: playSpeed.slow }));
+ setStorage();
+ },
+ () => {
+ dispatch(setOptionData({ key: 'textSpeed', value: playSpeed.normal }));
+ setStorage();
+ },
+ () => {
+ dispatch(setOptionData({ key: 'textSpeed', value: playSpeed.fast }));
+ setStorage();
+ },
+ ]}
+ currentChecked={userDataState.optionData.textSpeed}
+ />
+
+
+ {
+ dispatch(setOptionData({ key: 'textSize', value: textSize.small }));
+ setStorage();
+ },
+ () => {
+ dispatch(setOptionData({ key: 'textSize', value: textSize.medium }));
+ setStorage();
+ },
+ () => {
+ dispatch(setOptionData({ key: 'textSize', value: textSize.large }));
+ setStorage();
+ },
+ ]}
+ currentChecked={userDataState.optionData.textSize}
+ />
+
+
+ {
+ dispatch(setOptionData({ key: 'textboxFont', value: textFont.song }));
+ setStorage();
+ },
+ () => {
+ dispatch(setOptionData({ key: 'textboxFont', value: textFont.hei }));
+ setStorage();
+ },
+ () => {
+ dispatch(setOptionData({ key: 'textboxFont', value: textFont.lxgw }));
+ setStorage();
+ },
+ ]}
+ currentChecked={userDataState.optionData.textboxFont}
+ />
+
+
+ {
+ const newValue = event.target.value;
+ dispatch(setOptionData({ key: 'textboxOpacity', value: Number(newValue) }));
+ setStorage();
+ }}
+ />
+
+
+ {/* 这是一个临时的组件,用于模拟文本预览的效果 */}
+
+
+
+ );
+}
diff --git a/packages/webgal/src/UI/Menu/Options/NormalButton.tsx b/packages/webgal/src/UI/Menu/Options/NormalButton.tsx
new file mode 100644
index 000000000..9a3e313c0
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/NormalButton.tsx
@@ -0,0 +1,44 @@
+import { ReactElement } from 'react';
+import { INormalButton } from '@/UI/Menu/Options/OptionInterface';
+import styles from './normalButton.module.scss';
+import useSoundEffect from '@/hooks/useSoundEffect';
+
+export const NormalButton = (props: INormalButton) => {
+ const len: number = props.textList.length;
+ const buttonList: Array = [];
+ const { playSeEnter, playSeSwitch } = useSoundEffect();
+ for (let i = 0; i < len; i++) {
+ if (i === props.currentChecked) {
+ const t = (
+ {
+ playSeSwitch();
+ props.functionList[i]();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+ {props.textList[i]}
+
+ );
+ buttonList.push(t);
+ } else {
+ const t = (
+ {
+ playSeSwitch();
+ props.functionList[i]();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+ {props.textList[i]}
+
+ );
+ buttonList.push(t);
+ }
+ }
+ return <>{buttonList}>;
+};
diff --git a/packages/webgal/src/UI/Menu/Options/NormalOption.tsx b/packages/webgal/src/UI/Menu/Options/NormalOption.tsx
new file mode 100644
index 000000000..9954820bf
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/NormalOption.tsx
@@ -0,0 +1,14 @@
+import styles from './normalOption.module.scss';
+
+export const NormalOption = (props: any) => {
+ return (
+
+ {/*
{props.title}
*/}
+ {/*
{props.title}
*/}
+
{props.title}
+
+ {props.children}
+
+
+ );
+};
diff --git a/packages/webgal/src/UI/Menu/Options/OptionInterface.ts b/packages/webgal/src/UI/Menu/Options/OptionInterface.ts
new file mode 100644
index 000000000..504ff5cc3
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/OptionInterface.ts
@@ -0,0 +1,16 @@
+/**
+ * @interface INormalButton 普通按钮的参数
+ */
+import { ChangeEvent } from 'react';
+
+export interface INormalButton {
+ textList: Array;
+ functionList: Array;
+ currentChecked: number;
+}
+
+export interface ISlider {
+ uniqueID: string;
+ onChange: (event: ChangeEvent) => void;
+ initValue: number;
+}
diff --git a/packages/webgal/src/UI/Menu/Options/OptionSlider.tsx b/packages/webgal/src/UI/Menu/Options/OptionSlider.tsx
new file mode 100644
index 000000000..668e2273c
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/OptionSlider.tsx
@@ -0,0 +1,25 @@
+import './slider.css';
+import { ISlider } from '@/UI/Menu/Options/OptionInterface';
+import { useEffect } from 'react';
+import useSoundEffect from '@/hooks/useSoundEffect';
+
+export const OptionSlider = (props: ISlider) => {
+ const { playSeEnter } = useSoundEffect();
+ useEffect(() => {
+ setTimeout(() => {
+ const input = document.getElementById(props.uniqueID);
+ if (input !== null) input.setAttribute('value', props.initValue.toString());
+ }, 1);
+ }, []);
+ return (
+
+
+
+ );
+};
diff --git a/packages/webgal/src/UI/Menu/Options/Options.tsx b/packages/webgal/src/UI/Menu/Options/Options.tsx
new file mode 100644
index 000000000..f2db09af3
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/Options.tsx
@@ -0,0 +1,78 @@
+import { FC, useEffect } from 'react';
+import styles from './options.module.scss';
+import { getStorage } from '@/Core/controller/storage/storageController';
+import { useValue } from '@/hooks/useValue';
+import { System } from '@/UI/Menu/Options/System/System';
+import { Display } from '@/UI/Menu/Options/Display/Display';
+import { Sound } from '@/UI/Menu/Options/Sound/Sound';
+import useTrans from '@/hooks/useTrans';
+import useSoundEffect from '@/hooks/useSoundEffect';
+
+enum optionPage {
+ 'System',
+ 'Display',
+ 'Sound',
+}
+
+export const Options: FC = () => {
+ const { playSeEnter, playSeSwitch } = useSoundEffect();
+ const currentOptionPage = useValue(optionPage.System);
+ useEffect(getStorage, []);
+
+ function getClassName(page: optionPage) {
+ if (page === currentOptionPage.value) {
+ return styles.Options_page_button + ' ' + styles.Options_page_button_active;
+ } else return styles.Options_page_button;
+ }
+
+ const t = useTrans('menu.options.');
+
+ return (
+
+
+
+
+
{
+ currentOptionPage.set(optionPage.System);
+ playSeSwitch();
+ }}
+ className={getClassName(optionPage.System)}
+ onMouseEnter={playSeEnter}
+ >
+ {t('pages.system.title')}
+
+
{
+ currentOptionPage.set(optionPage.Display);
+ playSeSwitch();
+ }}
+ className={getClassName(optionPage.Display)}
+ onMouseEnter={playSeEnter}
+ >
+ {t('pages.display.title')}
+
+
{
+ currentOptionPage.set(optionPage.Sound);
+ playSeSwitch();
+ }}
+ className={getClassName(optionPage.Sound)}
+ onMouseEnter={playSeEnter}
+ >
+ {t('pages.sound.title')}
+
+
+
+ {currentOptionPage.value === optionPage.Display && }
+ {currentOptionPage.value === optionPage.System && }
+ {currentOptionPage.value === optionPage.Sound && }
+
+
+
+ );
+};
diff --git a/packages/webgal/src/UI/Menu/Options/Sound/Sound.tsx b/packages/webgal/src/UI/Menu/Options/Sound/Sound.tsx
new file mode 100644
index 000000000..99f2898a8
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/Sound/Sound.tsx
@@ -0,0 +1,92 @@
+import styles from '@/UI/Menu/Options/options.module.scss';
+import { NormalOption } from '@/UI/Menu/Options/NormalOption';
+import { OptionSlider } from '@/UI/Menu/Options/OptionSlider';
+import { NormalButton } from '@/UI/Menu/Options//NormalButton';
+import { setOptionData } from '@/store/userDataReducer';
+import { setStorage } from '@/Core/controller/storage/storageController';
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import useTrans from '@/hooks/useTrans';
+import { voiceOption } from '@/store/userDataInterface';
+
+export function Sound() {
+ const userDataState = useSelector((state: RootState) => state.userData);
+ const dispatch = useDispatch();
+ const t = useTrans('menu.options.pages.sound.options.');
+
+ return (
+
+
+ {
+ const newValue = event.target.value;
+ dispatch(setOptionData({ key: 'volumeMain', value: Number(newValue) }));
+ setStorage();
+ }}
+ />
+
+
+ {
+ const newValue = event.target.value;
+ dispatch(setOptionData({ key: 'vocalVolume', value: Number(newValue) }));
+ setStorage();
+ }}
+ />
+
+
+ {
+ const newValue = event.target.value;
+ dispatch(setOptionData({ key: 'bgmVolume', value: Number(newValue) }));
+ setStorage();
+ }}
+ />
+
+
+ {
+ const newValue = event.target.value;
+ dispatch(setOptionData({ key: 'seVolume', value: Number(newValue) }));
+ setStorage();
+ }}
+ />
+
+
+ {
+ const newValue = event.target.value;
+ dispatch(setOptionData({ key: 'uiSeVolume', value: Number(newValue) }));
+ setStorage();
+ }}
+ />
+
+
+ {
+ dispatch(setOptionData({ key: 'voiceInterruption', value: voiceOption.yes }));
+ setStorage();
+ },
+ () => {
+ dispatch(setOptionData({ key: 'voiceInterruption', value: voiceOption.no }));
+ setStorage();
+ },
+ ]}
+ currentChecked={userDataState.optionData.voiceInterruption}
+ />
+
+
+ );
+}
diff --git a/packages/webgal/src/UI/Menu/Options/System/About.tsx b/packages/webgal/src/UI/Menu/Options/System/About.tsx
new file mode 100644
index 000000000..f0c2fc5bf
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/System/About.tsx
@@ -0,0 +1,36 @@
+import useTrans from '@/hooks/useTrans';
+import { Left } from '@icon-park/react';
+import s from './about.module.scss';
+import { __INFO } from '@/config/info';
+
+export default function About(props: { onClose: () => void }) {
+ const t = useTrans('menu.options.pages.system.options.about.');
+ return (
+
+
+
+
+
{t('subTitle')}
+
{t('version')}
+
{__INFO.version}
+
{t('source')}
+
+
{t('contributors')}
+
+
{t('website')}
+
+
+ );
+}
diff --git a/packages/webgal/src/UI/Menu/Options/System/System.tsx b/packages/webgal/src/UI/Menu/Options/System/System.tsx
new file mode 100644
index 000000000..6d48e0011
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/System/System.tsx
@@ -0,0 +1,204 @@
+import styles from '@/UI/Menu/Options/options.module.scss';
+import { NormalOption } from '@/UI/Menu/Options/NormalOption';
+import { NormalButton } from '@/UI/Menu/Options/NormalButton';
+import { resetAllData, resetOptionSet, setOptionData } from '@/store/userDataReducer';
+import { IUserData, playSpeed } from '@/store/userDataInterface';
+import { getStorage, setStorage, dumpToStorageFast } from '@/Core/controller/storage/storageController';
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState, webgalStore } from '@/store/store';
+import { showGlogalDialog } from '@/UI/GlobalDialog/GlobalDialog';
+import localforage from 'localforage';
+import { logger } from '@/Core/util/logger';
+import useTrans from '@/hooks/useTrans';
+import useLanguage from '@/hooks/useLanguage';
+import languages, { language } from '@/config/language';
+import { useState } from 'react';
+import About from '@/UI/Menu/Options/System/About';
+import { WebGAL } from '@/Core/WebGAL';
+import useSoundEffect from '@/hooks/useSoundEffect';
+import savesReducer, { ISavesData, saveActions } from '@/store/savesReducer';
+import { dumpFastSaveToStorage, dumpSavesToStorage } from '@/Core/controller/storage/savesController';
+
+interface IExportGameData {
+ userData: IUserData;
+ saves: ISavesData;
+}
+
+export function System() {
+ const userDataState = useSelector((state: RootState) => state.userData);
+ const userSavesState = useSelector((state: RootState) => state.saveData);
+ const dispatch = useDispatch();
+ const setLanguage = useLanguage();
+ const t = useTrans('menu.options.pages.system.options.');
+ const { playSeDialogOpen } = useSoundEffect();
+
+ function exportSaves() {
+ const gameData: IExportGameData = {
+ userData: userDataState,
+ saves: userSavesState,
+ };
+
+ const saves = JSON.stringify(gameData);
+ if (saves !== null) {
+ // @ts-ignore
+ const blob = new Blob([saves], { type: 'application/json' });
+ const blobUrl = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = blobUrl;
+ a.download = 'saves.json';
+ a.click();
+ a.remove();
+ }
+ }
+
+ function importSavesEventHandler(ev: any) {
+ // const t = useTrans('menu.options.pages.system.options.');
+
+ const file = ev.target.files[0];
+ const reader = new FileReader();
+ reader.onload = (evR) => {
+ const saves = evR!.target!.result as string;
+ try {
+ const saveAsObj: IExportGameData = JSON.parse(saves);
+ playSeDialogOpen();
+ showGlogalDialog({
+ title: t('gameSave.dialogs.import.title'),
+ leftText: t('$common.yes'),
+ rightText: t('$common.no'),
+ leftFunc: async () => {
+ await localforage.setItem(WebGAL.gameKey, saveAsObj.userData).then(() => {
+ logger.info(t('gameSave.dialogs.import.tip'));
+ });
+ getStorage();
+ webgalStore.dispatch(saveActions.replaceSaveGame(saveAsObj.saves.saveData));
+ webgalStore.dispatch(saveActions.setFastSave(saveAsObj.saves.quickSaveData));
+ dumpFastSaveToStorage();
+ dumpSavesToStorage(0, 200);
+ },
+ rightFunc: () => {},
+ });
+ } catch (e) {
+ logger.error(t('gameSave.dialogs.import.error'), e);
+ }
+ // window.location.reload(); // dirty: 强制刷新 UI
+ };
+ reader.readAsText(file, 'UTF-8');
+ }
+
+ function importSaves() {
+ const inputElement = document.createElement('input');
+ inputElement.type = 'file';
+ inputElement.onchange = importSavesEventHandler;
+ inputElement.click();
+ }
+
+ const [showAbout, setShowAbout] = useState(false);
+
+ function toggleAbout() {
+ setShowAbout(!showAbout);
+ }
+
+ return (
+
+ {showAbout &&
}
+ {!showAbout && (
+ <>
+
+ {
+ dispatch(setOptionData({ key: 'autoSpeed', value: playSpeed.slow }));
+ setStorage();
+ },
+ () => {
+ dispatch(setOptionData({ key: 'autoSpeed', value: playSpeed.normal }));
+ setStorage();
+ },
+ () => {
+ dispatch(setOptionData({ key: 'autoSpeed', value: playSpeed.fast }));
+ setStorage();
+ },
+ ]}
+ currentChecked={userDataState.optionData.autoSpeed}
+ />
+
+
+ () => setLanguage(language[k as unknown as number] as unknown as language),
+ )}
+ />
+
+
+ {
+ playSeDialogOpen();
+ showGlogalDialog({
+ title: t('resetData.dialogs.clearGameSave'),
+ leftText: t('$common.yes'),
+ rightText: t('$common.no'),
+ leftFunc: () => {
+ dispatch(saveActions.resetSaves());
+ dumpSavesToStorage(0, 200);
+ dumpFastSaveToStorage();
+ },
+ rightFunc: () => {},
+ });
+ },
+ () => {
+ playSeDialogOpen();
+ showGlogalDialog({
+ title: t('resetData.dialogs.resetSettings'),
+ leftText: t('$common.yes'),
+ rightText: t('$common.no'),
+ leftFunc: () => {
+ dispatch(resetOptionSet());
+ dumpToStorageFast();
+ },
+ rightFunc: () => {},
+ });
+ },
+ () => {
+ playSeDialogOpen();
+ showGlogalDialog({
+ title: t('resetData.dialogs.clearAll'),
+ leftText: t('$common.yes'),
+ rightText: t('$common.no'),
+ leftFunc: () => {
+ dispatch(resetAllData());
+ dumpToStorageFast();
+ dispatch(saveActions.resetSaves());
+ dumpSavesToStorage(0, 200);
+ dumpFastSaveToStorage();
+ },
+ rightFunc: () => {},
+ });
+ },
+ ]}
+ currentChecked={3}
+ />
+
+
+
+
+
+ {t('about.title')}
+
+ >
+ )}
+
+ );
+}
diff --git a/packages/webgal/src/UI/Menu/Options/System/about.module.scss b/packages/webgal/src/UI/Menu/Options/System/about.module.scss
new file mode 100644
index 000000000..7cdee3bc9
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/System/about.module.scss
@@ -0,0 +1,46 @@
+.backButton{
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 50px;
+ height: 50px;
+ background: rgba(0,0,0,0.1);
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.backButton:hover{
+ background: rgba(0,0,0,0.2);
+}
+
+.about{
+ padding: 10px 0 0 0;
+}
+
+.icon{
+ display: inline-flex;
+}
+
+.title{
+ color: transparent;
+ background: linear-gradient(to left, #227D51, rgba(81, 110, 65, 1));
+ -webkit-background-clip: text;
+ font-size: 200%;
+ //border-bottom: 2px solid rgba(81, 110, 65, 0.9);
+ padding: 0.15em 0.5em 0.15em 0.1em;
+ font-weight: bold;
+ margin-top: 20px;
+}
+
+.text{
+ color: rgba(81, 110, 65, 1);
+ padding: 0 0 0 10px;
+ font-size: 135%;
+ a{
+ color: rgba(81, 110, 65, 1);
+ }
+}
+
+.contributor{
+ padding: 0 10px 0 0;
+}
diff --git a/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx b/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx
new file mode 100644
index 000000000..4e8bbc4d3
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx
@@ -0,0 +1,64 @@
+import styles from './textPreview.module.scss';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { useFontFamily } from '@/hooks/useFontFamily';
+import { useTextAnimationDuration, useTextDelay } from '@/hooks/useTextOptions';
+import useTrans from '@/hooks/useTrans';
+import { getTextSize } from '@/UI/getTextSize';
+import IMSSTextbox from '@/Stage/TextBox/IMSSTextbox';
+import { compileSentence } from '@/Stage/TextBox/TextBox';
+
+export const TextPreview = (props: any) => {
+ const t = useTrans('menu.options.pages.display.options.');
+ const userDataState = useSelector((state: RootState) => state.userData);
+ const stageState = useSelector((state: RootState) => state.stage);
+ const previewBackground = stageState.bgName;
+ const textDelay = useTextDelay(userDataState.optionData.textSpeed);
+ const textDuration = useTextAnimationDuration(userDataState.optionData.textSpeed);
+ const textboxOpacity = userDataState.optionData.textboxOpacity;
+ const size = getTextSize(userDataState.optionData.textSize) + '%';
+ const font = useFontFamily();
+ const userAgent = navigator.userAgent;
+ const isFirefox = /firefox/i.test(userAgent);
+ const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
+ const previewText = t('textPreview.text');
+ const previewTextArray = compileSentence(previewText, 3);
+ const showNameText = t('textPreview.title');
+ const showNameArray = compileSentence(showNameText, 3);
+ const isHasName = showNameText !== '';
+
+ const Textbox = IMSSTextbox;
+
+ const textboxProps = {
+ textArray: previewTextArray,
+ isText: true,
+ textDelay: textDelay,
+ isHasName: isHasName,
+ showName: showNameArray,
+ currentConcatDialogPrev: '',
+ fontSize: size,
+ currentDialogKey: '',
+ isSafari: isSafari,
+ isFirefox: isFirefox,
+ miniAvatar: '',
+ textDuration: textDuration,
+ font: font,
+ textSizeState: size as unknown as number,
+ lineLimit: 3,
+ isUseStroke: true,
+ textboxOpacity: textboxOpacity,
+ };
+
+ return (
+
+ );
+};
diff --git a/packages/webgal/src/UI/Menu/Options/TextPreview/textPreview.module.scss b/packages/webgal/src/UI/Menu/Options/TextPreview/textPreview.module.scss
new file mode 100644
index 000000000..420b3bd6d
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/TextPreview/textPreview.module.scss
@@ -0,0 +1,12 @@
+.textPreviewMain {
+ z-index: 1;
+ padding: 1em;
+ min-height: 480px;
+ width: 100%;
+}
+
+.textbox {
+ width: 100%;
+ height: 100%;
+ position: relative;
+}
\ No newline at end of file
diff --git a/packages/webgal/src/UI/Menu/Options/normalButton.module.scss b/packages/webgal/src/UI/Menu/Options/normalButton.module.scss
new file mode 100644
index 000000000..2f48b267e
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/normalButton.module.scss
@@ -0,0 +1,23 @@
+.NormalButton {
+ font-size: 150%;
+ box-sizing: border-box;
+ padding: 0.2em 1em 0.2em 1em;
+ background-color: rgba(50, 50, 50, 0.05);
+ margin: 0 0.4em 0 0;
+ color: rgba(160, 170, 160, 1);
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+}
+
+.NormalButton:hover {
+ border-bottom: 2px solid rgba(81, 110, 65, 0.9);
+ color: rgba(81, 110, 65, 0.9);
+ font-weight: bold;
+}
+
+.NormalButtonChecked {
+ background-color: rgba(81, 110, 65, 0.15);
+ border-bottom: 2px solid rgba(81, 110, 65, 0.9);
+ color: rgba(81, 110, 65, 0.9);
+ font-weight: bold;
+}
diff --git a/packages/webgal/src/UI/Menu/Options/normalOption.module.scss b/packages/webgal/src/UI/Menu/Options/normalOption.module.scss
new file mode 100644
index 000000000..8f368f6fc
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/normalOption.module.scss
@@ -0,0 +1,64 @@
+.NormalOption {
+ margin: 0.2em 1em 0.2em 1em;
+ padding: 0.2em 0.2em 0.2em 0.2em;
+ display: flex;
+ flex-flow: column;
+ align-items: flex-start;
+ animation: Elements_in ease-out 0.7s forwards;
+}
+
+.NormalOption_title {
+ //color: rgba(81, 110, 65, 0.9);
+ color: transparent;
+ background: linear-gradient(to left, #227D51, rgba(81, 110, 65, 1));
+ -webkit-background-clip: text;
+ font-size: 200%;
+ //border-bottom: 2px solid rgba(81, 110, 65, 0.9);
+ padding: 0.15em 0.5em 0.15em 0.1em;
+ font-weight: bold;
+}
+
+.NormalOption_title_bef {
+ font-weight: bold;
+ font-size: 200%;
+ content: attr(data-text);
+ position: absolute;
+ -webkit-text-stroke: 3px rgba(0, 0, 0, 1);
+ z-index: -1;
+ padding: 0.15em 0.5em 0.15em 0.1em;
+}
+
+.NormalOption_title_sd {
+ font-weight: bold;
+ color: rgba(0, 0, 0, 0);
+ font-size: 200%;
+ position: absolute;
+ z-index: -1;
+ padding: 0.15em 0.5em 0.15em 0.1em;
+ text-shadow: 0.04em 0.04em rgba(81, 110, 65, 0.9),
+ 0.05em 0.05em rgba(81, 110, 65, 0.9),
+ 0.06em 0.06em rgba(81, 110, 65, 0.9),
+ 0.07em 0.07em rgba(81, 110, 65, 0.9),
+ 0.08em 0.08em rgba(81, 110, 65, 0.9),
+ 0.09em 0.09em rgba(81, 110, 65, 0.9),
+ 0.10em 0.10em rgba(81, 110, 65, 0.9);
+ //0.11em 0.11em rgba(81, 110, 65, 0.9),
+ //0.12em 0.12em rgba(81, 110, 65, 0.9);
+}
+
+.NormalOption_buttonList {
+ padding: 0.5em 0 0.5em 0;
+ display: flex;
+}
+
+@keyframes Elements_in {
+ 0% {
+ opacity: 0;
+ transform: scale(1.03, 1.03) translate(-25px, -20px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: scale(1, 1) translate(0, 0);
+ }
+}
\ No newline at end of file
diff --git a/packages/webgal/src/UI/Menu/Options/options.module.scss b/packages/webgal/src/UI/Menu/Options/options.module.scss
new file mode 100644
index 000000000..bc3a7622e
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/options.module.scss
@@ -0,0 +1,136 @@
+.Options_main {
+ position: absolute;
+ cursor: default;
+ height: 90%;
+ width: 100%;
+ //background: rgba(255, 255, 255, 0.65);
+}
+
+.Options_top {
+ height: 15%;
+ width: 100%;
+ display: flex;
+ align-items: flex-start;
+}
+
+.Options_title {
+ font-family: "思源宋体", serif;
+ letter-spacing: 0.1em;
+ font-size: 225%;
+ margin: 0.5em 0 0.5em 0;
+ padding: 0.2em 2em 0.2em 1.1em;
+ box-sizing: border-box;
+ //background-color: rgba(255, 255, 255, 0.99);
+ //border-right: .2em solid rgba(81, 110, 65, 0.9);
+ //box-shadow: .1em .1em .8em .2em rgba(0, 0, 0, 0.07);
+}
+
+.Option_title_text {
+ font-size: 165%;
+ font-weight: bold;
+ color: transparent;
+ background: linear-gradient(to left, #227D51, rgba(81, 110, 65, 1));
+ -webkit-background-clip: text;
+ animation: Elements_in ease-out 0.7s forwards;
+}
+
+.Option_title_text_shadow {
+ position: absolute;
+ color: rgba(0, 0, 0, 0);
+ -webkit-text-stroke: 3px rgba(0, 0, 0, 1);
+ z-index: -1;
+}
+
+.Option_title_text_ts {
+ position: absolute;
+ color: rgba(0, 0, 0, 0);
+ text-shadow: 0.04em 0.04em rgba(81, 110, 65, 0.9),
+ 0.05em 0.05em rgba(81, 110, 65, 0.9),
+ 0.06em 0.06em rgba(81, 110, 65, 0.9),
+ 0.07em 0.07em rgba(81, 110, 65, 0.9);
+ //0.08em 0.08em rgba(81, 110, 65, 0.9),
+ //0.09em 0.09em rgba(81, 110, 65, 0.9),
+ //0.10em 0.10em rgba(81, 110, 65, 0.9),
+ //0.11em 0.11em rgba(81, 110, 65, 0.9),
+ //0.12em 0.12em rgba(81, 110, 65, 0.9);
+ z-index: -1;
+}
+
+.Options_main_content {
+ display: flex;
+ flex: 1;
+ padding: 0 0 0 3em;
+ overflow: auto;
+}
+
+.Options_main_content_half {
+ width: 95%;
+ display: flex;
+ flex-flow: row;
+ align-items: flex-start;
+ align-content: flex-start;
+ flex-wrap: wrap;
+ padding: 0 1em 0 1em;
+}
+
+.About_title_text {
+ margin: 0.2em 1em 0.2em 1em;
+ padding: 0.2em 0.2em 0.2em 0.2em;
+ width: 100%;
+ animation: Elements_in ease-out 0.7s forwards;
+ cursor: pointer;
+}
+
+.About_text {
+ font-weight: bold;
+ color: transparent;
+ background: linear-gradient(to left, rgba(34, 125, 81, 0.65), rgba(81, 110, 65, 0.65));
+ -webkit-background-clip: text;
+ font-size: 135%;
+ text-decoration: underline;
+
+}
+
+@keyframes Elements_in {
+ 0% {
+ opacity: 0;
+ transform: scale(1.03, 1.03) translate(-25px, -20px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: scale(1, 1) translate(0, 0);
+ }
+}
+
+.Options_page_container {
+ height: 85%;
+ display: flex;
+ padding: 1em 3.75em 1em 3.75em;
+}
+
+.Options_button_list {
+ animation: Elements_in ease-out 0.7s forwards;
+}
+
+.Options_page_button {
+ font-family: "思源宋体", serif;
+ font-size: 300%;
+ font-weight: bold;
+ color: transparent;
+ background: linear-gradient(to left, #227D51, rgba(81, 110, 65, 1));
+ opacity: 0.35;
+ -webkit-background-clip: text;
+ transition: color 0.33s, background-image 0.33s, opacity 0.33s;
+ cursor: pointer;
+}
+
+.Options_page_button_active {
+ background-image: linear-gradient(to left, #227D51, rgba(81, 110, 65, 1));
+ -webkit-background-clip: text;
+ opacity: 1;
+}
+
+.Options_page_button:hover {
+ opacity: 1;
+}
diff --git a/packages/webgal/src/UI/Menu/Options/slider.css b/packages/webgal/src/UI/Menu/Options/slider.css
new file mode 100644
index 000000000..b9115229c
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/Options/slider.css
@@ -0,0 +1,65 @@
+input[type=range] {
+ -webkit-appearance: none; /* 去掉底部的 track 默认样式,就是整个灰条 */
+ width: 500px; /* Firefox 需要指定明确的宽度 */
+ height: 50px;
+ background: transparent; /* 否则在 Chrome 中是白色背景 */
+ font-size: 100%;
+}
+
+/* 去掉 webkit 内核 滑块 的样式 */
+input[type=range]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+}
+
+input[type=range]:focus {
+ outline: none; /* 去除获取焦点时蓝色的外边框,你也可以自己定制其他你想要的效果 */
+}
+
+/*以下是自定义样式*/
+
+/*滑块样式*/
+input[type=range]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ /*border: 1px solid #000000;*/
+ height: 375%;
+ width: 7.2%;
+ border-radius: 5em;
+ background: #ffffff;
+ cursor: pointer;
+ margin-top: -14px; /* 在 Chrome 中你需要给定一个明确的 margin,但是在 Firefox 和 IE 中这个是固定的 */
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); /* 添加一条炫酷的效果为你的 thumb */
+}
+
+/*轨道样式*/
+input[type=range]::-webkit-slider-runnable-track {
+ width: 100%;
+ height: 20%;
+ cursor: pointer;
+ box-shadow: 1px 1px 7px rgba(0, 0, 0, 0.3);
+ background: rgba(81, 110, 65, 0.9);
+ border-radius: 2em;
+}
+
+input[type=range]:focus::-webkit-slider-runnable-track {
+ background: rgba(81, 110, 65, 0.9);
+}
+
+/* Firefox 同上 */
+input[type=range]::-moz-range-thumb {
+ height: 36px;
+ width: 7.2%;
+ border-radius: 5em;
+ background: #ffffff;
+ cursor: pointer;
+ /*margin-top: -14px; !* 在 Chrome 中你需要给定一个明确的 margin,但是在 Firefox 和 IE 中这个是固定的 *!*/
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); /* 添加一条炫酷的效果为你的 thumb */
+}
+
+input[type=range]::-moz-range-track {
+ width: 100%;
+ height: 20%;
+ cursor: pointer;
+ box-shadow: 1px 1px 7px rgba(0, 0, 0, 0.3);
+ background: rgba(81, 110, 65, 0.9);
+ border-radius: 2em;
+}
diff --git a/packages/webgal/src/UI/Menu/SaveAndLoad/Load/Load.tsx b/packages/webgal/src/UI/Menu/SaveAndLoad/Load/Load.tsx
new file mode 100644
index 000000000..bc0d30479
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/SaveAndLoad/Load/Load.tsx
@@ -0,0 +1,113 @@
+import { CSSProperties, FC, useEffect } from 'react';
+import { loadGame } from '@/Core/controller/storage/loadGame';
+import styles from '../SaveAndLoad.module.scss';
+// import {saveGame} from '@/Core/controller/storage/saveGame';
+import { setStorage } from '@/Core/controller/storage/storageController';
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { setSlPage } from '@/store/userDataReducer';
+import useTrans from '@/hooks/useTrans';
+import { useTranslation } from 'react-i18next';
+import useSoundEffect from '@/hooks/useSoundEffect';
+import { getSavesFromStorage } from '@/Core/controller/storage/savesController';
+
+export const Load: FC = () => {
+ const { playSeClick, playSeEnter, playSePageChange } = useSoundEffect();
+ const userDataState = useSelector((state: RootState) => state.userData);
+ const saveDataState = useSelector((state: RootState) => state.saveData);
+ const dispatch = useDispatch();
+ const page = [];
+ for (let i = 1; i <= 20; i++) {
+ let classNameOfElement = styles.Save_Load_top_button + ' ' + styles.Load_top_button;
+ if (i === userDataState.optionData.slPage) {
+ classNameOfElement = classNameOfElement + ' ' + styles.Save_Load_top_button_on + ' ' + styles.Load_top_button_on;
+ }
+ const element = (
+ {
+ dispatch(setSlPage(i));
+ setStorage();
+ playSePageChange();
+ }}
+ onMouseEnter={playSeEnter}
+ key={'Load_element_page' + i}
+ className={classNameOfElement}
+ >
+
{i}
+
+ );
+ page.push(element);
+ }
+
+ const showSaves = [];
+ // 现在尝试设置10个存档每页
+ const start = (userDataState.optionData.slPage - 1) * 10 + 1;
+ const end = start + 9;
+
+ useEffect(() => {
+ getSavesFromStorage(start, end);
+ }, [start, end]);
+
+ let animationIndex = 0;
+ for (let i = start; i <= end; i++) {
+ animationIndex++;
+ const saveData = saveDataState.saveData[i];
+ let saveElementContent =
;
+ if (saveData) {
+ const speaker = saveData.nowStageState.showName === '' ? '\u00A0' : `${saveData.nowStageState.showName}`;
+ saveElementContent = (
+ <>
+
+
+ {saveData.index}
+
+
+ {saveData.saveTime}
+
+
+
+
+
+
+
{speaker}
+
{saveData.nowStageState.showText}
+
+ >
+ );
+ }
+ // else {
+
+ // }
+ const saveElement = (
+ {
+ loadGame(i);
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ key={'loadElement_' + i}
+ className={styles.Save_Load_content_element}
+ style={{ animationDelay: `${animationIndex * 30}ms` }}
+ >
+ {saveElementContent}
+
+ );
+ showSaves.push(saveElement);
+ }
+
+ const t = useTrans('menu.');
+
+ return (
+
+
+
+
{t('loadSaving.title')}
+
+
{page}
+
+
+ {showSaves}
+
+
+ );
+};
diff --git a/packages/webgal/src/UI/Menu/SaveAndLoad/Save/Save.tsx b/packages/webgal/src/UI/Menu/SaveAndLoad/Save/Save.tsx
new file mode 100644
index 000000000..2f3dce088
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/SaveAndLoad/Save/Save.tsx
@@ -0,0 +1,125 @@
+import { CSSProperties, FC, useEffect } from 'react';
+import styles from '../SaveAndLoad.module.scss';
+import { saveGame } from '@/Core/controller/storage/saveGame';
+import { setStorage } from '@/Core/controller/storage/storageController';
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { setSlPage } from '@/store/userDataReducer';
+import { showGlogalDialog } from '@/UI/GlobalDialog/GlobalDialog';
+import useTrans from '@/hooks/useTrans';
+import { useTranslation } from 'react-i18next';
+import useSoundEffect from '@/hooks/useSoundEffect';
+import { getSavesFromStorage } from '@/Core/controller/storage/savesController';
+
+export const Save: FC = () => {
+ const { playSePageChange, playSeEnter, playSeDialogOpen } = useSoundEffect();
+ const userDataState = useSelector((state: RootState) => state.userData);
+ const savesDataState = useSelector((state: RootState) => state.saveData);
+ const dispatch = useDispatch();
+ const page = [];
+ for (let i = 1; i <= 20; i++) {
+ let classNameOfElement = styles.Save_Load_top_button;
+ if (i === userDataState.optionData.slPage) {
+ classNameOfElement = classNameOfElement + ' ' + styles.Save_Load_top_button_on;
+ }
+ const element = (
+ {
+ dispatch(setSlPage(i));
+ setStorage();
+ playSePageChange();
+ }}
+ onMouseEnter={playSeEnter}
+ key={'Save_element_page' + i}
+ className={classNameOfElement}
+ >
+
{i}
+
+ );
+ page.push(element);
+ }
+
+ const tCommon = useTrans('common.');
+
+ const showSaves = [];
+ // 现在尝试设置10个存档每页
+ const start = (userDataState.optionData.slPage - 1) * 10 + 1;
+ const end = start + 9;
+
+ useEffect(() => {
+ getSavesFromStorage(start, end);
+ }, [start, end]);
+
+ let animationIndex = 0;
+ for (let i = start; i <= end; i++) {
+ animationIndex++;
+ const saveData = savesDataState.saveData[i];
+ let saveElementContent =
;
+ if (saveData) {
+ const speaker = saveData.nowStageState.showName === '' ? '\u00A0' : `${saveData.nowStageState.showName}`;
+ saveElementContent = (
+ <>
+
+
{saveData.index}
+
{saveData.saveTime}
+
+
+
+
+
+
{speaker}
+
{saveData.nowStageState.showText}
+
+ >
+ );
+ }
+ // else {
+
+ // }
+ const saveElement = (
+ {
+ if (savesDataState.saveData[i]) {
+ playSeDialogOpen();
+ showGlogalDialog({
+ title: t('saving.isOverwrite'),
+ leftText: tCommon('yes'),
+ rightText: tCommon('no'),
+ leftFunc: () => {
+ saveGame(i);
+ setStorage();
+ },
+ rightFunc: () => {},
+ });
+ } else {
+ playSePageChange();
+ saveGame(i);
+ }
+ }}
+ onMouseEnter={playSeEnter}
+ key={'saveElement_' + i}
+ className={styles.Save_Load_content_element}
+ style={{ animationDelay: `${animationIndex * 30}ms` }}
+ >
+ {saveElementContent}
+
+ );
+ showSaves.push(saveElement);
+ }
+
+ const t = useTrans('menu.');
+
+ return (
+
+ );
+};
diff --git a/packages/webgal/src/UI/Menu/SaveAndLoad/SaveAndLoad.module.scss b/packages/webgal/src/UI/Menu/SaveAndLoad/SaveAndLoad.module.scss
new file mode 100644
index 000000000..a0f623ed2
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/SaveAndLoad/SaveAndLoad.module.scss
@@ -0,0 +1,267 @@
+.Save_Load_main {
+ height: 90%;
+ width: 100%;
+ position: absolute;
+ cursor: default;
+}
+
+.Save_Load_top {
+ height: 10%;
+ width: 100%;
+ display: flex;
+ //background-color: rgba(255, 255, 255, 1);
+ //box-shadow: 0 0 1.5em 0.1em rgba(0, 0, 0, 0.05);
+ animation: Elements_in ease-out 1s forwards;
+ //border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+ justify-content: center;
+}
+
+.Save_Load_title {
+ font-family: "思源宋体", serif;
+ letter-spacing: 0.1em;
+ width: auto;
+ font-size: 500%;
+ min-width: 350px;
+ //margin: 0 0 0 0.8em;
+ //padding: 0 0.8em 0 0.8em;
+ box-sizing: border-box;
+ //border-bottom: 4px solid #77428D;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: absolute;
+ left: 20px;
+ top:0;
+ z-index: -1;
+ opacity: 0.2;
+ transform: translateY(-10px);
+}
+
+.Save_title_text {
+ font-weight: bold;
+ color: transparent;
+ background: linear-gradient(135deg, #77428D 0%, #B28FCE 100%);
+ text-shadow: 2px 2px 15px rgba(255, 255, 255, 0.5);
+ -webkit-background-clip: text;
+}
+
+
+.Load_title_text {
+ font-weight: bold;
+ color: transparent;
+ background: linear-gradient(135deg, #005CAF 0%, #2EA9DF 100%);
+ text-shadow: 2px 2px 15px rgba(255, 255, 255, 0.5);
+ -webkit-background-clip: text;
+}
+
+.Save_Load_top_buttonList {
+ height: 100%;
+ display: flex;
+ //padding: 0 0 0 2em;
+}
+
+.Save_Load_top_button {
+ cursor: pointer;
+ font-size: 200%;
+ width: 2.05em;
+ text-align: center;
+ color: rgba(0, 0, 0, 0.5);
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+ border-bottom: 4px solid rgba(0, 0, 0, 0);
+ transition: background-color 0.7s, border-bottom-width 0.7s;
+}
+
+.Save_Load_top_button_text {
+ text-align: center;
+ width: 100%;
+ padding: 0 0 3px 0;
+ border-left: 2px solid rgba(0, 0, 0, 0.1);
+}
+
+.Save_Load_top_button:first-child > div {
+ border-left: 2px solid rgba(0, 0, 0, 0);
+}
+
+.Save_Load_top_button_on {
+ font-weight: bold;
+ border-bottom: 4px solid #77428D;
+ color: #77428D;
+ background-color: rgba(119, 66, 141, 0.05);
+}
+
+.Save_Load_top_button:hover {
+ color: #77428D;
+ font-weight: bold;
+ border-bottom: 4px solid #77428D;
+}
+
+.Load_top_button_on {
+ font-weight: bold;
+ border-bottom: 5px solid #005CAF;
+ color: #005CAF;
+ background-color: rgba(0, 92, 175, 0.1);
+}
+
+.Load_top_button:hover {
+ color: #005CAF;
+ font-weight: bold;
+ border-bottom: 5px solid #005CAF;
+}
+
+.Save_Load_content {
+ height: 90%;
+ width: 100%;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-evenly;
+ align-items: center;
+}
+
+.Save_Load_content_element {
+ //background: linear-gradient(-45deg, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.5) 100%);
+ background: linear-gradient(-45deg, rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.075));
+ overflow: hidden;
+ //border: 1px solid rgba(255, 255, 255, 1);
+ width: 17.5%;
+ height: 45%;
+ //box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
+ animation: Elements_in 1s ease-out forwards, Elements_in_transform 1s ease-out;
+ opacity: 0;
+ border-radius: 4px;
+ transition: transform 0.25s, box-shadow 0.25s;
+ cursor: pointer;
+}
+
+.Save_Load_content_element:hover {
+ //box-shadow: 0 0 25px 8px rgba(0, 0, 0, 0.1);
+ transform: scale(1.05, 1.05) translate(-0.2em, -0.2em);
+}
+
+
+.Save_Load_content_element_top {
+ font-family: "思源宋体", serif;
+ width: 100%;
+ height: 12%;
+ display: flex;
+}
+
+.Save_Load_content_element_top_index {
+ color: rgba(255, 255, 255, 1);
+ text-align: center;
+ font-size: 155%;
+ height: 100%;
+ width: 20%;
+ background-color: #B28FCE;
+}
+
+.Load_content_elememt_top_index {
+ background-color: #51A8DD;
+}
+
+.Save_Load_content_element_top_date {
+ padding: 0.425em 0 0 0.5em;
+ background-color: #77428D;
+ color: rgba(255, 255, 255, 1);
+ font-size: 115%;
+ height: 100%;
+ width: 80%;
+ font-family: WebgalUI, serif;
+ letter-spacing: 0.1em;
+}
+
+.Load_content_element_top_date {
+ background-color: #005CAF;
+}
+
+.Save_Load_content_text {
+ font-family: "WebgalUI", sans-serif;
+ letter-spacing: 0.05em;
+ color: #373C38;
+ background: linear-gradient(-45deg, rgba(255, 255, 255, 0.75) 0%, rgba(255, 255, 255, 1) 100%);
+ //background: rgba(255,255,255,1);
+ font-size: 120%;
+ height: 40%;
+ width: 100%;
+ //box-sizing: border-box;
+ display: flex;
+ flex-flow: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+}
+
+.Save_Load_content_text_padding {
+ padding: 0.2em 0.75em 0.2em 0.75em;
+}
+
+.Save_Load_content_speaker {
+ box-sizing: border-box;
+ //margin: 0.35em 0 0 0;
+ //background: rgba(0, 0, 0, 0.04);
+ font-weight: bold;
+ color: #77428D;
+ padding: 0.35em 0.8em 0.25em 0.8em;
+ width: 100%;
+ //border-radius: 4px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.Load_content_speaker {
+ color: #005caf;
+}
+
+
+.Load_content_text {
+ background-color: rgba(0, 92, 175, 0.75);
+}
+
+.Save_Load_content_miniRen {
+ width: 100%;
+ height: 48%;
+ position: relative;
+ background-size: cover;
+}
+
+.Save_Load_content_miniRen_bg {
+ background-size: cover;
+ height: 100%;
+ width: 100%;
+ background-position: center;
+}
+
+.Save_Load_content_miniRen_figure {
+ height: 100%;
+ max-height: 100%;
+ max-width: 100%;
+ position: absolute;
+ bottom: 0;
+}
+
+.Save_Load_content_miniRen_figLeft {
+ bottom: 0;
+ left: 0;
+}
+
+.Save_Load_content_miniRen_figRight {
+ bottom: 0;
+ right: 0;
+}
+
+@keyframes Elements_in {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+@keyframes Elements_in_transform {
+ 0% {
+ transform: scale(1.05, 1.05) translate(-25px, -20px) rotateY(15deg) rotateX(-15deg);
+ }
+ 100% {
+ transform: scale(1, 1) translate(0, 0);
+ }
+}
diff --git a/packages/webgal/src/UI/Menu/menu.module.scss b/packages/webgal/src/UI/Menu/menu.module.scss
new file mode 100644
index 000000000..143af6a87
--- /dev/null
+++ b/packages/webgal/src/UI/Menu/menu.module.scss
@@ -0,0 +1,24 @@
+.Menu_main {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ z-index: 16;
+ //backdrop-filter: blur(1px);
+ animation: Menu_ShowSoftly 0.5s forwards;
+ background-image: linear-gradient(to top, #accbee 0%, #e7f0fd 100%);
+}
+
+.Menu_TagContent {
+ width: 100%;
+ height: 90%;
+}
+
+
+@keyframes Menu_ShowSoftly {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
diff --git a/packages/webgal/src/UI/PanicOverlay/PanicImage/PanicImage.tsx b/packages/webgal/src/UI/PanicOverlay/PanicImage/PanicImage.tsx
new file mode 100644
index 000000000..f658637b7
--- /dev/null
+++ b/packages/webgal/src/UI/PanicOverlay/PanicImage/PanicImage.tsx
@@ -0,0 +1,13 @@
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+
+/**
+ * @todo Allow custom panic image from config
+ */
+export const PanicImage = () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/webgal/src/UI/PanicOverlay/PanicImage/panicImage.module.scss b/packages/webgal/src/UI/PanicOverlay/PanicImage/panicImage.module.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/webgal/src/UI/PanicOverlay/PanicOverlay.tsx b/packages/webgal/src/UI/PanicOverlay/PanicOverlay.tsx
new file mode 100644
index 000000000..59ea5f08f
--- /dev/null
+++ b/packages/webgal/src/UI/PanicOverlay/PanicOverlay.tsx
@@ -0,0 +1,22 @@
+import styles from './panicOverlay.module.scss';
+import { useEffect, useState } from 'react';
+import ReactDOM from 'react-dom';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { PanicYoozle } from '@/UI/PanicOverlay/PanicYoozle/PanicYoozle';
+
+export const PanicOverlay = () => {
+ const GUIStore = useSelector((state: RootState) => state.GUI);
+ const [showOverlay, setShowOverlay] = useState(false);
+ const globalVars = useSelector((state: RootState) => state.userData.globalGameVar);
+ const panic = globalVars['Show_panic'];
+ const hidePanic = panic === false;
+ useEffect(() => {
+ const isShowOverlay = GUIStore.showPanicOverlay && !hidePanic;
+ setShowOverlay(isShowOverlay);
+ }, [GUIStore.showPanicOverlay, hidePanic]);
+ return ReactDOM.createPortal(
+ ,
+ document.querySelector('div#panic-overlay')!,
+ );
+};
diff --git a/packages/webgal/src/UI/PanicOverlay/PanicYoozle/PanicYoozle.tsx b/packages/webgal/src/UI/PanicOverlay/PanicYoozle/PanicYoozle.tsx
new file mode 100644
index 000000000..4d2960f20
--- /dev/null
+++ b/packages/webgal/src/UI/PanicOverlay/PanicYoozle/PanicYoozle.tsx
@@ -0,0 +1,36 @@
+import styles from './panicYoozle.module.scss';
+import { useEffect } from 'react';
+
+export const PanicYoozle = () => {
+ useEffect(() => {
+ const panicTitle = 'Yoozle Search';
+ const originalTitle = document.title;
+ document.title = panicTitle;
+ return () => {
+ document.title = originalTitle;
+ };
+ }, []);
+ return (
+
+
+
+
+ Y
+
+ o
+ o
+ z
+ l
+ e
+
+
+
+
+ );
+};
diff --git a/packages/webgal/src/UI/PanicOverlay/PanicYoozle/panicYoozle.module.scss b/packages/webgal/src/UI/PanicOverlay/PanicYoozle/panicYoozle.module.scss
new file mode 100644
index 000000000..696d0d628
--- /dev/null
+++ b/packages/webgal/src/UI/PanicOverlay/PanicYoozle/panicYoozle.module.scss
@@ -0,0 +1,81 @@
+.yoozle_blue {
+ color: #4285f4;
+}
+
+.yoozle_red {
+ color: #db4437;
+}
+
+.yoozle_yellow {
+ color: #f4b400;
+}
+
+.yoozle_green {
+ color: #0f9d58;
+}
+
+.yoozle_e_rotate {
+ display: inline-block;
+ transform: rotate(-12deg);
+}
+
+.yoozle_container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+.yoozle_title {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding-top: 80px;
+ font-family: arial, sans-serif;
+ font-size: 90px;
+}
+
+.yoozle_search {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 20px 10px;
+}
+
+.yoozle_search_bar {
+ width: 40%;
+ line-height: 32px;
+ font-family: arial, sans-serif;
+ font-size: 18px;
+}
+
+.yoozle_search_buttons {
+ padding-top: 13px;
+}
+
+.yoozle_button {
+ background-color: #f8f9fa;
+ border: 1px solid #f8f9fa;
+ border-radius: 4px;
+ color: #3c4043;
+ font-family: arial, sans-serif;
+ font-size: 14px;
+ margin: 11px 4px;
+ padding: 0 16px;
+ line-height: 27px;
+ height: 36px;
+ min-width: 54px;
+ text-align: center;
+ cursor: pointer;
+ user-select: none;
+}
+
+.yoozle_button:hover {
+ box-shadow: 0 1px 1px rgba(0, 0, 0, .1);
+ border: 1px solid #dadce0;
+ color: #202124;
+}
+
+.yoozle_button:focus {
+ border: 1px solid #4285f4;
+ outline: none;
+}
diff --git a/packages/webgal/src/UI/PanicOverlay/panicOverlay.module.scss b/packages/webgal/src/UI/PanicOverlay/panicOverlay.module.scss
new file mode 100644
index 000000000..7619d39d1
--- /dev/null
+++ b/packages/webgal/src/UI/PanicOverlay/panicOverlay.module.scss
@@ -0,0 +1,9 @@
+.panic_overlay_main {
+ margin: 0;
+ position: fixed;
+ //display: none;
+ width: 100%;
+ height: 100%;
+ background-color: white;
+ z-index: 256;
+}
diff --git a/packages/webgal/src/UI/Title/Title.tsx b/packages/webgal/src/UI/Title/Title.tsx
new file mode 100644
index 000000000..15bc0eb4f
--- /dev/null
+++ b/packages/webgal/src/UI/Title/Title.tsx
@@ -0,0 +1,117 @@
+import { FC, useEffect } from 'react';
+import styles from './title.module.scss';
+import { playBgm } from '@/Core/controller/stage/playBgm';
+import { continueGame, startGame } from '@/Core/controller/gamePlay/startContinueGame';
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState, webgalStore } from '@/store/store';
+import { setMenuPanelTag, setVisibility } from '@/store/GUIReducer';
+import { MenuPanelTag } from '@/store/guiInterface';
+import useTrans from '@/hooks/useTrans';
+// import { resize } from '@/Core/util/resize';
+import { hasFastSaveRecord, loadFastSaveGame } from '@/Core/controller/storage/fastSaveLoad';
+import useSoundEffect from '@/hooks/useSoundEffect';
+import useApplyStyle from '@/hooks/useApplyStyle';
+import { fullScreenOption } from '@/store/userDataInterface';
+import { keyboard } from '@/hooks/useHotkey';
+import useConfigData from '@/hooks/useConfigData';
+/**
+ * 标题页
+ * @constructor
+ */
+const Title: FC = () => {
+ const userDataState = useSelector((state: RootState) => state.userData);
+ const GUIState = useSelector((state: RootState) => state.GUI);
+ const dispatch = useDispatch();
+ const fullScreen = userDataState.optionData.fullScreen;
+ const background = GUIState.titleBg;
+ const showBackground = background === '' ? 'rgba(0,0,0,1)' : `url("${background}")`;
+ const t = useTrans('title.');
+ const { playSeEnter, playSeClick } = useSoundEffect();
+
+ const applyStyle = useApplyStyle('UI/Title/title.scss');
+ useConfigData(); // 监听基础ConfigData变化
+ return (
+ <>
+ {GUIState.showTitle &&
}
+ {
+ playBgm(GUIState.titleBgm);
+ dispatch(setVisibility({ component: 'isEnterGame', visibility: true }));
+ if (fullScreen === fullScreenOption.on) {
+ document.documentElement.requestFullscreen();
+ if (keyboard) keyboard.lock(['Escape', 'F11']);
+ }
+ }}
+ onMouseEnter={playSeEnter}
+ />
+ {GUIState.showTitle && (
+
+
+
{
+ startGame();
+ playSeClick();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
{t('start.title')}
+
+
{
+ playSeClick();
+ dispatch(setVisibility({ component: 'showTitle', visibility: false }));
+ continueGame();
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
{t('continue.title')}
+
+
{
+ playSeClick();
+ dispatch(setVisibility({ component: 'showMenuPanel', visibility: true }));
+ dispatch(setMenuPanelTag(MenuPanelTag.Option));
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
{t('options.title')}
+
+
{
+ playSeClick();
+ dispatch(setVisibility({ component: 'showMenuPanel', visibility: true }));
+ dispatch(setMenuPanelTag(MenuPanelTag.Load));
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
{t('load.title')}
+
+
{
+ playSeClick();
+ dispatch(setVisibility({ component: 'showExtra', visibility: true }));
+ }}
+ onMouseEnter={playSeEnter}
+ >
+
{t('extra.title')}
+
+
+
+ )}
+ >
+ );
+};
+
+export default Title;
diff --git a/packages/webgal/src/UI/Title/title.module.scss b/packages/webgal/src/UI/Title/title.module.scss
new file mode 100644
index 000000000..c10fe2a60
--- /dev/null
+++ b/packages/webgal/src/UI/Title/title.module.scss
@@ -0,0 +1,54 @@
+.Title_main {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ z-index: 13;
+}
+
+.Title_buttonList {
+ display: flex;
+ position: absolute;
+ left: 0;
+ min-width: 25%;
+ height: 100%;
+ justify-content: center;
+ align-items: flex-start;
+ flex-flow: column;
+ transition: background 0.75s;
+ padding-left: 120px;
+}
+
+.Title_button {
+ font-weight: bold;
+ text-align: center;
+ flex: 0 1 auto;
+ cursor: pointer;
+ padding: 1em 2em 1em 2em;
+ margin: 20px 0;
+ transition: all 0.33s;
+ background: rgba(255, 255, 255, 0.15);
+ backdrop-filter: blur(5px);
+ border-radius: 4px;
+ transform: skewX(-10deg);
+ background: linear-gradient(to right, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.1));
+}
+
+.Title_button:hover {
+ text-shadow: 0 0 10px rgba(255, 255, 255, 1);
+ padding: 1em 6em 1em 3em;
+}
+
+.Title_button_text {
+ font-size: 165%;
+ color: #fbfbfb;
+ padding: 0 0.5em 0 0.5em;
+ letter-spacing: 0.2em;
+}
+
+.Title_backup_background {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ z-index: 13;
+ background: linear-gradient(135deg, #fdfbfb 0%, #dcddde 100%);
+}
diff --git a/packages/webgal/src/UI/Translation/Translation.tsx b/packages/webgal/src/UI/Translation/Translation.tsx
new file mode 100644
index 000000000..17c13197f
--- /dev/null
+++ b/packages/webgal/src/UI/Translation/Translation.tsx
@@ -0,0 +1,47 @@
+import useLanguage from '@/hooks/useLanguage';
+import { useEffect, useState } from 'react';
+import s from './translation.module.scss';
+import languages, { language } from '@/config/language';
+
+export default function Translation() {
+ const setLanguage = useLanguage();
+
+ const [isShowSelectLanguage, setIsShowSelectLanguage] = useState(false);
+
+ useEffect(() => {
+ const lang = window?.localStorage.getItem('lang');
+ if (!lang) {
+ setIsShowSelectLanguage(true);
+ } else {
+ setLanguage(Number(window?.localStorage.getItem('lang')), false);
+ }
+ }, []);
+
+ const setLang = (langId: language) => {
+ setIsShowSelectLanguage(false);
+ setLanguage(langId);
+ };
+
+ return (
+ <>
+ {isShowSelectLanguage && (
+
+
+
LANGUAGE SELECT
+
+ {Object.keys(languages).map((key) => (
+
setLang(language[key as unknown as language] as unknown as language)}
+ >
+ {languages[key]}
+
+ ))}
+
+
+
+ )}
+ >
+ );
+}
diff --git a/packages/webgal/src/UI/Translation/translation.module.scss b/packages/webgal/src/UI/Translation/translation.module.scss
new file mode 100644
index 000000000..cc95b696d
--- /dev/null
+++ b/packages/webgal/src/UI/Translation/translation.module.scss
@@ -0,0 +1,48 @@
+.trans {
+ height: 100%;
+ width: 100%;
+ background-image: linear-gradient(225deg, #a3bded 0%, #6991c7 100%);
+ position: absolute;
+ z-index: 20;
+}
+
+.langWrapper{
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: 100%;
+ flex-flow: column;
+}
+
+.lang {
+ width: 100%;
+ text-align: center;
+ font-family: "思源宋体", serif;
+ color: transparent;
+ font-size: 300%;
+ background: linear-gradient(150deg, rgb(255, 255, 255) 0%, rgb(255, 255, 255) 75%, #51A8DD 100%);
+ -webkit-background-clip: text;
+}
+
+.langSelect{
+ display: flex;
+ gap: 50px;
+ padding: 50px;
+}
+
+.langSelectButton{
+ font-family: "思源宋体", serif;
+ cursor: pointer;
+ font-size: 200%;
+ color: #FFFFFF;
+ border-radius: 4px;
+ border: 1px solid rgba(255,255,255,0.8);
+ padding: 10px 50px;
+ transition: color 0.33s, background-color 0.33s;
+}
+
+.langSelectButton:hover{
+ background: white;
+ color: #93a5cf;
+}
diff --git a/packages/webgal/src/UI/etc/QuickSL.tsx b/packages/webgal/src/UI/etc/QuickSL.tsx
new file mode 100644
index 000000000..db846ec66
--- /dev/null
+++ b/packages/webgal/src/UI/etc/QuickSL.tsx
@@ -0,0 +1,3 @@
+export const QuickSL = () => {
+ return
;
+};
diff --git a/packages/webgal/src/UI/getTextSize.ts b/packages/webgal/src/UI/getTextSize.ts
new file mode 100644
index 000000000..85824bc0b
--- /dev/null
+++ b/packages/webgal/src/UI/getTextSize.ts
@@ -0,0 +1,12 @@
+export function getTextSize(size: number) {
+ switch (size) {
+ case 0:
+ return 155;
+ case 1:
+ return 205;
+ case 2:
+ return 230;
+ default:
+ return 205;
+ }
+}
diff --git a/packages/webgal/src/assets/fonts/LXGWWenKai-Regular.ttf b/packages/webgal/src/assets/fonts/LXGWWenKai-Regular.ttf
new file mode 100644
index 000000000..a99443f5e
Binary files /dev/null and b/packages/webgal/src/assets/fonts/LXGWWenKai-Regular.ttf differ
diff --git a/packages/webgal/src/assets/fonts/OPPOSans-R.ttf b/packages/webgal/src/assets/fonts/OPPOSans-R.ttf
new file mode 100644
index 000000000..ddadfba63
Binary files /dev/null and b/packages/webgal/src/assets/fonts/OPPOSans-R.ttf differ
diff --git a/packages/webgal/src/assets/fonts/SourceHanSerifCN-Regular.ttf b/packages/webgal/src/assets/fonts/SourceHanSerifCN-Regular.ttf
new file mode 100644
index 000000000..dbafe4eab
Binary files /dev/null and b/packages/webgal/src/assets/fonts/SourceHanSerifCN-Regular.ttf differ
diff --git a/packages/webgal/src/assets/se/click.mp3 b/packages/webgal/src/assets/se/click.mp3
new file mode 100644
index 000000000..4ded50414
Binary files /dev/null and b/packages/webgal/src/assets/se/click.mp3 differ
diff --git a/packages/webgal/src/assets/se/dialog.mp3 b/packages/webgal/src/assets/se/dialog.mp3
new file mode 100644
index 000000000..12274b6ed
Binary files /dev/null and b/packages/webgal/src/assets/se/dialog.mp3 differ
diff --git a/packages/webgal/src/assets/se/license.txt b/packages/webgal/src/assets/se/license.txt
new file mode 100644
index 000000000..b736a141b
--- /dev/null
+++ b/packages/webgal/src/assets/se/license.txt
@@ -0,0 +1,13 @@
+mouse-enter.mp3
+协议:CC0 作者:deadsillyrabbit 来源:https://www.aigei.com
+
+dialog.mp3
+协议:CC0 作者:wobbleboxx 来源:https://www.aigei.com
+
+click.mp3
+协议:CC0 作者:OwlishMedia 来源:https://www.aigei.com
+
+page-flip-1.mp3
+switch-1.wav
+This work has been identified as being free of known restrictions under copyright law, including all related and neighboring rights.
+You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission.
diff --git a/packages/webgal/src/assets/se/mouse-enter.mp3 b/packages/webgal/src/assets/se/mouse-enter.mp3
new file mode 100644
index 000000000..74aa4fa12
Binary files /dev/null and b/packages/webgal/src/assets/se/mouse-enter.mp3 differ
diff --git a/packages/webgal/src/assets/se/page-flip-1.mp3 b/packages/webgal/src/assets/se/page-flip-1.mp3
new file mode 100644
index 000000000..06063a3e3
Binary files /dev/null and b/packages/webgal/src/assets/se/page-flip-1.mp3 differ
diff --git a/packages/webgal/src/assets/se/switch-1.mp3 b/packages/webgal/src/assets/se/switch-1.mp3
new file mode 100644
index 000000000..d6ebab655
Binary files /dev/null and b/packages/webgal/src/assets/se/switch-1.mp3 differ
diff --git a/packages/webgal/src/assets/style/animation.scss b/packages/webgal/src/assets/style/animation.scss
new file mode 100644
index 000000000..d69a2aab5
--- /dev/null
+++ b/packages/webgal/src/assets/style/animation.scss
@@ -0,0 +1,329 @@
+@keyframes centerIn {
+ 0% {
+ opacity: 0;
+ transform: scale(1, 1);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: scale(1, 1);
+ }
+}
+
+@keyframes upIn {
+ 0% {
+ opacity: 0;
+ transform: scale(1, 1) translate(0, 3%);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: scale(1, 1) translate(0, 0);
+ }
+}
+
+@keyframes leftIn {
+ 0% {
+ opacity: 0;
+ transform: scale(1, 1) translate(-3%, 0);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: scale(1, 1) translate(0, 0);
+ }
+}
+
+@keyframes rightIn {
+ 0% {
+ opacity: 0;
+ transform: scale(1, 1) translate(3%, 0);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: scale(1, 1) translate(0, 0);
+ }
+}
+
+@keyframes bg_down {
+ 0% {
+ opacity: 0;
+ transform: scale(1.1, 1.1) translate(0, -3%);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: scale(1, 1) translate(0, 0);
+ }
+}
+
+@keyframes bg_softIn {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+
+@keyframes hideBG {
+ 0% {
+ opacity: 1;
+ }
+
+ 100% {
+ opacity: 0;
+ }
+}
+
+@keyframes shake {
+ 0% {
+ transform: translate(0, 0);
+ }
+
+ 25% {
+ transform: translate(-2%, 0);
+ }
+
+ 75% {
+ transform: translate(2%, 0);
+ }
+
+ 100% {
+ transform: translate(0, 0);
+ }
+}
+
+@keyframes moveBaF {
+ 0% {
+ transform: scale(1, 1);
+ }
+
+ 50% {
+ transform: scale(1.1, 1.1);
+ }
+
+ 100% {
+ transform: scale(1, 1);
+ }
+}
+
+@keyframes showSoftly /* Safari 与 Chrome */
+{
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+@keyframes elementFadeIn {
+ 0% {
+ transform: translate(-15px, -20px) scale(1.03, 1.03);
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+@keyframes controlButtonHover {
+ 0% {
+ background-color: rgba(0, 0, 0, 0);
+ box-shadow: none;
+ }
+ 100% {
+ background-color: rgba(255, 255, 255, 0.25);
+ box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.65);
+ }
+}
+
+@keyframes controlButtonHoverBack {
+ 0% {
+ /*background-color: rgba(255, 255, 255, 0.25);*/
+ /*box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.65);*/
+ }
+ 100% {
+
+ }
+}
+
+@keyframes TitleButtonOnChoose {
+ 0% {
+ transform: scale(1, 1);
+ /*background-color: rgba(255,255,255,0.8);*/
+ }
+
+ 100% {
+ /*box-shadow: 3px 3px 15px rgba(0, 0, 0, 0.5);*/
+ transform: scale(1.1, 1.1);
+ /*background-color: rgba(0,0,0,0.15);*/
+ }
+}
+
+@keyframes TitleButtonNoneChoose {
+ 0% {
+ /*box-shadow: 3px 3px 15px rgba(0, 0, 0, 0.5);*/
+ transform: scale(1.1, 1.1);
+ /*background-color: rgba(0,0,0,0.15);*/
+ }
+
+ 100% {
+
+ }
+}
+
+@keyframes TitleModelHover {
+ 0% {
+ background-color: rgba(0, 0, 0, 0.35);
+ }
+ 100% {
+ background-color: rgba(0, 0, 0, 0.65);
+ }
+}
+
+@keyframes TitleModelNoneHover {
+ 0% {
+ background-color: rgba(0, 0, 0, 0.65);
+ }
+ 100% {
+ background-color: rgba(0, 0, 0, 0.35);
+ }
+}
+
+
+/*背景的演出效果*/
+@keyframes bg_focusLeft {
+ 0% {
+ transform: scale(1, 1) translate(0, 0);
+ filter: blur(0);
+ }
+ 100% {
+ transform: scale(1.15, 1.15) translate(5%, 0);
+ filter: blur(1px);
+ }
+}
+
+@keyframes bg_focusRight {
+ 0% {
+ transform: scale(1, 1) translate(0, 0);
+ filter: blur(0);
+ }
+ 100% {
+ transform: scale(1.15, 1.15) translate(-5%, 0);
+ filter: blur(1px);
+ }
+}
+
+@keyframes bg_LtoR {
+ 0% {
+ transform: scale(1.15, 1.15) translate(5%, 0);
+ filter: blur(1px);
+ }
+ 100% {
+ transform: scale(1.15, 1.15) translate(-5%, 0);
+ filter: blur(1px);
+ }
+}
+
+@keyframes bg_RtoL {
+ 0% {
+ transform: scale(1.15, 1.15) translate(-5%, 0);
+ filter: blur(1px);
+ }
+ 100% {
+ transform: scale(1.15, 1.15) translate(5%, 0);
+ filter: blur(1px);
+ }
+}
+
+@keyframes bg_LtoC {
+ 0% {
+ transform: scale(1.15, 1.15) translate(5%, 0);
+ filter: blur(1px);
+ }
+ 100% {
+ transform: scale(1, 1) translate(0, 0);
+ filter: blur(0);
+ }
+}
+
+@keyframes bg_RtoC {
+ 0% {
+ transform: scale(1.15, 1.15) translate(-5%, 0);
+ filter: blur(1px);
+ }
+ 100% {
+ transform: scale(1, 1) translate(0, 0);
+ filter: blur(0);
+ }
+}
+
+@keyframes bg_focus {
+ 0% {
+ transform: scale(1, 1) translate(0, 0);
+ filter: blur(0);
+ }
+ 100% {
+ transform: scale(1.15, 1.15);
+ filter: blur(1px);
+ }
+}
+
+@keyframes bg_LtoF {
+ 0% {
+ transform: scale(1.15, 1.15) translate(5%, 0);
+ filter: blur(1px);
+ }
+ 100% {
+ transform: scale(1.15, 1.15);
+ filter: blur(1px);
+ }
+}
+
+@keyframes bg_RtoF {
+ 0% {
+ transform: scale(1.15, 1.15) translate(-5%, 0);
+ filter: blur(1px);
+ }
+ 100% {
+ transform: scale(1.15, 1.15);
+ filter: blur(1px);
+ }
+}
+
+@keyframes bg_FtoL {
+ 0% {
+ transform: scale(1.15, 1.15);
+ filter: blur(1px);
+ }
+ 100% {
+ transform: scale(1.15, 1.15) translate(5%, 0);
+ filter: blur(1px);
+ }
+}
+
+@keyframes bg_FtoR {
+ 0% {
+ transform: scale(1.15, 1.15);
+ filter: blur(1px);
+ }
+ 100% {
+ transform: scale(1.15, 1.15) translate(-5%, 0);
+ filter: blur(1px);
+ }
+}
+
+@keyframes bg_FtoC {
+ 0% {
+ transform: scale(1.15, 1.15);
+ filter: blur(1px);
+ }
+ 100% {
+ transform: scale(1, 1) translate(0, 0);
+ filter: blur(0);
+ }
+}
diff --git a/packages/webgal/src/config/index.ts b/packages/webgal/src/config/index.ts
new file mode 100644
index 000000000..a45247feb
--- /dev/null
+++ b/packages/webgal/src/config/index.ts
@@ -0,0 +1,8 @@
+export const SYSTEM_CONFIG = {
+ backlog_size: 200,
+ fast_timeout: 50,
+};
+export const PERFORM_CONFIG = {
+ // 不能小于50
+ textInitialDelay: 80,
+};
diff --git a/packages/webgal/src/config/info.ts b/packages/webgal/src/config/info.ts
new file mode 100644
index 000000000..8fd76da0f
--- /dev/null
+++ b/packages/webgal/src/config/info.ts
@@ -0,0 +1,6 @@
+export const __INFO = {
+ version: 'WebGAL 4.5.9',
+ contributors: [
+ // 现在改为跳转到 GitHub 了
+ ],
+};
diff --git a/packages/webgal/src/config/language.ts b/packages/webgal/src/config/language.ts
new file mode 100644
index 000000000..c5ad0a146
--- /dev/null
+++ b/packages/webgal/src/config/language.ts
@@ -0,0 +1,49 @@
+/**
+ * You can config the languages display in this file.
+ * If you want close someone, please add "//" forward that line.
+ * If you want to add language, please add the language English abbreviation name into language and languages,
+ * also you need to code the name of it show.
+ */
+
+import en from '@/translations/en';
+import jp from '@/translations/jp';
+import zhCn from '@/translations/zh-cn';
+import fr from '@/translations/fr';
+import de from '@/translations/de';
+import zhTw from '@/translations/zh-tw';
+/*
+ Import your translation configs here;
+ example:
+ import myLang from '@/translations/filename of your config file';
+*/
+
+export enum language {
+ zhCn,
+ en,
+ jp,
+ fr,
+ de,
+ zhTw,
+}
+
+const languages: Record
= {
+ zhCn: '中文',
+ en: 'English',
+ jp: '日本語',
+ fr: 'Français',
+ de: 'Deutsch',
+ zhTw: '繁體中文',
+};
+
+export const i18nTranslationResources: Record }> = {
+ en: { translation: en },
+ zhCn: { translation: zhCn },
+ jp: { translation: jp },
+ fr: { translation: fr },
+ de: { translation: de },
+ zhTw: { translation: zhTw },
+};
+
+export const defaultLanguage: language = language.zhCn;
+
+export default languages;
diff --git a/packages/webgal/src/hooks/useApplyStyle.ts b/packages/webgal/src/hooks/useApplyStyle.ts
new file mode 100644
index 000000000..ad25477b1
--- /dev/null
+++ b/packages/webgal/src/hooks/useApplyStyle.ts
@@ -0,0 +1,54 @@
+import { useEffect } from 'react';
+import { WebGAL } from '@/Core/WebGAL';
+import axios from 'axios';
+import { scss2cssinjsParser } from '@/Core/controller/customUI/scss2cssinjsParser';
+import { useValue } from '@/hooks/useValue';
+import { css, injectGlobal } from '@emotion/css';
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { IWebGALStyleObj } from 'webgal-parser/build/types/styleParser';
+import { logger } from '@/Core/util/logger';
+
+export default function useApplyStyle(url: string) {
+ const styleObject = useValue({ classNameStyles: {}, others: '' });
+ const replaced = useSelector((state: RootState) => state.stage.replacedUIlable);
+
+ const applyStyle = (classNameLable: string, fallbackClassName: string) => {
+ // 先看看是否被用户用 applyStyle 指令替换了类名
+ const className = replaced?.[classNameLable] ?? classNameLable;
+ if (Object.keys(styleObject.value.classNameStyles).includes(className)) {
+ const cijClassName = css(styleObject.value.classNameStyles?.[className] ?? '');
+ return `${fallbackClassName} ${cijClassName}`;
+ }
+ return fallbackClassName;
+ };
+
+ const updateStyleFile = async () => {
+ logger.debug('更新 Scss 文件', url);
+ const resp = await axios.get(`game/template/${url}`);
+ const scssStr = resp.data;
+ styleObject.set(scss2cssinjsParser(scssStr));
+ };
+
+ useEffect(() => {
+ updateStyleFile();
+ }, []);
+
+ useEffect(() => {
+ injectGlobal(styleObject.value.others);
+ }, [styleObject.value.others]);
+
+ useRigisterStyleUpdate(updateStyleFile);
+
+ return applyStyle;
+}
+
+function useRigisterStyleUpdate(callback: Function) {
+ const handler = () => {
+ callback();
+ };
+ useEffect(() => {
+ WebGAL.events.styleUpdate.on(handler);
+ return () => WebGAL.events.styleUpdate.off(handler);
+ }, []);
+}
diff --git a/packages/webgal/src/hooks/useConfigData.ts b/packages/webgal/src/hooks/useConfigData.ts
new file mode 100644
index 000000000..830976794
--- /dev/null
+++ b/packages/webgal/src/hooks/useConfigData.ts
@@ -0,0 +1,60 @@
+import { getFastSaveFromStorage, getSavesFromStorage } from '@/Core/controller/storage/savesController';
+import { getStorage } from '@/Core/controller/storage/storageController';
+import { setEbg } from '@/Core/gameScripts/changeBg/setEbg';
+import { assetSetter, fileType } from '@/Core/util/gameAssetsAccess/assetSetter';
+import { WebGAL } from '@/Core/WebGAL';
+import { setGuiAsset, setLogoImage } from '@/store/GUIReducer';
+import { RootState, webgalStore } from '@/store/store';
+import { useEffect } from 'react';
+import { useSelector } from 'react-redux';
+
+const useConfigData = () => {
+ const _map = ['Title_img', 'Game_Logo', 'Title_bgm', 'Game_name', 'Game_key'];
+ const configData = useSelector((state: RootState) => state.userData.globalGameVar);
+ return useEffect(() => {
+ // configData发生变化
+ for (let i in configData) {
+ if (!_map.includes(i)) {
+ continue;
+ }
+ const val = configData[i] as string;
+ switch (i) {
+ case 'Title_img': {
+ const titleUrl = assetSetter(val, fileType.background);
+ webgalStore.dispatch(setGuiAsset({ asset: 'titleBg', value: titleUrl }));
+ setEbg(titleUrl);
+ break;
+ }
+
+ case 'Game_Logo': {
+ const logos = val.split('|');
+ const logoUrlList = logos.map((val) => assetSetter(val, fileType.background));
+ webgalStore.dispatch(setLogoImage(logoUrlList));
+ break;
+ }
+
+ case 'Title_bgm': {
+ const bgmUrl = assetSetter(val, fileType.bgm);
+ webgalStore.dispatch(setGuiAsset({ asset: 'titleBgm', value: bgmUrl }));
+ break;
+ }
+
+ case 'Game_name': {
+ WebGAL.gameName = val;
+ document.title = val;
+ break;
+ }
+
+ case 'Game_key': {
+ WebGAL.gameKey = val;
+ getStorage();
+ getFastSaveFromStorage();
+ getSavesFromStorage(0, 0);
+ break;
+ }
+ }
+ }
+ return () => {};
+ }, [configData.Game_Logo, configData.Game_key, configData.Game_name, configData.Title_bgm, configData.Title_img]);
+};
+export default useConfigData;
diff --git a/packages/webgal/src/hooks/useEscape.ts b/packages/webgal/src/hooks/useEscape.ts
new file mode 100644
index 000000000..3e54c99ef
--- /dev/null
+++ b/packages/webgal/src/hooks/useEscape.ts
@@ -0,0 +1,34 @@
+const escapeMap = [
+ {
+ reg: /\\\\/g,
+ val: '\\',
+ },
+ {
+ reg: /\\\|/g,
+ val: '|',
+ },
+ {
+ reg: /\\:/g,
+ val: ':',
+ },
+ {
+ reg: /\\,/g,
+ val: ',',
+ },
+ {
+ reg: /\\;/g,
+ val: ';',
+ },
+ {
+ reg: /\\./g,
+ val: '.',
+ },
+];
+const useEscape = (val: string): string => {
+ let _res = val;
+ for (let i of escapeMap) {
+ _res = _res.replaceAll(i.reg, i.val);
+ }
+ return _res;
+};
+export default useEscape;
diff --git a/packages/webgal/src/hooks/useFontFamily.ts b/packages/webgal/src/hooks/useFontFamily.ts
new file mode 100644
index 000000000..54c315ae9
--- /dev/null
+++ b/packages/webgal/src/hooks/useFontFamily.ts
@@ -0,0 +1,18 @@
+import { useSelector } from 'react-redux';
+import { RootState } from '@/store/store';
+import { textFont } from '@/store/userDataInterface';
+import { match } from '@/Core/util/match';
+
+export function useFontFamily() {
+ const fontFamily = useSelector((state: RootState) => state.userData.optionData.textboxFont);
+
+ function getFont() {
+ return match(fontFamily)
+ .with(textFont.song, () => '"思源宋体", serif')
+ .with(textFont.lxgw, () => '"LXGW", serif')
+ .with(textFont.hei, () => '"WebgalUI", serif')
+ .default(() => '"WebgalUI", serif');
+ }
+
+ return getFont();
+}
diff --git a/packages/webgal/src/hooks/useFullScreen.ts b/packages/webgal/src/hooks/useFullScreen.ts
new file mode 100644
index 000000000..228ce4fb3
--- /dev/null
+++ b/packages/webgal/src/hooks/useFullScreen.ts
@@ -0,0 +1,34 @@
+import { setStorage } from '@/Core/controller/storage/storageController';
+import { RootState } from '@/store/store';
+import { fullScreenOption } from '@/store/userDataInterface';
+import { setOptionData } from '@/store/userDataReducer';
+import { useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { keyboard } from './useHotkey';
+
+export function useFullScreen() {
+ const userDataState = useSelector((state: RootState) => state.userData);
+ const GUIState = useSelector((state: RootState) => state.GUI);
+ const dispatch = useDispatch();
+ const fullScreen = userDataState.optionData.fullScreen;
+ const isEnterGame = GUIState.isEnterGame;
+
+ useEffect(() => {
+ switch (fullScreen) {
+ case fullScreenOption.on: {
+ if (isEnterGame) {
+ document.documentElement.requestFullscreen();
+ if (keyboard) keyboard.lock(['Escape', 'F11']);
+ }
+ break;
+ }
+ case fullScreenOption.off: {
+ if (document.fullscreenElement) {
+ document.exitFullscreen();
+ if (keyboard) keyboard.unlock();
+ }
+ break;
+ }
+ }
+ }, [fullScreen]);
+}
diff --git a/packages/webgal/src/hooks/useGenSyncRef.ts b/packages/webgal/src/hooks/useGenSyncRef.ts
new file mode 100644
index 000000000..f1f689d8d
--- /dev/null
+++ b/packages/webgal/src/hooks/useGenSyncRef.ts
@@ -0,0 +1,16 @@
+import { useSelector } from 'react-redux';
+import { useEffect, useRef } from 'react';
+
+/**
+ * 生成一个和redux自动同步的Ref对象
+ */
+export function useGenSyncRef(
+ selector: (state: TState) => Selected,
+): { readonly current: Selected } {
+ const Store = useSelector(selector);
+ const Ref = useRef(Store);
+ useEffect(() => {
+ Ref.current = Store;
+ }, [Store]);
+ return Ref;
+}
diff --git a/packages/webgal/src/hooks/useHotkey.tsx b/packages/webgal/src/hooks/useHotkey.tsx
new file mode 100644
index 000000000..2e8ebb41f
--- /dev/null
+++ b/packages/webgal/src/hooks/useHotkey.tsx
@@ -0,0 +1,404 @@
+import { useGenSyncRef } from '@/hooks/useGenSyncRef';
+import { RootState } from '@/store/store';
+import { useMounted, useUnMounted, useUpdated } from '@/hooks/useLifeCycle';
+import { useCallback, useEffect, useRef } from 'react';
+import { componentsVisibility, MenuPanelTag } from '@/store/guiInterface';
+import { setVisibility } from '@/store/GUIReducer';
+import { useDispatch, useSelector } from 'react-redux';
+import { startFast, stopAll, stopFast } from '@/Core/controller/gamePlay/fastSkip';
+import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
+import styles from '@/UI/Backlog/backlog.module.scss';
+import throttle from 'lodash/throttle';
+import { fastSaveGame } from '@/Core/controller/storage/fastSaveLoad';
+import { WebGAL } from '@/Core/WebGAL';
+import { setOptionData } from '@/store/userDataReducer';
+import { setStorage } from '@/Core/controller/storage/storageController';
+import { fullScreenOption } from '@/store/userDataInterface';
+
+// options备用
+export interface HotKeyType {
+ MouseRight: {} | boolean;
+ MouseWheel: {} | boolean;
+ Ctrl: boolean;
+ Esc:
+ | {
+ href: string;
+ nav: 'replace' | 'push';
+ }
+ | boolean;
+ AutoSave: {} | boolean;
+}
+
+export interface Keyboard {
+ lock: (keys: string[]) => Promise;
+ unlock: () => Promise;
+}
+
+export const keyboard: Keyboard | undefined = 'keyboard' in navigator && (navigator.keyboard as any); // FireFox and Safari not support
+
+// export const fastSaveGameKey = `FastSaveKey`;
+// export const isFastSaveKey = `FastSaveActive`;
+
+export function useHotkey(opt?: HotKeyType) {
+ useMouseRightClickHotKey();
+ useMouseWheel();
+ useSkip();
+ usePanic();
+ useFastSaveBeforeUnloadPage();
+ useSpaceAndEnter();
+ useToggleFullScreen();
+}
+
+/**
+ * 右键关闭 & 打开 菜单栏
+ */
+export function useMouseRightClickHotKey() {
+ const GUIStore = useGenSyncRef((state: RootState) => state.GUI);
+ const setComponentVisibility = useSetComponentVisibility();
+ const isGameActive = useGameActive(GUIStore);
+ const isInBackLog = useIsInBackLog(GUIStore);
+ const isOpenedDialog = useIsOpenedDialog(GUIStore);
+ const validMenuPanelTag = useValidMenuPanelTag(GUIStore);
+ const isShowExtra = useIsOpenedExtra(GUIStore);
+ const handleContextMenu = useCallback((ev: MouseEvent) => {
+ if (isOpenedDialog()) {
+ setComponentVisibility('showGlobalDialog', false);
+ ev.preventDefault();
+ return false;
+ }
+ if (isShowExtra()) {
+ setComponentVisibility('showExtra', false);
+ }
+ if (isGameActive()) {
+ setComponentVisibility('showTextBox', !GUIStore.current.showTextBox);
+ }
+ if (isInBackLog()) {
+ setComponentVisibility('showBacklog', false);
+ setComponentVisibility('showTextBox', true);
+ }
+ if (validMenuPanelTag()) {
+ setComponentVisibility('showMenuPanel', false);
+ }
+ ev.preventDefault();
+ return false;
+ }, []);
+ useMounted(() => {
+ document.addEventListener('contextmenu', handleContextMenu);
+ });
+ useUnMounted(() => {
+ document.removeEventListener('contextmenu', handleContextMenu);
+ });
+}
+
+let wheelTimeout = setTimeout(() => {
+ // 初始化,什么也不干
+}, 0);
+
+/**
+ * 滚轮向上打开历史记录
+ * 滚轮向下关闭历史记录
+ * 滚轮向下下一句
+ */
+export function useMouseWheel() {
+ const GUIStore = useGenSyncRef((state: RootState) => state.GUI);
+ const setComponentVisibility = useSetComponentVisibility();
+ const isGameActive = useGameActive(GUIStore);
+ const isInBackLog = useIsInBackLog(GUIStore);
+ const isPanicOverlayOpen = useIsPanicOverlayOpen(GUIStore);
+ const next = useCallback(
+ throttle(() => {
+ nextSentence();
+ }, 100),
+ [],
+ );
+ // 防止一直往下滚的时候顺着滚出历史记录
+ // 问就是抄的999
+ const prevDownWheelTimeRef = useRef(0);
+ const handleMouseWheel = useCallback((ev) => {
+ if (isPanicOverlayOpen()) return;
+ const direction =
+ (ev.wheelDelta && (ev.wheelDelta > 0 ? 'up' : 'down')) ||
+ (ev.detail && (ev.detail < 0 ? 'up' : 'down')) ||
+ 'down';
+ const ctrlKey = ev.ctrlKey;
+ const dom = document.querySelector(`.${styles.backlog_content}`);
+ if (isGameActive() && direction === 'up' && !ctrlKey) {
+ setComponentVisibility('showBacklog', true);
+ setComponentVisibility('showTextBox', false);
+ } else if (isInBackLog() && direction === 'down' && !ctrlKey) {
+ if (dom) {
+ let flag = hasScrollToBottom(dom);
+ let curTime = new Date().getTime();
+ // 滚动到底部 & 非连续滚动
+ if (flag && curTime - prevDownWheelTimeRef.current > 100) {
+ setComponentVisibility('showBacklog', false);
+ setComponentVisibility('showTextBox', true);
+ }
+ prevDownWheelTimeRef.current = curTime;
+ }
+ // setComponentVisibility('showBacklog', false);
+ } else if (isGameActive() && direction === 'down' && !ctrlKey) {
+ clearTimeout(wheelTimeout);
+ WebGAL.gameplay.isFast = true;
+ // 滚轮视作快进
+ setTimeout(() => {
+ WebGAL.gameplay.isFast = false;
+ }, 150);
+ next();
+ }
+ }, []);
+ useMounted(() => {
+ document.addEventListener('wheel', handleMouseWheel);
+ });
+ useUnMounted(() => {
+ document.removeEventListener('wheel', handleMouseWheel);
+ });
+}
+
+/**
+ * Panic Button, use Esc and Backquote
+ */
+export function usePanic() {
+ const panicButtonList = ['Escape', 'Backquote'];
+ const isPanicButton = (ev: KeyboardEvent) =>
+ !ev.isComposing && !ev.defaultPrevented && panicButtonList.includes(ev.code);
+ const GUIStore = useGenSyncRef((state: RootState) => state.GUI);
+ const isTitleShown = useCallback(() => GUIStore.current.showTitle, [GUIStore]);
+ const isPanicOverlayOpen = useIsPanicOverlayOpen(GUIStore);
+ const setComponentVisibility = useSetComponentVisibility();
+ const handlePressPanicButton = useCallback((ev: KeyboardEvent) => {
+ if (!isPanicButton(ev) || isTitleShown()) return;
+ if (isPanicOverlayOpen()) {
+ setComponentVisibility('showPanicOverlay', false);
+ // todo: resume
+ } else {
+ setComponentVisibility('showPanicOverlay', true);
+ stopAll(); // despite the name, it only disables fast mode and auto mode
+ // todo: pause music & animation for better performance
+ }
+ }, []);
+ useMounted(() => {
+ document.addEventListener('keyup', handlePressPanicButton);
+ });
+ useUnMounted(() => {
+ document.removeEventListener('keyup', handlePressPanicButton);
+ });
+}
+
+/**
+ * ctrl控制快进
+ */
+export function useSkip() {
+ // 因为document事件只绑定一次 为了防止之后更新GUIStore时取不到最新值
+ // 使用Ref共享GUIStore
+ const GUIStore = useGenSyncRef((state: RootState) => state.GUI);
+ // 判断是否位于标题 & 存读档,选项 & 回想等页面
+ const isGameActive = useGameActive(GUIStore);
+ // 判断按键是否为ctrl
+ const isCtrlKey = useCallback((e) => e.keyCode === 17, []);
+ const handleCtrlKeydown = useCallback((e) => {
+ if (isCtrlKey(e) && isGameActive()) {
+ startFast();
+ }
+ }, []);
+ const handleCtrlKeyup = useCallback((e) => {
+ if (isCtrlKey(e) && isGameActive()) {
+ stopFast();
+ }
+ }, []);
+ const handleWindowBlur = useCallback((e) => {
+ // 停止快进
+ stopFast();
+ }, []);
+ // mounted时绑定事件
+ useMounted(() => {
+ document.addEventListener('keydown', handleCtrlKeydown);
+ document.addEventListener('keyup', handleCtrlKeyup);
+ window.addEventListener('blur', handleWindowBlur);
+ });
+ // unmounted解绑
+ useUnMounted(() => {
+ document.removeEventListener('keydown', handleCtrlKeydown);
+ document.removeEventListener('keyup', handleCtrlKeyup);
+ window.removeEventListener('blur', handleWindowBlur);
+ });
+ // updated时验证状态
+ useUpdated(() => {
+ if (!isGameActive()) {
+ stopFast();
+ }
+ });
+}
+
+/**
+ * F5刷新 & 其他情况下导致页面卸载时快速保存
+ */
+export function useFastSaveBeforeUnloadPage() {
+ const validMenuGameStart = useValidMenuGameStart();
+ const handleWindowUnload = useCallback(async (e: BeforeUnloadEvent) => {
+ if (validMenuGameStart()) {
+ // 游戏启动了才保存数据 防止无效数据覆盖现在的数据
+ await fastSaveGame();
+ }
+ }, []);
+ useMounted(() => {
+ window.addEventListener('beforeunload', handleWindowUnload);
+ });
+ useUnMounted(() => {
+ window.removeEventListener('beforeunload', handleWindowUnload);
+ });
+}
+
+// 判断游戏是否激活
+function useGameActive(GUIStore: T & any): () => boolean {
+ return useCallback(() => {
+ return (
+ !GUIStore.current.showTitle &&
+ !GUIStore.current.showMenuPanel &&
+ !GUIStore.current.showBacklog &&
+ !GUIStore.current.showPanicOverlay
+ );
+ }, [GUIStore]);
+}
+
+// 判断是否打开backlog
+function useIsInBackLog(GUIStore: T & any): () => boolean {
+ return useCallback(() => {
+ return GUIStore.current.showBacklog;
+ }, [GUIStore]);
+}
+
+// 判断是否打开了全局对话框
+function useIsOpenedDialog(GUIStore: T & any): () => boolean {
+ return useCallback(() => {
+ return GUIStore.current.showGlobalDialog;
+ }, [GUIStore]);
+}
+
+// 判断是否打开了鉴赏模式
+function useIsOpenedExtra(GUIStore: T & any): () => boolean {
+ return useCallback(() => {
+ return GUIStore.current.showExtra;
+ }, [GUIStore]);
+}
+
+function useIsPanicOverlayOpen(GUIStore: T & any): () => boolean {
+ return useCallback(() => {
+ return GUIStore.current.showPanicOverlay;
+ }, [GUIStore]);
+}
+
+// 验证是否在存档 / 读档 / 选项页面
+function useValidMenuPanelTag(GUIStore: T & any): () => boolean {
+ return useCallback(() => {
+ return [MenuPanelTag.Save, MenuPanelTag.Load, MenuPanelTag.Option].includes(GUIStore.current.currentMenuTag);
+ }, [GUIStore]);
+}
+
+function useValidMenuGameStart() {
+ return useCallback(() => {
+ // return !(runtime_currentSceneData.currentSentenceId === 0 &&
+ // runtime_currentSceneData.currentScene.sceneName === 'start.txt');
+ return !(WebGAL.sceneManager.sceneData.currentSentenceId === 0);
+ }, [WebGAL.sceneManager.sceneData]);
+}
+
+function useSetComponentVisibility(): (component: keyof componentsVisibility, visibility: boolean) => void {
+ const dispatch = useDispatch();
+ return (component: keyof componentsVisibility, visibility: boolean) => {
+ dispatch(setVisibility({ component, visibility }));
+ };
+}
+
+function nextTick(callback: () => void) {
+ // 具体实现根据浏览器的兼容实现微任务
+ if (typeof Promise !== 'undefined') {
+ const p = Promise.resolve();
+ p.then(callback);
+ } else {
+ // 兼容IE
+ setTimeout(callback, 0);
+ }
+}
+
+/**
+ * 空格 & 回车 跳转到下一条
+ */
+export function useSpaceAndEnter() {
+ const GUIStore = useGenSyncRef((state: RootState) => state.GUI);
+ const isGameActive = useGameActive(GUIStore);
+ const setComponentVisibility = useSetComponentVisibility();
+ // 防止一直触发keydown导致快进
+ const lockRef = useRef(false);
+ // 判断按键是否为空格 & 回车
+ const isSpaceOrEnter = useCallback((e) => {
+ return e.keyCode === 32 || e.keyCode === 13;
+ }, []);
+ const handleKeydown = useCallback((e) => {
+ if (isSpaceOrEnter(e) && isGameActive() && !lockRef.current) {
+ if (!GUIStore.current.showTextBox) {
+ setComponentVisibility('showTextBox', true);
+ return;
+ }
+ stopAll();
+ nextSentence();
+ lockRef.current = true;
+ }
+ }, []);
+ const handleKeyup = useCallback((e) => {
+ if (isSpaceOrEnter(e) && isGameActive()) {
+ lockRef.current = false;
+ }
+ }, []);
+ const handleWindowBlur = useCallback((e) => {
+ lockRef.current = false;
+ }, []);
+ // mounted时绑定事件
+ useMounted(() => {
+ document.addEventListener('keydown', handleKeydown);
+ document.addEventListener('keyup', handleKeyup);
+ document.addEventListener('blur', handleWindowBlur);
+ });
+ // unmounted解绑
+ useUnMounted(() => {
+ document.removeEventListener('keydown', handleKeydown);
+ document.removeEventListener('keyup', handleKeyup);
+ document.removeEventListener('blur', handleWindowBlur);
+ });
+}
+
+/**
+ * 是否滚动到底部
+ * @param dom
+ */
+function hasScrollToBottom(dom: Element) {
+ const { scrollTop, clientHeight, scrollHeight } = dom;
+ return scrollTop === 0;
+}
+
+/**
+ * F11 进入全屏
+ */
+function useToggleFullScreen() {
+ const userDataState = useSelector((state: RootState) => state.userData);
+ const dispatch = useDispatch();
+ const fullScreen = userDataState.optionData.fullScreen;
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.code === 'F11') {
+ e.preventDefault();
+ if (fullScreen !== fullScreenOption.on) {
+ dispatch(setOptionData({ key: 'fullScreen', value: fullScreenOption.on }));
+ setStorage();
+ } else {
+ dispatch(setOptionData({ key: 'fullScreen', value: fullScreenOption.off }));
+ setStorage();
+ }
+ }
+ };
+ document.addEventListener('keydown', handleKeyDown);
+ return () => {
+ document.removeEventListener('keydown', handleKeyDown);
+ };
+ }, [fullScreen]);
+}
diff --git a/packages/webgal/src/hooks/useLanguage.ts b/packages/webgal/src/hooks/useLanguage.ts
new file mode 100644
index 000000000..7abf88974
--- /dev/null
+++ b/packages/webgal/src/hooks/useLanguage.ts
@@ -0,0 +1,32 @@
+import { RootState } from '@/store/store';
+import { setOptionData } from '@/store/userDataReducer';
+import { useTranslation } from 'react-i18next';
+import { useDispatch } from 'react-redux';
+import { useGenSyncRef } from './useGenSyncRef';
+import { logger } from '@/Core/util/logger';
+import { setStorage } from '@/Core/controller/storage/storageController';
+import { language } from '@/config/language';
+
+export function getLanguageName(lang: language): string {
+ return language[lang];
+}
+
+export default function useLanguage() {
+ const { i18n } = useTranslation();
+ const userDataRef = useGenSyncRef((state: RootState) => state.userData);
+ const dispatch = useDispatch();
+
+ return (_lang?: language, isSyncStorage = true) => {
+ const lang = _lang ?? userDataRef.current?.optionData.language ?? language.zhCn;
+
+ const languageName = getLanguageName(lang);
+ i18n.changeLanguage(languageName);
+
+ dispatch(setOptionData({ key: 'language', value: lang }));
+ logger.info('设置语言: ' + languageName);
+ window?.localStorage.setItem('lang', lang.toString());
+ if (isSyncStorage) {
+ setStorage();
+ }
+ };
+}
diff --git a/packages/webgal/src/hooks/useLifeCycle.ts b/packages/webgal/src/hooks/useLifeCycle.ts
new file mode 100644
index 000000000..e902981b3
--- /dev/null
+++ b/packages/webgal/src/hooks/useLifeCycle.ts
@@ -0,0 +1,24 @@
+import { useEffect } from 'react';
+
+// 挂载
+export function useMounted(callback: Function) {
+ useEffect(() => {
+ callback();
+ }, []);
+}
+
+// 卸载
+export function useUnMounted(callback: Function) {
+ useEffect(() => {
+ return function () {
+ callback();
+ };
+ }, []);
+}
+
+// 更新
+export function useUpdated(callback: Function) {
+ useEffect(() => {
+ callback();
+ });
+}
diff --git a/packages/webgal/src/hooks/useNotFirstEffect.ts b/packages/webgal/src/hooks/useNotFirstEffect.ts
new file mode 100644
index 000000000..a8baa9bda
--- /dev/null
+++ b/packages/webgal/src/hooks/useNotFirstEffect.ts
@@ -0,0 +1,10 @@
+import { DependencyList, useEffect, useState } from 'react';
+
+export default function useNotFirstEffect(callback: () => void, deps: DependencyList) {
+ const [firstly, setFirstly] = useState(false);
+
+ useEffect(() => {
+ setFirstly(true);
+ if (firstly) callback();
+ }, deps);
+}
diff --git a/packages/webgal/src/hooks/useSoundEffect.ts b/packages/webgal/src/hooks/useSoundEffect.ts
new file mode 100644
index 000000000..f17cab4b8
--- /dev/null
+++ b/packages/webgal/src/hooks/useSoundEffect.ts
@@ -0,0 +1,59 @@
+import { setStage } from '@/store/stageReducer';
+
+import page_flip_1 from '@/assets/se/page-flip-1.mp3';
+import switch_1 from '@/assets/se/switch-1.mp3';
+import mouse_enter from '@/assets/se/mouse-enter.mp3';
+import dialog_se from '@/assets/se/dialog.mp3';
+import click_se from '@/assets/se/click.mp3';
+import { useDispatch } from 'react-redux';
+import { webgalStore } from '@/store/store';
+
+/**
+ * 调用音效
+ */
+const useSoundEffect = () => {
+ const dispatch = useDispatch();
+
+ const playSeEnter = () => {
+ dispatch(setStage({ key: 'uiSe', value: mouse_enter }));
+ };
+ const playSeClick = () => {
+ dispatch(setStage({ key: 'uiSe', value: click_se }));
+ };
+ const playSeSwitch = () => {
+ dispatch(setStage({ key: 'uiSe', value: switch_1 }));
+ };
+ const playSePageChange = () => {
+ dispatch(setStage({ key: 'uiSe', value: page_flip_1 }));
+ };
+
+ const playSeDialogOpen = () => {
+ dispatch(setStage({ key: 'uiSe', value: dialog_se }));
+ };
+
+ return {
+ playSeEnter,
+ playSeClick,
+ playSePageChange,
+ playSeDialogOpen,
+ playSeSwitch,
+ };
+};
+
+/**
+ * 调用音效(只供 choose.tsx 使用)
+ */
+export const useSEByWebgalStore = () => {
+ const playSeEnter = () => {
+ webgalStore.dispatch(setStage({ key: 'uiSe', value: mouse_enter }));
+ };
+ const playSeClick = () => {
+ webgalStore.dispatch(setStage({ key: 'uiSe', value: click_se }));
+ };
+ return {
+ playSeEnter, // 鼠标进入
+ playSeClick, // 鼠标点击
+ };
+};
+
+export default useSoundEffect;
diff --git a/packages/webgal/src/hooks/useTextOptions.ts b/packages/webgal/src/hooks/useTextOptions.ts
new file mode 100644
index 000000000..b388d0564
--- /dev/null
+++ b/packages/webgal/src/hooks/useTextOptions.ts
@@ -0,0 +1,23 @@
+import { playSpeed } from '@/store/userDataInterface';
+
+export function useTextDelay(type: playSpeed) {
+ switch (type) {
+ case playSpeed.slow:
+ return 80;
+ case playSpeed.normal:
+ return 35;
+ case playSpeed.fast:
+ return 3;
+ }
+}
+
+export function useTextAnimationDuration(type: playSpeed) {
+ switch (type) {
+ case playSpeed.slow:
+ return 800;
+ case playSpeed.normal:
+ return 350;
+ case playSpeed.fast:
+ return 200;
+ }
+}
diff --git a/packages/webgal/src/hooks/useTrans.ts b/packages/webgal/src/hooks/useTrans.ts
new file mode 100644
index 000000000..85ad09112
--- /dev/null
+++ b/packages/webgal/src/hooks/useTrans.ts
@@ -0,0 +1,19 @@
+import { useTranslation } from 'react-i18next';
+
+/**
+ * @param prefix 翻译时自动添加的前缀
+ * @returns 翻译函数, 输入key时会自动添加前缀, "$" 开头则不填加. 输入多个 key 则会返回翻译数组.
+ */
+export default function useTrans(prefix?: string) {
+ const { t } = useTranslation();
+ const trans = (key: string) => t(key[0] === '$' ? key.slice(1) : prefix + key);
+
+ function translation(key: string): string;
+ function translation(key: string, ...keys: string[]): string[];
+ function translation(key: string, ...keys: string[]) {
+ if (keys.length) return [trans(key), ...keys.map((v) => trans(v))];
+ return trans(key);
+ }
+
+ return translation;
+}
diff --git a/packages/webgal/src/hooks/useValue.ts b/packages/webgal/src/hooks/useValue.ts
new file mode 100644
index 000000000..89002fb7b
--- /dev/null
+++ b/packages/webgal/src/hooks/useValue.ts
@@ -0,0 +1,18 @@
+import { useState } from 'react';
+
+export function useValue(initialState: T) {
+ const [value, setValue] = useState(initialState);
+ return {
+ _value: value,
+ set: function (newValue: T) {
+ this._value = newValue;
+ setValue(newValue);
+ },
+ get value() {
+ return this._value;
+ },
+ set value(newValue) {
+ this.set(newValue);
+ },
+ };
+}
diff --git a/packages/webgal/src/index.scss b/packages/webgal/src/index.scss
new file mode 100644
index 000000000..475ab2533
--- /dev/null
+++ b/packages/webgal/src/index.scss
@@ -0,0 +1,177 @@
+@charset "UTF-8";
+
+@import "Core/util/constants";
+
+@font-face {
+ font-family: "思源宋体";
+ /*noinspection CssUnknownTarget*/
+ src: url("assets/fonts/SourceHanSerifCN-Regular.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "LXGW";
+ /*noinspection CssUnknownTarget*/
+ src: url("assets/fonts/LXGWWenKai-Regular.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "WebgalUI";
+ /*noinspection CssUnknownTarget*/
+ src: url("assets/fonts/OPPOSans-R.ttf") format("truetype");
+}
+
+.StartButton {
+ animation: StartButton_blink 4s infinite;
+}
+
+@keyframes StartButton_blink {
+ 0% {
+ text-shadow: 0 0 15px rgba(0, 0, 0, 0.65);
+ }
+ 50% {
+ text-shadow: 0 0 15px rgba(255, 255, 255, 0.5);
+ }
+ 100% {
+ text-shadow: 0 0 15px rgba(0, 0, 0, 0.65);
+ }
+}
+
+a {
+ transition: color 1s;
+}
+
+a:link {
+ color: #434343;
+}
+
+a:visited {
+ color: #434343;
+}
+
+a:hover {
+ color: #434343;
+}
+
+a:active {
+ color: #434343;
+}
+
+body {
+ background-color: #000;
+ font-size: 16px;
+ margin: 0;
+ font-family: "WebgalUI", -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ overflow: hidden;
+ user-select: none;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
+
+#root {
+ width: $screenWidth;
+ height: $screenHeight;
+ overflow: hidden;
+ /*position: absolute;*/
+ /*top: 0;*/
+ font-size: 160%;
+}
+
+/* 设置滚动条的样式 */
+::-webkit-scrollbar {
+ width: 12px;
+}
+
+/* 滚动槽 */
+::-webkit-scrollbar-track {
+ box-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
+ -webkit-box-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 10px;
+}
+
+/* 滚动条滑块 */
+::-webkit-scrollbar-thumb {
+ border-radius: 10px;
+ background: rgba(255, 255, 255, 0.5);
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
+ -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
+}
+
+#ebg {
+ height: 100vh;
+ width: 100vw;
+ filter: blur(50px);
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: cover;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+/* ::-webkit-scrollbar-thumb:window-inactive {
+ background:rgba(255,0,0,0.4);
+} */
+
+/*@media screen and (orientation: portrait) {*/
+/* !*竖屏 css*!*/
+/* #root {*/
+/* !*transform-origin: calc(100vw / 2) calc(100vw / 2);*!*/
+/* transform: rotate(90deg);*/
+/* width: 1600px;*/
+/* height: 900px;*/
+/* overflow: hidden;*/
+/* }*/
+/*}*/
+
+/*@media screen and (orientation: landscape) {*/
+/* !*横屏 css*!*/
+/* #root {*/
+/* width: 1600px;*/
+/* height: 900px;*/
+/* overflow: hidden;*/
+/* }*/
+/*}*/
+
+
+/*@media screen and (max-width: 1100px) {*/
+/* !*手机端视图*!*/
+/* #root {*/
+/* font-size: 50%;*/
+/* }*/
+/*}*/
+
+/*@media screen and (max-height: 650px) {*/
+/* !*手机端视图*!*/
+/* #root {*/
+/* font-size: 50%;*/
+/* }*/
+/*}*/
+
+.App {
+ height: 100%;
+ width: 100%;
+ background: rgba(0, 0, 0, 1);
+ overflow: hidden;
+ perspective: 0;
+ -webkit-overflow-scrolling: auto;
+}
+
+*{
+ /*-webkit-overflow-scrolling: auto;*/
+}
+
+#pixiCanvas{
+ z-index: 5;
+}
+
+#root{
+ /*will-change: transform;*/
+}
diff --git a/packages/webgal/src/main.tsx b/packages/webgal/src/main.tsx
new file mode 100644
index 000000000..78d0b4e25
--- /dev/null
+++ b/packages/webgal/src/main.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import './index.scss';
+import App from './App';
+import './assets/style/animation.scss';
+import 'modern-css-reset/dist/reset.min.css';
+
+/**
+ * i18n
+ */
+import i18n from 'i18next';
+import { initReactI18next, Trans } from 'react-i18next';
+import { defaultLanguage, i18nTranslationResources, language } from './config/language';
+import { webgalStore } from './store/store';
+import { Provider } from 'react-redux';
+
+i18n
+ .use(initReactI18next) // passes i18n down to react-i18next
+ .init({
+ // the translations
+ // (tip move them in a JSON file and import them,
+ // or even better, manage them via a UI: https://react.i18next.com/guides/multiple-translation-files#manage-your-translations-with-a-management-gui)
+ resources: i18nTranslationResources || {},
+ lng: language[defaultLanguage] || 'zhCn', // if you're using a language detector, do not define the lng option
+ fallbackLng: 'zhCn',
+
+ interpolation: {
+ escapeValue: false, // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
+ },
+ })
+ .then(() => console.log('WebGAL i18n Ready!'));
+
+// eslint-disable-next-line react/no-deprecated
+ReactDOM.render(
+
+
+
+
+
+
+ ,
+ document.getElementById('root'),
+);
diff --git a/packages/webgal/src/store/GUIReducer.ts b/packages/webgal/src/store/GUIReducer.ts
new file mode 100644
index 000000000..8eb91875c
--- /dev/null
+++ b/packages/webgal/src/store/GUIReducer.ts
@@ -0,0 +1,130 @@
+/**
+ * @file 记录当前GUI的状态信息,引擎初始化时会重置。
+ * @author Mahiru
+ */
+import { getStorage } from '@/Core/controller/storage/storageController';
+import { GuiAsset, IGuiState, MenuPanelTag, setAssetPayload, setVisibilityPayload } from '@/store/guiInterface';
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import { key } from 'localforage';
+
+/**
+ * 初始GUI状态表
+ */
+const initState: IGuiState = {
+ showBacklog: false,
+ showStarter: true,
+ showTitle: true,
+ showMenuPanel: false,
+ showTextBox: true,
+ showControls: true,
+ controlsVisibility: true,
+ currentMenuTag: MenuPanelTag.Option,
+ titleBg: '',
+ titleBgm: '',
+ logoImage: [],
+ showExtra: false,
+ showGlobalDialog: false,
+ showPanicOverlay: false,
+ isEnterGame: false,
+ isShowLogo: true,
+};
+
+/**
+ * GUI状态的Reducer
+ */
+const GUISlice = createSlice({
+ name: 'gui',
+ initialState: initState,
+ reducers: {
+ /**
+ * 设置GUI的各组件的显示状态
+ * @param state 当前GUI状态
+ * @param action 改变显示状态的Action
+ */
+ setVisibility: (state, action: PayloadAction) => {
+ getStorage();
+ const { component, visibility } = action.payload;
+ state[component] = visibility;
+ },
+ /**
+ * 设置MenuPanel的当前选中项
+ * @param state 当前GUI状态
+ * @param action 改变当前选中项的Action
+ */
+ setMenuPanelTag: (state, action: PayloadAction) => {
+ getStorage();
+ state.currentMenuTag = action.payload;
+ },
+ /**
+ * 设置GUI资源的值
+ * @param state 当前GUI状态
+ * @param action 改变资源的Action
+ */
+ setGuiAsset: (state, action: PayloadAction) => {
+ const { asset, value } = action.payload;
+ state[asset] = value;
+ },
+ setLogoImage: (state, action: PayloadAction) => {
+ state.logoImage = [...action.payload];
+ },
+ },
+});
+
+export const { setVisibility, setMenuPanelTag, setGuiAsset, setLogoImage } = GUISlice.actions;
+export default GUISlice.reducer;
+
+// export function GuiStateStore(): GuiStore {
+// const [GuiState, setGuiState] = useState(initState);
+// /**
+// * 设置各组件的可见性
+// * @param key 设置的组件
+// * @param value 可见性,true or false
+// */
+// const setVisibility = (key: K, value: boolean) => {
+//
+// setGuiState(state => {
+// getStorage();
+// state[key] = value;
+// if (key === 'showMenuPanel' || key === 'showBacklog') {
+// state['showTextBox'] = !value;
+// }
+// return {...state};
+// });
+//
+// };
+//
+// /**
+// * 设置Menu组件显示的标签页
+// * @param value 标签页
+// */
+// const setMenuPanelTag = (value: MenuPanelTag) => {
+//
+// setGuiState(state => {
+// getStorage();
+// state.currentMenuTag = value;
+// return {...state};
+// });
+//
+// };
+//
+// /**
+// * 设置标题页的资源路径
+// * @param key 资源名
+// * @param value 资源路径
+// */
+// const setGuiAsset = (key: K, value: string) => {
+//
+// setGuiState(state => {
+// state[key] = value;
+// return {...state};
+// });
+//
+// };
+//
+// return {
+// GuiState,
+// setGuiAsset,
+// setVisibility,
+// setMenuPanelTag,
+// };
+// }
diff --git a/packages/webgal/src/store/guiInterface.ts b/packages/webgal/src/store/guiInterface.ts
new file mode 100644
index 000000000..845c7e652
--- /dev/null
+++ b/packages/webgal/src/store/guiInterface.ts
@@ -0,0 +1,58 @@
+import { IWebGalTextBoxTheme } from '@/Stage/themeInterface';
+
+/**
+ * 当前Menu页面显示的Tag
+ */
+export enum MenuPanelTag {
+ Save, // “保存”选项卡
+ Load, // “读取”选项卡
+ Option, // “设置”选项卡
+}
+
+/**
+ * @interface IGuiState GUI状态接口
+ */
+export interface IGuiState {
+ showStarter: boolean; // 是否显示初始界面(用于使得bgm可以播放)
+ showTitle: boolean; // 是否显示标题界面
+ showMenuPanel: boolean; // 是否显示Menu界面
+ showTextBox: boolean;
+ showControls: boolean;
+ controlsVisibility: boolean;
+ currentMenuTag: MenuPanelTag; // 当前Menu界面的选项卡
+ showBacklog: boolean;
+ titleBgm: string; // 标题背景音乐
+ titleBg: string; // 标题背景图片
+ logoImage: string[];
+ showExtra: boolean;
+ showGlobalDialog: boolean;
+ showPanicOverlay: boolean;
+ isEnterGame: boolean;
+ isShowLogo: boolean;
+}
+
+export type componentsVisibility = Pick<
+ IGuiState,
+ Exclude
+>;
+// 标题资源
+export type GuiAsset = Pick;
+
+export interface IGuiStore {
+ GuiState: IGuiState;
+ setGuiAsset: (key: K, value: string) => void;
+ setVisibility: (key: K, value: boolean) => void;
+ setMenuPanelTag: (value: MenuPanelTag) => void;
+}
+
+export interface setVisibilityPayload {
+ component: keyof componentsVisibility;
+ visibility: boolean;
+}
+
+export interface setAssetPayload {
+ asset: keyof GuiAsset;
+ value: string;
+}
+
+export type GuiStore = IGuiStore;
diff --git a/packages/webgal/src/store/savesReducer.ts b/packages/webgal/src/store/savesReducer.ts
new file mode 100644
index 000000000..e4e4f4b0e
--- /dev/null
+++ b/packages/webgal/src/store/savesReducer.ts
@@ -0,0 +1,45 @@
+import { ISaveData } from './userDataInterface';
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import cloneDeep from 'lodash/cloneDeep';
+
+export interface ISavesData {
+ saveData: Array; // 用户存档数据
+ quickSaveData: ISaveData | null;
+}
+
+const initState: ISavesData = {
+ saveData: [],
+ quickSaveData: null,
+};
+
+interface SaveAction {
+ saveData: ISaveData;
+ index: number;
+}
+
+const saveDataSlice = createSlice({
+ name: 'saveData',
+ initialState: cloneDeep(initState),
+ reducers: {
+ setFastSave: (state, action: PayloadAction) => {
+ state.quickSaveData = action.payload;
+ },
+ resetFastSave: (state) => {
+ state.quickSaveData = null;
+ },
+ resetSaves: (state) => {
+ state.quickSaveData = null;
+ state.saveData = [];
+ },
+ saveGame: (state, action: PayloadAction) => {
+ state.saveData[action.payload.index] = action.payload.saveData;
+ },
+ replaceSaveGame: (state, action: PayloadAction) => {
+ state.saveData = action.payload;
+ },
+ },
+});
+
+export const saveActions = saveDataSlice.actions;
+
+export default saveDataSlice.reducer;
diff --git a/packages/webgal/src/store/stageInterface.ts b/packages/webgal/src/store/stageInterface.ts
new file mode 100644
index 000000000..e1f7633b7
--- /dev/null
+++ b/packages/webgal/src/store/stageInterface.ts
@@ -0,0 +1,186 @@
+import { ISentence } from '@/Core/controller/scene/sceneInterface';
+
+/**
+ * 游戏内变量
+ * @interface IGameVar
+ */
+export interface IGameVar {
+ [propName: string]: string | boolean | number | Array;
+}
+
+export interface ISetGameVar {
+ key: string;
+ value: string | boolean | number;
+}
+
+/**
+ * 单个选项
+ * @interface IChooseItem
+ */
+export interface IChooseItem {
+ key: string; // 选项名称
+ targetScene: string; // 选项target
+ isSubScene: boolean; // 是否是子场景调用
+}
+
+export interface ITransform {
+ alpha: number;
+ scale: {
+ x: number;
+ y: number;
+ };
+ // pivot: {
+ // x: number;
+ // y: number;
+ // };
+ position: {
+ x: number;
+ y: number;
+ };
+ rotation: number;
+ blur: number;
+}
+
+/**
+ * 基本效果接口
+ * @interface IEffect
+ */
+export interface IEffect {
+ target: string; // 作用目标
+ transform?: ITransform; // 变换
+}
+
+/**
+ * 基本变换预设
+ */
+export const baseTransform: ITransform = {
+ alpha: 1,
+ scale: {
+ x: 1,
+ y: 1,
+ },
+ // pivot: {
+ // x: 0.5,
+ // y: 0.5,
+ // },
+ position: {
+ x: 0,
+ y: 0,
+ },
+ rotation: 0,
+ blur: 0,
+};
+
+export interface IFreeFigure {
+ basePosition: 'left' | 'center' | 'right';
+ name: string;
+ key: string;
+}
+
+export interface IFigureAssociatedAnimation {
+ mouthAnimation: IMouthAnimationFile;
+ blinkAnimation: IEyesAnimationFile;
+ targetId: string;
+ animationFlag: string;
+}
+
+export interface IMouthAnimationFile {
+ open: string;
+ close: string;
+ halfOpen: string;
+}
+
+export interface IEyesAnimationFile {
+ open: string;
+ close: string;
+}
+
+/**
+ * 启动演出接口
+ * @interface IRunPerform
+ */
+export interface IRunPerform {
+ id: string;
+ isHoldOn: boolean; // 演出类型
+ script: ISentence; // 演出脚本
+}
+
+export interface ILive2DMotion {
+ target: string;
+ motion: string;
+ overrideBounds?: [number, number, number, number];
+}
+
+export interface ILive2DExpression {
+ target: string;
+ expression: string;
+}
+
+export interface IFigureMetadata {
+ zIndex?: number;
+}
+
+type figureMetaData = Record;
+
+/**
+ * @interface IStageState 游戏舞台数据接口
+ */
+export interface IStageState {
+ oldBgName: string; // 旧背景的文件路径
+ bgName: string; // 背景文件地址(相对或绝对)
+ figName: string; // 立绘_中 文件地址(相对或绝对)
+ figNameLeft: string; // 立绘_左 文件地址(相对或绝对)
+ figNameRight: string; // 立绘_右 文件地址(相对或绝对)
+ // 自由立绘
+ freeFigure: Array;
+ figureAssociatedAnimation: Array;
+ showText: string; // 文字
+ showTextSize: number; // 文字
+ showName: string; // 人物名
+ command: string; // 语句指令
+ choose: Array; // 选项列表
+ vocal: string; // 语音 文件地址(相对或绝对)
+ playVocal: string; // 真实播放语音
+ vocalVolume: number; // 语音 音量调整(0 - 100)
+ bgm: {
+ // 背景音乐
+ src: string; // 背景音乐 文件地址(相对或绝对)
+ enter: number; // 背景音乐 淡入或淡出的毫秒数
+ volume: number; // 背景音乐 音量调整(0 - 100)
+ };
+ uiSe: string; // 用户界面音效 文件地址(相对或绝对)
+ miniAvatar: string; // 小头像 文件地址(相对或绝对)
+ GameVar: IGameVar; // 游戏内变量
+ effects: Array; // 应用的变换
+ bgTransform: string;
+ bgFilter: string;
+ PerformList: Array; // 要启动的演出列表
+ currentDialogKey: string; // 当前对话的key
+ live2dMotion: ILive2DMotion[];
+ live2dExpression: ILive2DExpression[];
+ // 当前演出的延迟,用于做对话插演出!
+ // currentPerformDelay:number
+ currentConcatDialogPrev: string;
+ // 测试:电影叙事
+ enableFilm: string;
+ isDisableTextbox: boolean;
+ replacedUIlable: Record;
+ figureMetaData: figureMetaData;
+}
+
+/**
+ * @interface ISetStagePayload 设置舞台状态的Action的Payload的数据接口
+ */
+export interface ISetStagePayload {
+ key: keyof IStageState;
+ value: any;
+}
+
+export interface IStageStore {
+ stageState: IStageState;
+ setStage: (key: K, value: any) => void;
+ getStageState: () => IStageState;
+ restoreStage: (newState: IStageState) => void;
+}
+
+export type StageStore = IStageStore;
diff --git a/packages/webgal/src/store/stageReducer.ts b/packages/webgal/src/store/stageReducer.ts
new file mode 100644
index 000000000..beb7f6cca
--- /dev/null
+++ b/packages/webgal/src/store/stageReducer.ts
@@ -0,0 +1,274 @@
+/**
+ * 所有会被Save和Backlog记录下的信息,构成当前的舞台状态(也包括游戏运行时变量)
+ * 舞台状态是演出结束后的“终态”,在读档时不发生演出,只是将舞台状态替换为读取的状态。
+ */
+
+import {
+ IEffect,
+ IFigureMetadata,
+ IFreeFigure,
+ ILive2DExpression,
+ ILive2DMotion,
+ IRunPerform,
+ ISetGameVar,
+ ISetStagePayload,
+ IStageState,
+} from '@/store/stageInterface';
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import cloneDeep from 'lodash/cloneDeep';
+import { commandType } from '@/Core/controller/scene/sceneInterface';
+import { STAGE_KEYS } from '@/Core/constants';
+
+// 初始化舞台数据
+
+export const initState: IStageState = {
+ oldBgName: '',
+ bgName: '', // 背景文件地址(相对或绝对)
+ figName: '', // 立绘_中 文件地址(相对或绝对)
+ figNameLeft: '', // 立绘_左 文件地址(相对或绝对)
+ figNameRight: '', // 立绘_右 文件地址(相对或绝对)
+ freeFigure: [],
+ figureAssociatedAnimation: [],
+ showText: '', // 文字
+ showTextSize: -1,
+ showName: '', // 人物名
+ command: '', // 语句指令
+ choose: [], // 选项列表,现在不用,先预留
+ vocal: '', // 语音 文件地址(相对或绝对)
+ playVocal: '', // 语音,真实的播放音频
+ vocalVolume: 100, // 语音 音量调整(0 - 100)
+ bgm: {
+ // 背景音乐
+ src: '', // 背景音乐 文件地址(相对或绝对)
+ enter: 0, // 背景音乐 淡入或淡出的毫秒数
+ volume: 100, // 背景音乐 音量调整(0 - 100)
+ },
+ uiSe: '', // 用户界面音效 文件地址(相对或绝对)
+ miniAvatar: '', // 小头像 文件地址(相对或绝对)
+ GameVar: {}, // 游戏内变量
+ effects: [], // 应用的效果
+ bgFilter: '', // 现在不用,先预留
+ bgTransform: '', // 现在不用,先预留
+ PerformList: [], // 要启动的演出列表
+ currentDialogKey: 'initial',
+ live2dMotion: [],
+ live2dExpression: [],
+ // currentPerformDelay: 0
+ currentConcatDialogPrev: '',
+ enableFilm: '',
+ isDisableTextbox: false,
+ replacedUIlable: {},
+ figureMetaData: {},
+};
+
+/**
+ * 创建舞台的状态管理
+ */
+const stageSlice = createSlice({
+ name: 'stage',
+ initialState: cloneDeep(initState),
+ reducers: {
+ /**
+ * 替换舞台状态
+ * @param state 当前状态
+ * @param action 替换的状态
+ */
+ resetStageState: (state, action: PayloadAction) => {
+ Object.assign(state, action.payload);
+ },
+ /**
+ * 设置舞台状态
+ * @param state 当前状态
+ * @param action 要替换的键值对
+ */
+ setStage: (state, action: PayloadAction) => {
+ // @ts-ignore
+ state[action.payload.key] = action.payload.value;
+ },
+ /**
+ * 修改舞台状态变量
+ * @param state 当前状态
+ * @param action 要改变或添加的变量
+ */
+ setStageVar: (state, action: PayloadAction) => {
+ state.GameVar[action.payload.key] = action.payload.value;
+ },
+ updateEffect: (state, action: PayloadAction) => {
+ const { target, transform } = action.payload;
+ // 如果找不到目标,不能设置 transform
+ const activeTargets = [
+ STAGE_KEYS.BGMAIN,
+ STAGE_KEYS.FIG_C,
+ STAGE_KEYS.FIG_L,
+ STAGE_KEYS.FIG_R,
+ ...state.freeFigure.map((figure) => figure.key),
+ ];
+ if (!activeTargets.includes(target)) return;
+ // 尝试找到待修改的 Effect
+ const effectIndex = state.effects.findIndex((e) => e.target === target);
+ if (effectIndex >= 0) {
+ // Update the existing effect
+ state.effects[effectIndex].transform = transform;
+ } else {
+ // Add a new effect
+ state.effects.push({
+ target,
+ transform,
+ });
+ }
+ },
+ removeEffectByTargetId: (state, action: PayloadAction) => {
+ const index = state.effects.findIndex((e) => e.target === action.payload);
+ if (index >= 0) {
+ state.effects.splice(index, 1);
+ }
+ },
+ addPerform: (state, action: PayloadAction) => {
+ // 先检查是否有重复的,全部干掉
+ const dupPerformIndex = state.PerformList.findIndex((p) => p.id === action.payload.id);
+ if (dupPerformIndex > -1) {
+ const dupId = action.payload.id;
+ // 删除全部重复演出
+ for (let i = 0; i < state.PerformList.length; i++) {
+ const performItem: IRunPerform = state.PerformList[i];
+ if (performItem.id === dupId) {
+ state.PerformList.splice(i, 1);
+ i--;
+ }
+ }
+ }
+ state.PerformList.push(action.payload);
+ },
+ removePerformByName: (state, action: PayloadAction) => {
+ for (let i = 0; i < state.PerformList.length; i++) {
+ const performItem: IRunPerform = state.PerformList[i];
+ if (performItem.id === action.payload) {
+ state.PerformList.splice(i, 1);
+ i--;
+ }
+ }
+ },
+ removeAllPerform: (state) => {
+ state.PerformList.splice(0, state.PerformList.length);
+ },
+ removeAllPixiPerforms: (state, action: PayloadAction) => {
+ for (let i = 0; i < state.PerformList.length; i++) {
+ const performItem: IRunPerform = state.PerformList[i];
+ if (performItem.script.command === commandType.pixi) {
+ state.PerformList.splice(i, 1);
+ i--;
+ }
+ }
+ },
+ setFreeFigureByKey: (state, action: PayloadAction) => {
+ const currentFreeFigures = state.freeFigure;
+ const newFigure = action.payload;
+ const index = currentFreeFigures.findIndex((figure) => figure.key === newFigure.key);
+ if (index >= 0) {
+ if (newFigure.name === '') {
+ // 删掉立绘和相关的动画
+ currentFreeFigures.splice(index, 1);
+ const figureAssociatedAnimationIndex = state.figureAssociatedAnimation.findIndex(
+ (a) => a.targetId === newFigure.key,
+ );
+ state.figureAssociatedAnimation.splice(figureAssociatedAnimationIndex, 1);
+ } else {
+ currentFreeFigures[index].basePosition = newFigure.basePosition;
+ currentFreeFigures[index].name = newFigure.name;
+ }
+ } else {
+ // 新加
+ if (newFigure.name !== '') currentFreeFigures.push(newFigure);
+ }
+ },
+ setLive2dMotion: (state, action: PayloadAction) => {
+ const { target, motion, overrideBounds } = action.payload;
+
+ const index = state.live2dMotion.findIndex((e) => e.target === target);
+
+ if (index < 0) {
+ // Add a new motion
+ state.live2dMotion.push({ target, motion, overrideBounds });
+ } else {
+ // Update the existing motion
+ state.live2dMotion[index].motion = motion;
+ state.live2dMotion[index].overrideBounds = overrideBounds;
+ }
+ },
+ setLive2dExpression: (state, action: PayloadAction) => {
+ const { target, expression } = action.payload;
+
+ const index = state.live2dExpression.findIndex((e) => e.target === target);
+
+ if (index < 0) {
+ // Add a new expression
+ state.live2dExpression.push({ target, expression });
+ } else {
+ // Update the existing expression
+ state.live2dExpression[index].expression = expression;
+ }
+ },
+ replaceUIlable: (state, action: PayloadAction<[string, string]>) => {
+ state.replacedUIlable[action.payload[0]] = action.payload[1];
+ },
+ /**
+ * 设置 figure 元数据 [立绘 key, metadata key, 值, 是否重设]
+ * @param state
+ * @param action
+ */
+ setFigureMetaData: (state, action: PayloadAction<[string, keyof IFigureMetadata, any, undefined | boolean]>) => {
+ // 立绘退出,重设
+ if (action.payload[3]) {
+ if (state.figureMetaData[action.payload[0]]) delete state.figureMetaData[action.payload[0]];
+ } else {
+ // 初始化对象
+ if (!state.figureMetaData[action.payload[0]]) {
+ state.figureMetaData[action.payload[0]] = {};
+ }
+ state.figureMetaData[action.payload[0]][action.payload[1]] = action.payload[2];
+ }
+ },
+ },
+});
+
+export const { resetStageState, setStage, setStageVar } = stageSlice.actions;
+export const stageActions = stageSlice.actions;
+export default stageSlice.reducer;
+
+// /**
+// * 创建舞台的状态管理
+// * @return {IStageState} 舞台状态
+// * @return {function} 改变舞台状态
+// */
+// export function stageStateStore():StageStore {
+// const [stageState, setStageState] = useState(_.cloneDeep(initState));
+//
+// /**
+// * 设置舞台状态,以后会改
+// * @param key
+// * @param value
+// */
+// const setStage = (key: K, value: any) => {
+//
+// setStageState(state => {
+// state[key] = value;
+// return {...state};
+// });
+//
+// };
+//
+// const getStageState = () => {
+// return stageState;
+// };
+//
+// const restoreStage = (newState: IStageState) => {
+// setStageState((state) => ({ ...state, ...newState }));
+// };
+//
+// return {
+// stageState,
+// setStage,
+// getStageState,
+// restoreStage,
+// };
+// }
diff --git a/packages/webgal/src/store/store.ts b/packages/webgal/src/store/store.ts
new file mode 100644
index 000000000..e31b9d900
--- /dev/null
+++ b/packages/webgal/src/store/store.ts
@@ -0,0 +1,23 @@
+import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
+import stageReducer from '@/store/stageReducer';
+import GUIReducer from '@/store/GUIReducer';
+import userDataReducer from '@/store/userDataReducer';
+import savesReducer from '@/store/savesReducer';
+
+/**
+ * WebGAL 全局状态管理
+ */
+export const webgalStore = configureStore({
+ reducer: {
+ stage: stageReducer,
+ GUI: GUIReducer,
+ userData: userDataReducer,
+ saveData: savesReducer,
+ },
+ middleware: getDefaultMiddleware({
+ serializableCheck: false,
+ }),
+});
+
+// 在 TS 中的类型声明
+export type RootState = ReturnType;
diff --git a/packages/webgal/src/store/userDataInterface.ts b/packages/webgal/src/store/userDataInterface.ts
new file mode 100644
index 000000000..626544ded
--- /dev/null
+++ b/packages/webgal/src/store/userDataInterface.ts
@@ -0,0 +1,119 @@
+import { IGameVar, IStageState } from './stageInterface';
+import { language } from '@/config/language';
+import { IBacklogItem } from '@/Core/Modules/backlog';
+import { ISceneEntry } from '@/Core/Modules/scene';
+
+/**
+ * 播放速度的枚举类型
+ */
+export enum playSpeed {
+ slow, // 慢
+ normal, // 中
+ fast, // 快
+}
+
+export enum textSize {
+ small,
+ medium,
+ large,
+}
+
+export enum textFont {
+ song,
+ hei,
+ lxgw,
+}
+
+export enum voiceOption {
+ yes,
+ no,
+}
+
+export enum fullScreenOption {
+ on,
+ off,
+}
+
+/**
+ * @interface IOptionData 用户设置数据接口
+ */
+export interface IOptionData {
+ volumeMain: number; // 主音量
+ textSpeed: playSpeed; // 文字速度
+ autoSpeed: playSpeed; // 自动播放速度
+ textSize: textSize;
+ vocalVolume: number; // 语音音量
+ bgmVolume: number; // 背景音乐音量
+ seVolume: number; // 音效音量
+ uiSeVolume: number; // 用户界面音效音量
+ slPage: number; // 存读档界面所在页面
+ textboxFont: textFont;
+ textboxOpacity: number;
+ language: language;
+ voiceInterruption: voiceOption; // 是否中断语音
+ fullScreen: fullScreenOption;
+}
+
+/**
+ * 场景存档接口
+ * @interface ISaveScene
+ */
+export interface ISaveScene {
+ currentSentenceId: number; // 当前语句ID
+ sceneStack: Array; // 场景栈
+ sceneName: string; // 场景名称
+ sceneUrl: string; // 场景url
+}
+
+/**
+ * @interface ISaveData 存档文件接口
+ */
+export interface ISaveData {
+ nowStageState: IStageState;
+ backlog: Array; // 舞台数据
+ index: number; // 存档的序号
+ saveTime: string; // 保存时间
+ sceneData: ISaveScene; // 场景数据
+ previewImage: string;
+}
+
+export interface IAppreciationAsset {
+ name: string;
+ url: string;
+ series: string;
+}
+
+export interface IAppreciation {
+ bgm: Array;
+ cg: Array;
+}
+
+/**
+ * @interface IUserData 用户数据接口
+ */
+export interface IUserData {
+ scriptManagedGlobalVar: string[];
+ globalGameVar: IGameVar; // 不跟随存档的全局变量
+ optionData: IOptionData; // 用户设置选项数据
+ appreciationData: IAppreciation;
+}
+
+export interface ISetUserDataPayload {
+ key: keyof IUserData;
+ value: any;
+}
+
+export interface ISetOptionDataPayload {
+ key: keyof IOptionData;
+ value: any;
+}
+
+export interface IUserDataStore {
+ userDataState: IUserData;
+ setUserData: (key: K, value: any) => void;
+ replaceUserData: (newUserData: IUserData) => void;
+ setOptionData: (key: K, value: any) => void;
+ setSlPage: (index: number) => void;
+}
+
+export type UserDataStore = IUserDataStore;
diff --git a/packages/webgal/src/store/userDataReducer.ts b/packages/webgal/src/store/userDataReducer.ts
new file mode 100644
index 000000000..a70827073
--- /dev/null
+++ b/packages/webgal/src/store/userDataReducer.ts
@@ -0,0 +1,207 @@
+/**
+ * 用于存储与本地存储交换的状态信息。
+ * 这些状态会在指定的生命周期与本地存储发生交换,比如打开存档界面、存档、修改设置时。
+ * 在引擎初始化时会将这些状态从本地存储加载到运行时状态。
+ */
+import { language } from '@/config/language';
+import {
+ IAppreciationAsset,
+ IOptionData,
+ ISaveData,
+ ISetOptionDataPayload,
+ ISetUserDataPayload,
+ IUserData,
+ fullScreenOption,
+ playSpeed,
+ textFont,
+ textSize,
+ voiceOption,
+} from '@/store/userDataInterface';
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import cloneDeep from 'lodash/cloneDeep';
+import { ISetGameVar } from './stageInterface';
+
+const initialOptionSet: IOptionData = {
+ slPage: 1,
+ volumeMain: 100, // 主音量
+ textSpeed: playSpeed.normal, // 文字速度
+ autoSpeed: playSpeed.normal, // 自动播放速度
+ textSize: textSize.medium,
+ vocalVolume: 100, // 语音音量
+ bgmVolume: 25, // 背景音乐音量
+ seVolume: 100, // 音效音量
+ uiSeVolume: 50, // UI音效音量
+ textboxFont: textFont.song,
+ textboxOpacity: 75,
+ language: language.zhCn,
+ voiceInterruption: voiceOption.yes,
+ fullScreen: fullScreenOption.off,
+};
+
+// 初始化用户数据
+export const initState: IUserData = {
+ optionData: initialOptionSet,
+ scriptManagedGlobalVar: [],
+ globalGameVar: {},
+ appreciationData: {
+ bgm: [],
+ cg: [],
+ },
+};
+
+const userDataSlice = createSlice({
+ name: 'userData',
+ initialState: cloneDeep(initState),
+ reducers: {
+ /**
+ * 设置用户数据
+ * @param state
+ * @param action
+ */
+ setUserData: (state, action: PayloadAction) => {
+ const { key, value } = action.payload;
+ state[key] = value;
+ },
+ unlockCgInUserData: (state, action: PayloadAction) => {
+ const { name, url, series } = action.payload;
+ // 检查是否存在
+ let isExist = false;
+ state.appreciationData.cg.forEach((e) => {
+ if (url === e.url) {
+ isExist = true;
+ e.url = url;
+ e.series = series;
+ }
+ });
+ if (!isExist) {
+ state.appreciationData.cg.push(action.payload);
+ }
+ },
+ unlockBgmInUserData: (state, action: PayloadAction) => {
+ const { name, url, series } = action.payload;
+ // 检查是否存在
+ let isExist = false;
+ state.appreciationData.bgm.forEach((e) => {
+ if (url === e.url) {
+ isExist = true;
+ e.url = url;
+ e.series = series;
+ }
+ });
+ if (!isExist) {
+ state.appreciationData.bgm.push(action.payload);
+ }
+ },
+ /**
+ * 替换用户数据
+ * @param state
+ * @param action
+ */
+ resetUserData: (state, action: PayloadAction) => {
+ Object.assign(state, action.payload);
+ },
+ /**
+ * 设置选项数据
+ * @param state
+ * @param action
+ */
+ setOptionData: (state, action: PayloadAction) => {
+ const { key, value } = action.payload;
+ (state.optionData as any)[key] = value;
+ },
+ /**
+ * 修改不跟随存档的全局变量
+ * @param state 当前状态
+ * @param action 要改变或添加的变量
+ */
+ setGlobalVar: (state, action: PayloadAction) => {
+ const isRegistedInUserData = state.scriptManagedGlobalVar.findIndex((key) => key === action.payload.key) >= 0;
+ if (!isRegistedInUserData) {
+ state.globalGameVar[action.payload.key] = action.payload.value;
+ }
+ },
+ setScriptManagedGlobalVar: (state, action: PayloadAction) => {
+ const isRegistedInUserData = state.scriptManagedGlobalVar.findIndex((key) => key === action.payload.key) >= 0;
+ state.globalGameVar[action.payload.key] = action.payload.value;
+ if (!isRegistedInUserData) {
+ state.scriptManagedGlobalVar.push(action.payload.key);
+ }
+ },
+ /**
+ * 设置存档/读档页面
+ * @param state
+ * @param action
+ */
+ setSlPage: (state, action: PayloadAction) => {
+ state.optionData.slPage = action.payload;
+ },
+ resetOptionSet(state) {
+ Object.assign(state.optionData, initialOptionSet);
+ },
+ resetAllData(state) {
+ Object.assign(state, cloneDeep(initState));
+ },
+ },
+});
+
+export const {
+ setUserData,
+ resetUserData,
+ setOptionData,
+ setGlobalVar,
+ setScriptManagedGlobalVar,
+ setSlPage,
+ unlockCgInUserData,
+ unlockBgmInUserData,
+ resetOptionSet,
+ resetAllData,
+} = userDataSlice.actions;
+export default userDataSlice.reducer;
+
+// /**
+// * 创建用户数据的状态管理
+// * @return {IUserData} 用户数据
+// * @return {function} 改变用户数据
+// */
+// export function userDataStateStore():UserDataStore {
+// const [userDataState, setUserDataState] = useState(initState);
+//
+// // 设置用户数据
+// const setUserData = (key: K, value: any) => {
+//
+// setUserDataState(state => {
+// state[key] = value;
+// return {...state};
+// });
+//
+// };
+//
+// // 替换用户数据(多用于与本地存储交互)
+// const replaceUserData = (newUserData: IUserData) => {
+//
+// setUserDataState(state => ({...state, ...newUserData}));
+// };
+//
+// const setOptionData = (key: K, value: any) => {
+// setUserDataState(state => {
+// state.optionData[key] = value;
+// return {...state};
+// });
+// };
+//
+// const setSlPage = (index: number) => {
+// setUserDataState(state => {
+// state.optionData.slPage = index;
+// return {...state};
+// });
+//
+// };
+//
+// return {
+// userDataState,
+// setUserData,
+// replaceUserData,
+// setOptionData,
+// setSlPage,
+// };
+// }
diff --git a/packages/webgal/src/translations/de.ts b/packages/webgal/src/translations/de.ts
new file mode 100644
index 000000000..06295bade
--- /dev/null
+++ b/packages/webgal/src/translations/de.ts
@@ -0,0 +1,177 @@
+const de = {
+ // 通用
+ common: {
+ yes: 'Ja',
+ no: 'Nein',
+ },
+
+ menu: {
+ options: {
+ title: 'OPTIONEN',
+ pages: {
+ system: {
+ title: 'System',
+ options: {
+ autoSpeed: {
+ title: 'Auto-Geschwindigkeit',
+ options: {
+ slow: 'Langsam',
+ medium: 'Normal',
+ fast: 'Schnell',
+ },
+ },
+ language: {
+ title: 'Sprache',
+ },
+ resetData: {
+ title: 'Daten löschen oder zurücksetzen',
+ options: {
+ clearGameSave: 'Alle Spielstände löschen',
+ resetSettings: 'Alle Einstellungen zurücksetzen',
+ clearAll: 'Alle Daten löschen',
+ },
+ dialogs: {
+ clearGameSave: 'Sind Sie sicher, dass Sie den Spielstand löschen möchten?',
+ resetSettings: 'Sind Sie sicher, dass Sie alle Einstellungen zurücksetzen möchten?',
+ clearAll: 'Sind Sie sicher, dass Sie alle Daten löschen möchten?',
+ },
+ },
+ gameSave: {
+ title: 'Spielstand und Optionen importieren oder exportieren',
+ options: {
+ export: 'Spielstand und Optionen exportieren',
+ import: 'Spielstand und Optionen importieren',
+ },
+ dialogs: {
+ import: {
+ title: 'Sind Sie sicher, dass Sie den Spielstand und die Optionen importieren möchten?',
+ tip: 'Spielstand importieren',
+ error: 'Ein Fehler ist beim Analysieren des Spielstands aufgetreten',
+ },
+ },
+ },
+ about: {
+ title: 'Über WebGAL',
+ subTitle: 'WebGAL: Eine Open-Source Web-Based Visual Novel Engine',
+ version: 'Version',
+ source: 'Source Code Repository',
+ contributors: 'Contributors',
+ website: 'Website',
+ },
+ },
+ },
+ display: {
+ title: 'Darstellung',
+ options: {
+ textSpeed: {
+ title: 'Geschwindigkeit der Textanzeige',
+ options: {
+ slow: 'Langsam',
+ medium: 'Normal',
+ fast: 'Schnell',
+ },
+ },
+ textSize: {
+ title: 'Textgröße',
+ options: {
+ small: 'Klein',
+ medium: 'Normal',
+ large: 'Groß',
+ },
+ },
+ textFont: {
+ title: 'Schriftart',
+ options: {
+ siYuanSimSun: 'Source Han Serif',
+ SimHei: 'Sans',
+ lxgw: 'LXGW WenKai',
+ },
+ },
+ textboxOpacity: {
+ title: 'Textbox Opacity',
+ },
+ textPreview: {
+ title: 'Vorschautext wird angezeigt',
+ text: 'Sie können jederzeit die Schriftart, Größe und Wiedergabegeschwindigkeit des Textes nach Ihrer Vorliebe anpassen.',
+ },
+ },
+ },
+ sound: {
+ title: 'Ton',
+ options: {
+ volumeMain: { title: 'Hauptlautstärke' },
+ vocalVolume: { title: 'Stimmlautstärke' },
+ bgmVolume: { title: 'Musiklautstärke' },
+ seVolume: { title: 'Soundeffektlautstärke' },
+ uiSeVolume: { title: 'UI Soundeffektlautstärke' },
+ },
+ },
+ // language: {
+ // title: 'Sprache',
+ // options: {
+ // },
+ // },
+ },
+ },
+ saving: {
+ title: 'SPEICHERN',
+ isOverwrite: 'Sind Sie sicher, dass Sie diesen Spielstand überschreiben möchten?',
+ },
+ loadSaving: {
+ title: 'LADEN',
+ },
+ title: {
+ title: 'TITEL',
+ },
+ exit: {
+ title: 'ZURÜCK',
+ },
+ },
+
+ title: {
+ start: {
+ title: 'STARTEN',
+ subtitle: '',
+ },
+ continue: {
+ title: 'WEITERLESEN',
+ subtitle: '',
+ },
+ options: {
+ title: 'OPTIONEN',
+ subtitle: '',
+ },
+ load: {
+ title: 'LADEN',
+ subtitle: '',
+ },
+ extra: {
+ title: 'EXTRA',
+ subtitle: '',
+ },
+ },
+
+ gaming: {
+ noSaving: 'Keine Speicherung',
+ buttons: {
+ hide: 'Verstecken',
+ show: 'Anzeigen',
+ backlog: 'Verlauf',
+ replay: 'Wiedergabe',
+ auto: 'Auto',
+ forward: 'Überspringen',
+ quicklySave: 'Quickly Save',
+ quicklyLoad: 'Quickly Load',
+ save: 'Speichern',
+ load: 'Laden',
+ options: 'Optionen',
+ title: 'Titel',
+ },
+ },
+
+ extra: {
+ title: 'EXTRA',
+ },
+};
+
+export default de;
diff --git a/packages/webgal/src/translations/en.ts b/packages/webgal/src/translations/en.ts
new file mode 100644
index 000000000..c4b98f739
--- /dev/null
+++ b/packages/webgal/src/translations/en.ts
@@ -0,0 +1,185 @@
+const en = {
+ // 通用
+ common: {
+ yes: 'OK',
+ no: 'Cancel',
+ },
+
+ menu: {
+ options: {
+ title: 'OPTIONS',
+ pages: {
+ system: {
+ title: 'System',
+ options: {
+ autoSpeed: {
+ title: 'Autoplay Speed',
+ options: {
+ slow: 'Slow',
+ medium: 'Medium',
+ fast: 'Fast',
+ },
+ },
+ language: {
+ title: 'Language',
+ },
+ resetData: {
+ title: 'Clear or Reset Data',
+ options: {
+ clearGameSave: 'Clear game saving',
+ resetSettings: 'Reset settings',
+ clearAll: 'Clear all data',
+ },
+ dialogs: {
+ clearGameSave: 'Are you sure you want to clear game saving',
+ resetSettings: 'Are you sure you want to reset all settings',
+ clearAll: 'Are you sure you want to clear all data',
+ },
+ },
+ gameSave: {
+ title: 'Import or Export Game Saving and Options',
+ options: {
+ export: 'Export game saving and options',
+ import: 'Import game saving and options',
+ },
+ dialogs: {
+ import: {
+ title: 'Are you sure you want to import game saving and options',
+ tip: 'Import game saving',
+ error: 'Parse game saving failed',
+ },
+ },
+ },
+ about: {
+ title: 'About WebGAL',
+ subTitle: 'WebGAL: An Open-Source Web-Based Visual Novel Engine',
+ version: 'Version',
+ source: 'Source Code Repository',
+ contributors: 'Contributors',
+ website: 'Website',
+ },
+ },
+ },
+ display: {
+ title: 'Display',
+ options: {
+ fullScreen: {
+ title: 'Full Screen',
+ options: {
+ on: 'ON',
+ off: 'OFF',
+ },
+ },
+ textSpeed: {
+ title: 'Text Speed',
+ options: {
+ slow: 'Slow',
+ medium: 'Medium',
+ fast: 'Fast',
+ },
+ },
+ textSize: {
+ title: 'Text Size',
+ options: {
+ small: 'Small',
+ medium: 'Medium',
+ large: 'Large',
+ },
+ },
+ textFont: {
+ title: 'Text Font',
+ options: {
+ siYuanSimSun: 'Source Han Serif',
+ SimHei: 'Sans',
+ lxgw: 'LXGW WenKai',
+ },
+ },
+ textboxOpacity: {
+ title: 'Textbox Opacity',
+ },
+ textPreview: {
+ title: 'Preview Text Showing',
+ text: "You are previewing the text's font, size and playback speed, now. You can adjust the above options according to your perception.",
+ },
+ },
+ },
+ sound: {
+ title: 'Sound',
+ options: {
+ volumeMain: { title: 'Main Volume' },
+ vocalVolume: { title: 'Vocal Volume' },
+ bgmVolume: { title: 'BGM Volume' },
+ seVolume: { title: 'Sound Effects Volume' },
+ uiSeVolume: { title: 'UI Sound Effects Volume' },
+ },
+ },
+ // language: {
+ // title: '语言',
+ // options: {
+ // },
+ // },
+ },
+ },
+ saving: {
+ title: 'SAVE',
+ isOverwrite: 'Are you sure you want to overwrite this save?',
+ },
+ loadSaving: {
+ title: 'LOAD',
+ },
+ title: {
+ title: 'TITLE',
+ },
+ exit: {
+ title: 'BACK',
+ },
+ },
+
+ title: {
+ start: {
+ title: 'START',
+ subtitle: '',
+ },
+ continue: {
+ title: 'CONTINUE',
+ subtitle: '',
+ },
+ options: {
+ title: 'OPTIONS',
+ subtitle: '',
+ },
+ load: {
+ title: 'LOAD',
+ subtitle: '',
+ },
+ extra: {
+ title: 'EXTRA',
+ subtitle: '',
+ },
+ },
+
+ gaming: {
+ noSaving: 'No saving',
+ buttons: {
+ hide: 'Hide',
+ show: 'Show',
+ backlog: 'Backlog',
+ replay: 'Replay',
+ auto: 'Auto',
+ forward: 'Forward',
+ quicklySave: 'Quickly Save',
+ quicklyLoad: 'Quickly Load',
+ save: 'Save',
+ load: 'Load',
+ options: 'Options',
+ title: 'Title',
+ titleTips: 'Confirm return to the title screen',
+ },
+ },
+
+ extra: {
+ title: 'EXTRA',
+ },
+};
+
+export default en;
diff --git a/packages/webgal/src/translations/fr.ts b/packages/webgal/src/translations/fr.ts
new file mode 100644
index 000000000..b157cd071
--- /dev/null
+++ b/packages/webgal/src/translations/fr.ts
@@ -0,0 +1,178 @@
+const fr = {
+ // 通用
+ common: {
+ yes: 'OK',
+ no: 'Annuler',
+ },
+
+ menu: {
+ options: {
+ title: 'OPTIONS',
+ pages: {
+ system: {
+ title: 'Système',
+ options: {
+ autoSpeed: {
+ title: 'Vitesse de lecture automatique',
+ options: {
+ slow: 'Lente',
+ medium: 'Moyenne',
+ fast: 'Rapide',
+ },
+ },
+ language: {
+ title: 'Langue',
+ },
+ resetData: {
+ title: 'Effacer ou réinitialiser les données',
+ options: {
+ clearGameSave: 'Effacer la sauvegarde du jeu',
+ resetSettings: 'Réinitialiser les paramètres',
+ clearAll: 'Tout effacer',
+ },
+ dialogs: {
+ clearGameSave: 'Êtes-vous sûr de vouloir effacer la sauvegarde du jeu',
+ resetSettings: 'Êtes-vous sûr de vouloir réinitialiser tous les paramètres',
+ clearAll: 'Êtes-vous sûr de vouloir tout effacer',
+ },
+ },
+ gameSave: {
+ title: 'Importer ou exporter la sauvegarde du jeu et les options',
+ options: {
+ export: 'Exporter la sauvegarde du jeu et les options',
+ import: 'Importer la sauvegarde du jeu et les options',
+ },
+ dialogs: {
+ import: {
+ title: 'Êtes-vous sûr de vouloir importer la sauvegarde du jeu et les options',
+ tip: 'Importer la sauvegarde du jeu',
+ error: "Impossible d'analyser la sauvegarde du jeu",
+ },
+ },
+ },
+ about: {
+ title: 'À propos de WebGAL',
+ subTitle: 'WebGAL: Un moteur de visual novel basé sur le web en open-source',
+ version: 'Version',
+ source: 'Dépôt de code source',
+ contributors: 'Contributeurs',
+ website: 'Site web',
+ },
+ },
+ },
+ display: {
+ title: 'Affichage',
+ options: {
+ textSpeed: {
+ title: "Vitesse d'affichage du texte",
+ options: {
+ slow: 'Lente',
+ medium: 'Moyenne',
+ fast: 'Rapide',
+ },
+ },
+ textSize: {
+ title: 'Taille du texte',
+ options: {
+ small: 'Petite',
+ medium: 'Moyenne',
+ large: 'Grande',
+ },
+ },
+ textFont: {
+ title: 'Police du texte',
+ options: {
+ siYuanSimSun: 'Source Han Serif',
+ SimHei: 'Sans',
+ lxgw: 'LXGW WenKai',
+ },
+ },
+ textboxOpacity: {
+ title: 'Textbox Opacity',
+ },
+ textPreview: {
+ title: "Aperçu de l'affichage du texte",
+ text: 'Vous prévisualisez la police, la taille et la vitesse de lecture du texte, maintenant. Vous pouvez ajuster les options ci-dessus selon votre perception.',
+ },
+ },
+ },
+ sound: {
+ title: 'Son',
+ options: {
+ volumeMain: { title: 'Volume principal' },
+ vocalVolume: { title: 'Volume des voix' },
+ bgmVolume: { title: 'Volume de la musique de fond' },
+ seVolume: { title: 'Volume des effets sonores' },
+ uiSeVolume: { title: 'Volume de l’interface utilisateur' },
+ },
+ },
+ // language: {
+ // title: 'Langue',
+ // options: {
+ // },
+ // },
+ },
+ },
+ saving: {
+ title: 'SAUVEGARDER',
+ isOverwrite: 'Êtes-vous sûr de vouloir écraser cette sauvegarde ?',
+ },
+ loadSaving: {
+ title: 'CHARGER',
+ },
+ title: {
+ title: 'TITRE',
+ },
+ exit: {
+ title: 'RETOUR',
+ },
+ },
+
+ title: {
+ start: {
+ title: 'COMMENCER',
+ subtitle: '',
+ },
+ continue: {
+ title: 'CONTINUER',
+ subtitle: '',
+ },
+ options: {
+ title: 'OPTIONS',
+ subtitle: '',
+ },
+ load: {
+ title: 'CHARGER',
+ subtitle: '',
+ },
+ extra: {
+ title: 'EXTRA',
+ subtitle: '',
+ },
+ },
+
+ gaming: {
+ noSaving: 'Aucune sauvegarde',
+ buttons: {
+ hide: 'Masquer',
+ show: 'Afficher',
+ backlog: 'Journal',
+ replay: 'Rejouer',
+ auto: 'Automatique',
+ forward: 'Avancer',
+ quicklySave: 'Sauvegarde rapide',
+ quicklyLoad: 'Chargement rapide',
+ save: 'Sauvegarder',
+ load: 'Charger',
+ options: 'Options',
+ title: 'Titre',
+ titleTips: "Confirmer le retour à l'écran titre",
+ },
+ },
+
+ extra: {
+ title: 'EXTRA',
+ },
+};
+
+export default fr;
diff --git a/packages/webgal/src/translations/jp.ts b/packages/webgal/src/translations/jp.ts
new file mode 100644
index 000000000..f720af7b3
--- /dev/null
+++ b/packages/webgal/src/translations/jp.ts
@@ -0,0 +1,188 @@
+const jp = {
+ // 通用
+ common: {
+ yes: 'はい',
+ no: 'いいえ',
+ },
+
+ menu: {
+ options: {
+ title: 'CONFIG',
+ pages: {
+ system: {
+ title: 'システム',
+ options: {
+ autoSpeed: {
+ title: '自動再生速度',
+ options: {
+ slow: '遅く',
+ medium: '標準',
+ fast: '速く',
+ },
+ },
+ language: {
+ title: '言語',
+ },
+ resetData: {
+ title: 'データの復元と削除',
+ options: {
+ clearGameSave: 'すべてのセーブデータを削除',
+ resetSettings: '設定を元に戻す',
+ clearAll: 'すべてのデータを削除',
+ },
+ dialogs: {
+ clearGameSave: 'すべてのセーブデータを削除しますか?',
+ resetSettings: '設定を元に戻しますか?',
+ clearAll: 'すべてのデータを削除しますか?',
+ },
+ },
+ gameSave: {
+ title: 'セーブデータと設定のインポートとエクスポート',
+ options: {
+ export: 'セーブデータと設定のエクスポート',
+ import: 'セーブデータと設定のインポート',
+ },
+ dialogs: {
+ import: {
+ title: 'セーブデータと設定をインポートしますか?',
+ tip: 'セーブデータのインポート',
+ error: 'セーブデータの読み込みに失敗しました',
+ },
+ },
+ },
+ about: {
+ title: 'WebGAL について',
+ subTitle: 'WebGAL: オープンソースのウェブベースビジュアルノベルエンジン',
+ version: 'バージョン',
+ source: 'ソースコードリポジトリ',
+ contributors: '貢献者',
+ website: 'ウェブサイト',
+ },
+ },
+ },
+ display: {
+ title: 'ウィンドウ',
+ options: {
+ fullScreen: {
+ title: 'フルスクリーン',
+ options: {
+ on: 'オン',
+ off: 'オフ',
+ },
+ },
+ textSpeed: {
+ title: 'テキスト表示速度',
+ options: {
+ slow: '遅く',
+ medium: '標準',
+ fast: '速く',
+ },
+ },
+ textSize: {
+ title: 'テキストサイズ',
+ options: {
+ small: '小',
+ medium: '中',
+ large: '大',
+ },
+ },
+ textFont: {
+ title: 'フォント',
+ options: {
+ siYuanSimSun: '源ノ明朝(中国語)',
+ SimHei: 'OPPO Sans',
+ lxgw: 'LXGW WenKai',
+ },
+ },
+ textboxOpacity: {
+ title: 'テキストボックスの不透明度',
+ },
+ textPreview: {
+ title: 'テキスト表示プレビュー',
+ text: 'これはテキストボックスのフォントとサイズ、表示速度のプレビューです。上にある設定で変更できます。',
+ },
+ },
+ },
+ sound: {
+ title: 'サウンド',
+ options: {
+ volumeMain: { title: 'メイン音量' },
+ vocalVolume: { title: 'ボイス音量' },
+ bgmVolume: { title: 'BGM 音量' },
+ seVolume: { title: '効果音音量' },
+ uiSeVolume: { title: 'UI 効果音音量' },
+ voiceOption: { title: 'ボイスの中断' },
+ voiceStop: { title: '中断する' },
+ voiceContinue: { title: '中断しない' },
+ },
+ },
+ // language: {
+ // title: '言語',
+ // options: {
+ // },
+ // },
+ },
+ },
+ saving: {
+ title: 'SAVE',
+ isOverwrite: 'セーブデータを上書きしますか?',
+ },
+ loadSaving: {
+ title: 'LOAD',
+ },
+ title: {
+ title: 'HOME',
+ },
+ exit: {
+ title: 'BACK',
+ },
+ },
+
+ title: {
+ start: {
+ title: '初めから',
+ subtitle: 'START',
+ },
+ continue: {
+ title: '続きから',
+ subtitle: 'CONTINUE',
+ },
+ options: {
+ title: '設定',
+ subtitle: 'CONFIG',
+ },
+ load: {
+ title: 'ロード',
+ subtitle: 'LOAD',
+ },
+ extra: {
+ title: '鑑賞モード',
+ subtitle: 'EXTRA',
+ },
+ },
+
+ gaming: {
+ noSaving: 'クイックセーブなし',
+ buttons: {
+ hide: 'CLOSE',
+ show: 'SHOW',
+ backlog: 'LOG',
+ replay: 'REPLAY',
+ auto: 'AUTO',
+ forward: 'SKIP',
+ quicklySave: 'QUICK SAVE',
+ quicklyLoad: 'QUICK LOAD',
+ save: 'SAVE',
+ load: 'LOAD',
+ options: 'CONFIG',
+ title: 'HOME',
+ titleTips: 'タイトル画面に戻りますか?',
+ },
+ },
+
+ extra: {
+ title: '鑑賞モード',
+ },
+};
+
+export default jp;
diff --git a/packages/webgal/src/translations/zh-cn.ts b/packages/webgal/src/translations/zh-cn.ts
new file mode 100644
index 000000000..471b2a547
--- /dev/null
+++ b/packages/webgal/src/translations/zh-cn.ts
@@ -0,0 +1,192 @@
+const zhCn = {
+ // 通用
+ common: {
+ yes: '是',
+ no: '否',
+ },
+
+ menu: {
+ options: {
+ title: '选项',
+ pages: {
+ system: {
+ title: '系统',
+ options: {
+ autoSpeed: {
+ title: '自动播放速度',
+ options: {
+ slow: '慢',
+ medium: '中',
+ fast: '快',
+ },
+ },
+ language: {
+ title: '语言',
+ },
+ resetData: {
+ title: '清除或还原数据',
+ options: {
+ clearGameSave: '清除所有存档',
+ resetSettings: '还原默认设置',
+ clearAll: '清除所有数据',
+ },
+ dialogs: {
+ clearGameSave: '确定要清除存档吗',
+ resetSettings: '确定要还原默认设置吗',
+ clearAll: '确定要清除所有数据吗',
+ },
+ },
+ gameSave: {
+ title: '导入或导出存档与选项',
+ options: {
+ export: '导出存档与选项',
+ import: '导入存档与选项',
+ },
+ dialogs: {
+ import: {
+ title: '确定要导入存档与选项吗',
+ tip: '导入存档',
+ error: '存档解析失败',
+ },
+ },
+ },
+ about: {
+ title: '关于 WebGAL',
+ subTitle: 'WebGAL:开源的网页端视觉小说引擎',
+ version: '版本号',
+ source: '源代码仓库',
+ contributors: '贡献者',
+ website: '网站',
+ },
+ },
+ },
+ display: {
+ title: '显示',
+ options: {
+ fullScreen: {
+ title: '全屏模式',
+ options: {
+ on: '开启',
+ off: '关闭',
+ },
+ },
+ textSpeed: {
+ title: '文字显示速度',
+ options: {
+ slow: '慢',
+ medium: '中',
+ fast: '快',
+ },
+ },
+ textSize: {
+ title: '文本大小',
+ options: {
+ small: '小',
+ medium: '中',
+ large: '大',
+ },
+ },
+ textFont: {
+ title: '文本字体',
+ options: {
+ siYuanSimSun: '思源宋体',
+ SimHei: '黑体',
+ lxgw: '霞鹜文楷',
+ },
+ },
+ textboxOpacity: {
+ title: '文本框不透明度',
+ },
+ textPreview: {
+ title: '文本显示预览',
+ text: '现在预览的是文本框字体大小和播放速度的情况,您可以根据您的观感调整上面的选项。',
+ },
+ },
+ },
+ sound: {
+ title: '音频',
+ options: {
+ volumeMain: { title: '主音量' },
+ vocalVolume: { title: '语音音量' },
+ bgmVolume: { title: '背景音乐音量' },
+ seVolume: { title: '音效音量' },
+ uiSeVolume: { title: '用户界面音效音量' },
+ voiceOption: { title: '是否中断语音' },
+ voiceStop: { title: '停止语音' },
+ voiceContinue: { title: '继续语音' },
+ },
+ },
+ // language: {
+ // title: '语言',
+ // options: {
+ // },
+ // },
+ },
+ },
+ saving: {
+ title: '存档',
+ isOverwrite: '是否覆盖存档?',
+ },
+ loadSaving: {
+ title: '读档',
+ },
+ title: {
+ title: '标题',
+ options: {
+ load: '',
+ extra: '鉴赏模式',
+ },
+ },
+ exit: {
+ title: '返回',
+ },
+ },
+
+ title: {
+ start: {
+ title: '开始游戏',
+ subtitle: 'START',
+ },
+ continue: {
+ title: '继续游戏',
+ subtitle: 'CONTINUE',
+ },
+ options: {
+ title: '游戏选项',
+ subtitle: 'OPTIONS',
+ },
+ load: {
+ title: '读取存档',
+ subtitle: 'LOAD',
+ },
+ extra: {
+ title: '鉴赏模式',
+ subtitle: 'EXTRA',
+ },
+ },
+
+ gaming: {
+ noSaving: '暂无存档',
+ buttons: {
+ hide: '隐藏',
+ show: '显示',
+ backlog: '回想',
+ replay: '重播',
+ auto: '自动',
+ forward: '快进',
+ quicklySave: '快速存档',
+ quicklyLoad: '快速读档',
+ save: '存档',
+ load: '读档',
+ options: '选项',
+ title: '标题',
+ titleTips: '确认返回到标题界面吗',
+ },
+ },
+
+ extra: {
+ title: '鉴赏模式',
+ },
+};
+
+export default zhCn;
diff --git a/packages/webgal/src/translations/zh-tw.ts b/packages/webgal/src/translations/zh-tw.ts
new file mode 100644
index 000000000..986bf23d0
--- /dev/null
+++ b/packages/webgal/src/translations/zh-tw.ts
@@ -0,0 +1,181 @@
+const zhTw = {
+ // 通用
+ common: {
+ yes: '是',
+ no: '否',
+ },
+
+ menu: {
+ options: {
+ title: '選項',
+ pages: {
+ system: {
+ title: '系統',
+ options: {
+ autoSpeed: {
+ title: '自動播放速度',
+ options: {
+ slow: '慢',
+ medium: '中',
+ fast: '快',
+ },
+ },
+ language: {
+ title: '語言',
+ },
+ resetData: {
+ title: '清除或還原數據',
+ options: {
+ clearGameSave: '清除所有存檔',
+ resetSettings: '還原默認設定',
+ clearAll: '清除所有數據',
+ },
+ dialogs: {
+ clearGameSave: '確定要清除存檔嗎',
+ resetSettings: '確定要還原默認設定嗎',
+ clearAll: '確定要清除所有數據嗎',
+ },
+ },
+ gameSave: {
+ title: '導入或導出存檔與選項',
+ options: {
+ export: '導出存檔與選項',
+ import: '導入存檔與選項',
+ },
+ dialogs: {
+ import: {
+ title: '確定要導入存檔與選項嗎',
+ tip: '導入存檔',
+ error: '存檔解析失敗',
+ },
+ },
+ },
+ about: {
+ title: '關於 WebGAL',
+ subTitle: 'WebGAL:開源的線上視覺小說製作引擎',
+ version: '版本號',
+ source: '源代碼倉庫',
+ contributors: '貢獻者',
+ website: '網站',
+ },
+ },
+ },
+ display: {
+ title: '顯示',
+ options: {
+ textSpeed: {
+ title: '文字顯示速度',
+ options: {
+ slow: '慢',
+ medium: '中',
+ fast: '快',
+ },
+ },
+ textSize: {
+ title: '文字大小',
+ options: {
+ small: '小',
+ medium: '中',
+ large: '大',
+ },
+ },
+ textFont: {
+ title: '文字字體',
+ options: {
+ siYuanSimSun: '霞鹜文楷',
+ SimHei: '黑體',
+ },
+ },
+ textboxOpacity: {
+ title: '文本框不透明度',
+ },
+ textPreview: {
+ title: '文字顯示預覽',
+ text: '現在預覽的是文字框字體大小和播放速度的情況,您可以根據您的觀感調整上面的選項。',
+ },
+ },
+ },
+ sound: {
+ title: '音量',
+ options: {
+ volumeMain: { title: '主音量' },
+ vocalVolume: { title: '語音音量' },
+ bgmVolume: { title: '背景音樂音量' },
+ seVolume: { title: '音效音量' },
+ uiSeVolume: { title: '用戶界面音效音量' },
+ },
+ },
+ // language: {
+ // title: '語言',
+ // options: {
+ // },
+ // },
+ },
+ },
+ saving: {
+ title: '存檔',
+ isOverwrite: '是否要覆蓋存檔?',
+ },
+ loadSaving: {
+ title: '讀檔',
+ },
+ title: {
+ title: '標題',
+ options: {
+ load: '',
+ extra: 'CG模式',
+ },
+ },
+ exit: {
+ title: '返回',
+ },
+ },
+
+ title: {
+ start: {
+ title: '開始遊戲',
+ subtitle: 'START',
+ },
+ continue: {
+ title: '繼續遊戲',
+ subtitle: 'CONTINUE',
+ },
+ options: {
+ title: '遊戲選項',
+ subtitle: 'OPTIONS',
+ },
+ load: {
+ title: '讀取存檔',
+ subtitle: 'LOAD',
+ },
+ extra: {
+ title: 'CG模式',
+ subtitle: 'EXTRA',
+ },
+ },
+
+ gaming: {
+ noSaving: '暫無存檔',
+ buttons: {
+ hide: '隱藏',
+ show: '顯示',
+ backlog: '回想',
+ replay: '重播',
+ auto: '自動',
+ forward: '加速',
+ quicklySave: '快速存檔',
+ quicklyLoad: '快速讀檔',
+ save: '存檔',
+ load: '讀檔',
+ options: '選項',
+ title: '標題',
+ titleTips: '確認返回到標題界面嗎',
+ },
+ },
+
+ extra: {
+ title: 'CG模式',
+ },
+};
+
+export default zhTw;
diff --git a/packages/webgal/src/types/angular-expressions.d.ts b/packages/webgal/src/types/angular-expressions.d.ts
new file mode 100644
index 000000000..b5518d2a3
--- /dev/null
+++ b/packages/webgal/src/types/angular-expressions.d.ts
@@ -0,0 +1,4 @@
+import { EvaluatorFunc } from 'angular-expressions';
+declare module 'angular-expressions' {
+ export function compile(src: string, lexerOptions?: any): () => number | boolean | EvaluatorFunc;
+}
diff --git a/packages/webgal/src/types/debugProtocol.ts b/packages/webgal/src/types/debugProtocol.ts
new file mode 100644
index 000000000..267f67a18
--- /dev/null
+++ b/packages/webgal/src/types/debugProtocol.ts
@@ -0,0 +1,27 @@
+import { IStageState } from '@/store/stageInterface';
+
+export enum DebugCommand {
+ // 跳转
+ JUMP,
+ // 同步自客户端
+ SYNCFC,
+ // 同步自编辑器
+ SYNCFE,
+ // 执行指令
+ EXE_COMMAND,
+ // 重新拉取模板样式文件
+ REFETCH_TEMPLATE_FILES,
+}
+
+export interface IDebugMessage {
+ event: string;
+ data: {
+ command: DebugCommand;
+ sceneMsg: {
+ sentence: number;
+ scene: string;
+ };
+ message: string;
+ stageSyncMsg: IStageState;
+ };
+}
diff --git a/packages/webgal/src/vite-env.d.ts b/packages/webgal/src/vite-env.d.ts
new file mode 100644
index 000000000..11f02fe2a
--- /dev/null
+++ b/packages/webgal/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/packages/webgal/tsconfig.json b/packages/webgal/tsconfig.json
new file mode 100644
index 000000000..e46b697af
--- /dev/null
+++ b/packages/webgal/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": [
+ "src"
+ ],
+ "references": [{ "path": "./tsconfig.node.json"}]
+}
diff --git a/packages/webgal/tsconfig.node.json b/packages/webgal/tsconfig.node.json
new file mode 100644
index 000000000..99b8d5789
--- /dev/null
+++ b/packages/webgal/tsconfig.node.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["ES2021.String"],
+ "composite": true,
+ "module": "esnext",
+ "moduleResolution": "node"
+ },
+ "include": [
+ "vite.config.ts"
+ ]
+}
diff --git a/packages/webgal/vite.config.ts b/packages/webgal/vite.config.ts
new file mode 100644
index 000000000..0fb7f4bbe
--- /dev/null
+++ b/packages/webgal/vite.config.ts
@@ -0,0 +1,63 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import loadVersion from 'vite-plugin-package-version';
+import { resolve, relative } from 'path';
+import { visualizer } from 'rollup-plugin-visualizer';
+import { readdirSync, watch, writeFileSync } from 'fs';
+import { isEqual } from 'lodash';
+// https://vitejs.dev/config/
+
+// @ts-ignore
+const env = process.env.NODE_ENV;
+console.log(env);
+(() => {
+ const pixiPerformScriptDirPath = './src/Core/gameScripts/pixi/performs/';
+ const pixiPerformManagerDirPath = './src/Core/util/pixiPerformManager/';
+ const relativePath = relative(pixiPerformManagerDirPath, pixiPerformScriptDirPath).replaceAll('\\', '/');
+ let lastFiles: string[] = [];
+
+ function setInitFile() {
+ console.log('正在自动编写pixi特效依赖注入');
+ writeFileSync(
+ resolve(pixiPerformManagerDirPath, 'initRegister.ts'),
+ lastFiles
+ .map((v) => {
+ const filePath = relativePath + '/' + v.slice(0, v.lastIndexOf('.'));
+ return `import '${filePath}';`;
+ })
+ .join('\n') + '\n',
+ { encoding: 'utf-8' },
+ );
+ }
+
+ function getPixiPerformScriptFiles() {
+ const pixiPerformScriptFiles = readdirSync(pixiPerformScriptDirPath, { encoding: 'utf-8' }).filter((v) =>
+ ['ts', 'js', 'tsx', 'jsx'].includes(v.slice(v.indexOf('.') + 1, v.length)),
+ );
+ if (!isEqual(pixiPerformScriptFiles, lastFiles)) {
+ lastFiles = pixiPerformScriptFiles;
+ setInitFile();
+ }
+ }
+
+ getPixiPerformScriptFiles();
+
+ if (env !== 'production') watch(pixiPerformScriptDirPath, { encoding: 'utf-8' }, getPixiPerformScriptFiles);
+})();
+
+export default defineConfig({
+ plugins: [
+ react(),
+ loadVersion(),
+ // @ts-ignore
+ visualizer(),
+ ],
+ resolve: {
+ alias: {
+ '@': resolve('src'),
+ },
+ },
+ build: {
+ // sourcemap: true,
+ },
+});
diff --git a/packages/yukimi/libwebgal-base.ykm b/packages/yukimi/libwebgal-base.ykm
new file mode 100644
index 000000000..b145e2d88
--- /dev/null
+++ b/packages/yukimi/libwebgal-base.ykm
@@ -0,0 +1,41 @@
+# Dialog
+- extern say content speaker vocal %
+@__type content string
+@__type speaker string
+@__type vocal string
+^
+
+
+# Background and figure
+-extern changeBg content %
+@__type content string
+^
+
+-extern changeFigure content left center right id %
+@__type conetnt string
+@__type left boolean
+@__type center boolean
+@__type right boolean
+@__type id string
+^
+
+
+#bgm, effect sound, video
+-extern bgm conetnt %
+@__type conetnt string
+^
+
+-extern playVideo conetnt %
+@__type content string
+^
+
+-extern playEffects conetnt %
+@__type conetnt string
+^
+
+#
+
+
+# Demo
+- scene "main"
+@say "你好" "WebGAL" "v1.ogg"
\ No newline at end of file
diff --git a/packages/yukimi/ykmc.exe b/packages/yukimi/ykmc.exe
new file mode 100644
index 000000000..f307dbfd2
Binary files /dev/null and b/packages/yukimi/ykmc.exe differ
diff --git a/release-to-server.sh b/release-to-server.sh
new file mode 100644
index 000000000..c87eb5bda
--- /dev/null
+++ b/release-to-server.sh
@@ -0,0 +1,13 @@
+# 安装依赖并构建
+yarn
+yarn build
+
+# 进入 WebGAL 构建目录
+cd packages || exit
+rm -r server/WebGAL/*
+cd webgal || exit
+# 复制
+cp -r dist/index.html ../server/WebGAL
+cp -r dist/assets ../server/WebGAL
+cp -r dist/game ../server/WebGAL
+cp -r dist/webgal-serviceworker.js ../server/WebGAL
diff --git a/release-to-terre.sh b/release-to-terre.sh
new file mode 100644
index 000000000..d0ede3b16
--- /dev/null
+++ b/release-to-terre.sh
@@ -0,0 +1,17 @@
+# 安装依赖并构建
+yarn
+yarn build
+
+# 进入 Terre 目录
+cd ../WebGAL_Terre/packages/terre2/assets/templates/WebGAL_Template || exit
+# 删除其他文件
+rm -r assets
+rm -r index.html
+rm -r webgal-serviceworker.js
+
+# 进入 WebGAL 构建目录
+cd ../../../../../../WebGAL/packages/webgal || exit
+# 复制
+cp -r dist/index.html ../../../WebGAL_Terre/packages/terre2/assets/templates/WebGAL_Template
+cp -r dist/assets ../../../WebGAL_Terre/packages/terre2/assets/templates/WebGAL_Template
+cp -r dist/webgal-serviceworker.js ../../../WebGAL_Terre/packages/terre2/assets/templates/WebGAL_Template
diff --git a/releasenote.md b/releasenote.md
new file mode 100644
index 000000000..28f366fa2
--- /dev/null
+++ b/releasenote.md
@@ -0,0 +1,62 @@
+## 发布日志
+
+**本仓库只发布源代码**
+
+**如果你想要体验使用便捷的图形化编辑器创建、制作并实时预览 WebGAL 游戏,请 [下载 WebGAL 图形化编辑器](https://github.com/MakinoharaShoko/WebGAL_Terre/releases)**
+
+### 在此版本中
+
+#### 新功能
+
+对话内容支持不间断的连续空格
+
+#### 修复
+
+读取存档时意外在状态表中存储了多份演出记录的问题
+
+带有 id 的效果音播放在停止后演出未完全清除的问题
+
+对状态表和演出控制器中的演出列表在插入时去重
+
+
+## Release Notes
+
+**Only source code is released in this repository**
+
+**If you want to experience creating, making, and real-time previewing WebGAL games using a user-friendly graphical editor, please [download the WebGAL graphical editor](https://github.com/MakinoharaShoko/WebGAL_Terre/releases)**
+
+### In this version
+
+#### New Features
+
+Dialogue content now supports continuous spaces.
+
+#### Fixes
+
+Fixed an issue where multiple performance records were unexpectedly stored in the state table when loading a save.
+
+Fixed an issue where performances with IDs were not completely cleared after stopping sound effects playback.
+
+Deduplicated performance lists in the state table and performance controller upon insertion.
+
+
+## リリースノート
+
+**このリポジトリはソースコードのみを公開しています**
+
+**もしあなたが使いやすいグラフィカルエディタでWebGALゲームを作成、制作、リアルタイムプレビューしたい場合は、[WebGALグラフィカルエディタをダウンロードしてください](https://github.com/MakinoharaShoko/WebGAL_Terre/releases)**
+
+### このバージョンについて
+
+#### 新機能
+
+会話内容で連続するスペースが正しく表示されるようになりました。
+
+#### 修正
+
+セーブデータ読み込み時に、ステータステーブルに複数の演出記録が重複して保存される問題を修正しました。
+
+IDを持つ効果音が停止した後、演出が完全にクリアされない問題を修正しました。
+
+ステータステーブルと演出コントローラーの演出リストにおいて、重複した項目が挿入されるのを防ぐように修正しました。
+
diff --git a/res/script/WG_main.js b/res/script/WG_main.js
deleted file mode 100644
index c7637b52d..000000000
--- a/res/script/WG_main.js
+++ /dev/null
@@ -1,41 +0,0 @@
-var currentScene ='';
-var currentSceneIndex = 0;
-var currentSentence = 0;
-function getScene() {
- let getScReq = null;
- getScReq = new XMLHttpRequest();
-
- if (getScReq != null) {
- getScReq.open("get", "game/scene/start.sce", true);
- getScReq.send();
- getScReq.onreadystatechange = doResult; //设置回调函数
- }
- function doResult() {
- if (getScReq.readyState === 4) { //4表示执行完成
- if (getScReq.status === 200) { //200表示执行成功
- currentScene = getScReq.responseText;
- currentScene = currentScene.split('\n');
- for (let i = 0;i{processSentence(currentSentence)}
- ReactDOM.render(changedText,document.getElementById('SceneText'));
- currentSentence = currentSentence+1;
-}
\ No newline at end of file
diff --git a/res/script/babel.min.js b/res/script/babel.min.js
deleted file mode 100644
index 10757768a..000000000
--- a/res/script/babel.min.js
+++ /dev/null
@@ -1,25 +0,0 @@
-!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Babel=t():e.Babel=t()}(this,function(){return function(e){function t(n){if(r[n])return r[n].exports;var i=r[n]={exports:{},id:n,loaded:!1};return e[n].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var r={};return t.m=e,t.c=r,t.p="",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var r=t.slice(1),n=e[t[0]];return function(e,t,i){n.apply(this,[e,t,i].concat(r))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,r){"use strict";function n(e,t){return g(t)&&"string"==typeof t[0]?e.hasOwnProperty(t[0])?[e[t[0]]].concat(t.slice(1)):void 0:"string"==typeof t?e[t]:t}function i(e){var t=(e.presets||[]).map(function(e){var t=n(E,e);if(!t)throw new Error('Invalid preset specified in Babel options: "'+e+'"');return g(t)&&"object"===h(t[0])&&t[0].hasOwnProperty("buildPreset")&&(t[0]=d({},t[0],{buildPreset:t[0].buildPreset})),t}),r=(e.plugins||[]).map(function(e){var t=n(b,e);if(!t)throw new Error('Invalid plugin specified in Babel options: "'+e+'"');return t});return d({babelrc:!1},e,{presets:t,plugins:r})}function s(e,t){return y.transform(e,i(t))}function a(e,t,r){return y.transformFromAst(e,t,i(r))}function o(e,t){b.hasOwnProperty(e)&&console.warn('A plugin named "'+e+'" is already registered, it will be overridden'),b[e]=t}function u(e){Object.keys(e).forEach(function(t){return o(t,e[t])})}function l(e,t){E.hasOwnProperty(e)&&console.warn('A preset named "'+e+'" is already registered, it will be overridden'),E[e]=t}function c(e){Object.keys(e).forEach(function(t){return l(t,e[t])})}function f(e){(0,v.runScripts)(s,e)}function p(){window.removeEventListener("DOMContentLoaded",f)}Object.defineProperty(t,"__esModule",{value:!0}),t.version=t.buildExternalHelpers=t.availablePresets=t.availablePlugins=void 0;var d=Object.assign||function(e){for(var t=1;t=n.length)break;a=n[s++]}else{if(s=n.next(),s.done)break;a=s.value}if(e===a)return!0}}return!1}function o(e,t,r){if(e){var n=z.NODE_FIELDS[e.type];if(n){var i=n[t];i&&i.validate&&(i.optional&&null==r||i.validate(e,t,r))}}}function u(e,t){for(var r=(0,B.default)(t),n=r,i=Array.isArray(n),s=0,n=i?n:(0,T.default)(n);;){var a;if(i){if(s>=n.length)break;a=n[s++]}else{if(s=n.next(),s.done)break;a=s.value}var o=a;if(e[o]!==t[o])return!1}return!0}function l(e,t,r){return e.object=z.memberExpression(e.object,e.property,e.computed),e.property=t,e.computed=!!r,e}function c(e,t){return e.object=z.memberExpression(t,e.object),e}function f(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"body";return e[t]=z.toBlock(e[t],e)}function p(e){if(!e)return e;var t={};for(var r in e)"_"!==r[0]&&(t[r]=e[r]);return t}function d(e){var t=p(e);return delete t.loc,t}function h(e){if(!e)return e;var t={};for(var r in e)if("_"!==r[0]){var n=e[r];n&&(n.type?n=z.cloneDeep(n):Array.isArray(n)&&(n=n.map(z.cloneDeep))),t[r]=n}return t}function m(e,t){var r=e.split(".");return function(e){if(!z.isMemberExpression(e))return!1;for(var n=[e],i=0;n.length;){var s=n.shift();if(t&&i===r.length)return!0;if(z.isIdentifier(s)){if(r[i]!==s.name)return!1}else{if(!z.isStringLiteral(s)){if(z.isMemberExpression(s)){if(s.computed&&!z.isStringLiteral(s.property))return!1;n.push(s.object),n.push(s.property);continue}return!1}if(r[i]!==s.value)return!1}if(++i>r.length)return!1}return!0}}function y(e){for(var t=z.COMMENT_KEYS,r=Array.isArray(t),n=0,t=r?t:(0,T.default)(t);;){var i;if(r){if(n>=t.length)break;i=t[n++]}else{if(n=t.next(),n.done)break;i=n.value}delete e[i]}return e}function v(e,t){return g(e,t),b(e,t),E(e,t),e}function g(e,t){x("trailingComments",e,t)}function b(e,t){x("leadingComments",e,t)}function E(e,t){x("innerComments",e,t)}function x(e,t,r){t&&r&&(t[e]=(0,K.default)([].concat(t[e],r[e]).filter(Boolean)))}function A(e,t){if(!e||!t)return e;for(var r=z.INHERIT_KEYS.optional,n=Array.isArray(r),i=0,r=n?r:(0,T.default)(r);;){var s;if(n){if(i>=r.length)break;s=r[i++]}else{if(i=r.next(),i.done)break;s=i.value}var a=s;null==e[a]&&(e[a]=t[a])}for(var o in t)"_"===o[0]&&(e[o]=t[o]);for(var u=z.INHERIT_KEYS.force,l=Array.isArray(u),c=0,u=l?u:(0,T.default)(u);;){var f;if(l){if(c>=u.length)break;f=u[c++]}else{if(c=u.next(),c.done)break;f=c.value}var p=f;e[p]=t[p]}return z.inheritsComments(e,t),e}function S(e){if(!_(e))throw new TypeError("Not a valid node "+(e&&e.type))}function _(e){return!(!e||!H.VISITOR_KEYS[e.type])}function D(e,t,r){if(e){var n=z.VISITOR_KEYS[e.type];if(n){r=r||{},t(e,r);for(var i=n,s=Array.isArray(i),a=0,i=s?i:(0,T.default)(i);;){var o;if(s){if(a>=i.length)break;o=i[a++]}else{if(a=i.next(),a.done)break;o=a.value}var u=o,l=e[u];if(Array.isArray(l))for(var c=l,f=Array.isArray(c),p=0,c=f?c:(0,T.default)(c);;){var d;if(f){if(p>=c.length)break;d=c[p++]}else{if(p=c.next(),p.done)break;d=p.value}var h=d;D(h,t,r)}else D(l,t,r)}}}}function C(e,t){t=t||{};for(var r=t.preserveComments?Z:ee,n=r,i=Array.isArray(n),s=0,n=i?n:(0,T.default)(n);;){var a;if(i){if(s>=n.length)break;a=n[s++]}else{if(s=n.next(),s.done)break;a=s.value}var o=a;null!=e[o]&&(e[o]=void 0)}for(var u in e)"_"===u[0]&&null!=e[u]&&(e[u]=void 0);for(var l=(0,k.default)(e),c=l,f=Array.isArray(c),p=0,c=f?c:(0,T.default)(c);;){var d;if(f){if(p>=c.length)break;d=c[p++]}else{if(p=c.next(),p.done)break;d=p.value}e[d]=null}}function w(e,t){return D(e,C,t),e}t.__esModule=!0,t.createTypeAnnotationBasedOnTypeof=t.removeTypeDuplicates=t.createUnionTypeAnnotation=t.valueToNode=t.toBlock=t.toExpression=t.toStatement=t.toBindingIdentifierName=t.toIdentifier=t.toKeyAlias=t.toSequenceExpression=t.toComputedKey=t.isNodesEquivalent=t.isImmutable=t.isScope=t.isSpecifierDefault=t.isVar=t.isBlockScoped=t.isLet=t.isValidIdentifier=t.isReferenced=t.isBinding=t.getOuterBindingIdentifiers=t.getBindingIdentifiers=t.TYPES=t.react=t.DEPRECATED_KEYS=t.BUILDER_KEYS=t.NODE_FIELDS=t.ALIAS_KEYS=t.VISITOR_KEYS=t.NOT_LOCAL_BINDING=t.BLOCK_SCOPED_SYMBOL=t.INHERIT_KEYS=t.UNARY_OPERATORS=t.STRING_UNARY_OPERATORS=t.NUMBER_UNARY_OPERATORS=t.BOOLEAN_UNARY_OPERATORS=t.BINARY_OPERATORS=t.NUMBER_BINARY_OPERATORS=t.BOOLEAN_BINARY_OPERATORS=t.COMPARISON_BINARY_OPERATORS=t.EQUALITY_BINARY_OPERATORS=t.BOOLEAN_NUMBER_BINARY_OPERATORS=t.UPDATE_OPERATORS=t.LOGICAL_OPERATORS=t.COMMENT_KEYS=t.FOR_INIT_KEYS=t.FLATTENABLE_KEYS=t.STATEMENT_OR_BLOCK_KEYS=void 0;var P=r(360),k=n(P),F=r(2),T=n(F),O=r(14),B=n(O),R=r(35),I=n(R),M=r(135);Object.defineProperty(t,"STATEMENT_OR_BLOCK_KEYS",{enumerable:!0,get:function(){return M.STATEMENT_OR_BLOCK_KEYS}}),Object.defineProperty(t,"FLATTENABLE_KEYS",{enumerable:!0,get:function(){return M.FLATTENABLE_KEYS}}),Object.defineProperty(t,"FOR_INIT_KEYS",{enumerable:!0,get:function(){return M.FOR_INIT_KEYS}}),Object.defineProperty(t,"COMMENT_KEYS",{enumerable:!0,get:function(){return M.COMMENT_KEYS}}),Object.defineProperty(t,"LOGICAL_OPERATORS",{enumerable:!0,get:function(){return M.LOGICAL_OPERATORS}}),Object.defineProperty(t,"UPDATE_OPERATORS",{enumerable:!0,get:function(){return M.UPDATE_OPERATORS}}),Object.defineProperty(t,"BOOLEAN_NUMBER_BINARY_OPERATORS",{enumerable:!0,get:function(){return M.BOOLEAN_NUMBER_BINARY_OPERATORS}}),Object.defineProperty(t,"EQUALITY_BINARY_OPERATORS",{enumerable:!0,get:function(){return M.EQUALITY_BINARY_OPERATORS}}),Object.defineProperty(t,"COMPARISON_BINARY_OPERATORS",{enumerable:!0,get:function(){return M.COMPARISON_BINARY_OPERATORS}}),Object.defineProperty(t,"BOOLEAN_BINARY_OPERATORS",{enumerable:!0,get:function(){return M.BOOLEAN_BINARY_OPERATORS}}),Object.defineProperty(t,"NUMBER_BINARY_OPERATORS",{enumerable:!0,get:function(){return M.NUMBER_BINARY_OPERATORS}}),Object.defineProperty(t,"BINARY_OPERATORS",{enumerable:!0,get:function(){return M.BINARY_OPERATORS}}),Object.defineProperty(t,"BOOLEAN_UNARY_OPERATORS",{enumerable:!0,get:function(){return M.BOOLEAN_UNARY_OPERATORS}}),Object.defineProperty(t,"NUMBER_UNARY_OPERATORS",{enumerable:!0,get:function(){return M.NUMBER_UNARY_OPERATORS}}),Object.defineProperty(t,"STRING_UNARY_OPERATORS",{enumerable:!0,get:function(){return M.STRING_UNARY_OPERATORS}}),Object.defineProperty(t,"UNARY_OPERATORS",{enumerable:!0,get:function(){return M.UNARY_OPERATORS}}),Object.defineProperty(t,"INHERIT_KEYS",{enumerable:!0,get:function(){return M.INHERIT_KEYS}}),Object.defineProperty(t,"BLOCK_SCOPED_SYMBOL",{enumerable:!0,get:function(){return M.BLOCK_SCOPED_SYMBOL}}),Object.defineProperty(t,"NOT_LOCAL_BINDING",{enumerable:!0,get:function(){return M.NOT_LOCAL_BINDING}}),t.is=s,t.isType=a,t.validate=o,t.shallowEqual=u,t.appendToMemberExpression=l,t.prependToMemberExpression=c,t.ensureBlock=f,t.clone=p,t.cloneWithoutLoc=d,t.cloneDeep=h,t.buildMatchMemberExpression=m,t.removeComments=y,t.inheritsComments=v,t.inheritTrailingComments=g,t.inheritLeadingComments=b,t.inheritInnerComments=E,t.inherits=A,t.assertNode=S,t.isNode=_,t.traverseFast=D,t.removeProperties=C,t.removePropertiesDeep=w;var N=r(226);Object.defineProperty(t,"getBindingIdentifiers",{enumerable:!0,get:function(){return N.getBindingIdentifiers}}),Object.defineProperty(t,"getOuterBindingIdentifiers",{enumerable:!0,get:function(){return N.getOuterBindingIdentifiers}});var L=r(395);Object.defineProperty(t,"isBinding",{enumerable:!0,get:function(){return L.isBinding}}),Object.defineProperty(t,"isReferenced",{enumerable:!0,get:function(){return L.isReferenced}}),Object.defineProperty(t,"isValidIdentifier",{enumerable:!0,get:function(){return L.isValidIdentifier}}),Object.defineProperty(t,"isLet",{enumerable:!0,get:function(){return L.isLet}}),Object.defineProperty(t,"isBlockScoped",{enumerable:!0,get:function(){return L.isBlockScoped}}),Object.defineProperty(t,"isVar",{enumerable:!0,get:function(){return L.isVar}}),Object.defineProperty(t,"isSpecifierDefault",{enumerable:!0,get:function(){return L.isSpecifierDefault}}),Object.defineProperty(t,"isScope",{enumerable:!0,get:function(){return L.isScope}}),Object.defineProperty(t,"isImmutable",{enumerable:!0,get:function(){return L.isImmutable}}),Object.defineProperty(t,"isNodesEquivalent",{enumerable:!0,get:function(){return L.isNodesEquivalent}});var j=r(385);Object.defineProperty(t,"toComputedKey",{enumerable:!0,get:function(){return j.toComputedKey}}),Object.defineProperty(t,"toSequenceExpression",{enumerable:!0,get:function(){return j.toSequenceExpression}}),Object.defineProperty(t,"toKeyAlias",{enumerable:!0,get:function(){return j.toKeyAlias}}),Object.defineProperty(t,"toIdentifier",{enumerable:!0,get:function(){return j.toIdentifier}}),Object.defineProperty(t,"toBindingIdentifierName",{enumerable:!0,get:function(){return j.toBindingIdentifierName}}),Object.defineProperty(t,"toStatement",{enumerable:!0,get:function(){return j.toStatement}}),Object.defineProperty(t,"toExpression",{enumerable:!0,get:function(){return j.toExpression}}),Object.defineProperty(t,"toBlock",{enumerable:!0,get:function(){return j.toBlock}}),Object.defineProperty(t,"valueToNode",{enumerable:!0,get:function(){return j.valueToNode}});var U=r(393);Object.defineProperty(t,"createUnionTypeAnnotation",{enumerable:!0,get:function(){return U.createUnionTypeAnnotation}}),Object.defineProperty(t,"removeTypeDuplicates",{enumerable:!0,get:function(){return U.removeTypeDuplicates}}),Object.defineProperty(t,"createTypeAnnotationBasedOnTypeof",{enumerable:!0,get:function(){return U.createTypeAnnotationBasedOnTypeof}});var V=r(624),G=n(V),W=r(109),Y=n(W),q=r(600),K=n(q);r(390);var H=r(26),J=r(394),X=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}(J),z=t;t.VISITOR_KEYS=H.VISITOR_KEYS,t.ALIAS_KEYS=H.ALIAS_KEYS,t.NODE_FIELDS=H.NODE_FIELDS,t.BUILDER_KEYS=H.BUILDER_KEYS,t.DEPRECATED_KEYS=H.DEPRECATED_KEYS,t.react=X;for(var $ in z.VISITOR_KEYS)i($);z.FLIPPED_ALIAS_KEYS={},(0,B.default)(z.ALIAS_KEYS).forEach(function(e){z.ALIAS_KEYS[e].forEach(function(t){(z.FLIPPED_ALIAS_KEYS[t]=z.FLIPPED_ALIAS_KEYS[t]||[]).push(e)})}),(0,B.default)(z.FLIPPED_ALIAS_KEYS).forEach(function(e){z[e.toUpperCase()+"_TYPES"]=z.FLIPPED_ALIAS_KEYS[e],i(e)});t.TYPES=(0,B.default)(z.VISITOR_KEYS).concat((0,B.default)(z.FLIPPED_ALIAS_KEYS)).concat((0,B.default)(z.DEPRECATED_KEYS));(0,B.default)(z.BUILDER_KEYS).forEach(function(e){function t(){if(arguments.length>r.length)throw new Error("t."+e+": Too many arguments passed. Received "+arguments.length+" but can receive no more than "+r.length);var t={};t.type=e;for(var n=0,i=r,s=Array.isArray(i),a=0,i=s?i:(0,T.default)(i);;){var u;if(s){if(a>=i.length)break;u=i[a++]}else{if(a=i.next(),a.done)break;u=a.value}var l=u,c=z.NODE_FIELDS[e][l],f=arguments[n++];void 0===f&&(f=(0,Y.default)(c.default)),t[l]=f}for(var p in t)o(t,p,t[p]);return t}var r=z.BUILDER_KEYS[e];z[e]=t,z[e[0].toLowerCase()+e.slice(1)]=t});for(var Q in z.DEPRECATED_KEYS)!function(e){function t(t){return function(){return console.trace("The node type "+e+" has been renamed to "+r),t.apply(this,arguments)}}var r=z.DEPRECATED_KEYS[e];z[e]=z[e[0].toLowerCase()+e.slice(1)]=t(z[r]),z["is"+e]=t(z["is"+r]),z["assert"+e]=t(z["assert"+r])}(Q);(0,G.default)(z),(0,G.default)(z.VISITOR_KEYS);var Z=["tokens","start","end","loc","raw","rawValue"],ee=z.COMMENT_KEYS.concat(["comments"]).concat(Z)},function(e,t,r){"use strict";e.exports={default:r(404),__esModule:!0}},function(e,t){"use strict";t.__esModule=!0,t.default=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t,r){"use strict";function n(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}function i(e){return e&&e.__esModule?e:{default:e}}function s(e,t){e=(0,l.default)(e);var r=e,n=r.program;return t.length&&(0,m.default)(e,A,null,t),n.body.length>1?n.body:n.body[0]}t.__esModule=!0;var a=r(10),o=i(a);t.default=function(e,t){var r=void 0;try{throw new Error}catch(e){e.stack&&(r=e.stack.split("\n").slice(1).join("\n"))}t=(0,f.default)({allowReturnOutsideFunction:!0,allowSuperOutsideMethod:!0,preserveComments:!1},t);var n=function(){var i=void 0;try{i=v.parse(e,t),i=m.default.removeProperties(i,{preserveComments:t.preserveComments}),m.default.cheap(i,function(e){e[E]=!0})}catch(e){throw e.stack=e.stack+"from\n"+r,e}return n=function(){return i},i};return function(){for(var e=arguments.length,t=Array(e),r=0;r=l.length)break;p=l[f++]}else{if(f=l.next(),f.done)break;p=f.value}var h=p;if((!s||!s[h])&&o.visit(e,h))return}},s.clearNode=function(e,t){x.removeProperties(e,t),S.path.delete(e)},s.removeProperties=function(e,t){return x.traverseFast(e,s.clearNode,t),e},s.hasType=function(e,t,r,n){if((0,b.default)(n,e.type))return!1;if(e.type===r)return!0;var i={has:!1,type:r};return s(e,{blacklist:n,enter:a},t,i),i.has},s.clearCache=function(){S.clear()},s.clearCache.clearPath=S.clearPath,s.clearCache.clearScope=S.clearScope,s.copyCache=function(e,t){S.path.has(e)&&S.path.set(t,S.path.get(e))}},function(e,t){"use strict";function r(){throw new Error("setTimeout has not been defined")}function n(){throw new Error("clearTimeout has not been defined")}function i(e){if(c===setTimeout)return setTimeout(e,0);if((c===r||!c)&&setTimeout)return c=setTimeout,setTimeout(e,0);try{return c(e,0)}catch(t){try{return c.call(null,e,0)}catch(t){return c.call(this,e,0)}}}function s(e){if(f===clearTimeout)return clearTimeout(e);if((f===n||!f)&&clearTimeout)return f=clearTimeout,clearTimeout(e);try{return f(e)}catch(t){try{return f.call(null,e)}catch(t){return f.call(this,e)}}}function a(){m&&d&&(m=!1,d.length?h=d.concat(h):y=-1,h.length&&o())}function o(){if(!m){var e=i(a);m=!0;for(var t=h.length;t;){for(d=h,h=[];++y1)for(var r=1;r=0;n--){var i=e[n];"."===i?e.splice(n,1):".."===i?(e.splice(n,1),r++):r&&(e.splice(n,1),r--)}if(t)for(;r--;r)e.unshift("..");return e}function n(e,t){if(e.filter)return e.filter(t);for(var r=[],n=0;n=-1&&!i;s--){var a=s>=0?arguments[s]:e.cwd();if("string"!=typeof a)throw new TypeError("Arguments to path.resolve must be strings");a&&(t=a+"/"+t,i="/"===a.charAt(0))}return t=r(n(t.split("/"),function(e){return!!e}),!i).join("/"),(i?"/":"")+t||"."},t.normalize=function(e){var i=t.isAbsolute(e),s="/"===a(e,-1);return e=r(n(e.split("/"),function(e){return!!e}),!i).join("/"),e||i||(e="."),e&&s&&(e+="/"),(i?"/":"")+e},t.isAbsolute=function(e){return"/"===e.charAt(0)},t.join=function(){var e=Array.prototype.slice.call(arguments,0);return t.normalize(n(e,function(e,t){if("string"!=typeof e)throw new TypeError("Arguments to path.join must be strings");return e}).join("/"))},t.relative=function(e,r){function n(e){for(var t=0;t=0&&""===e[r];r--);return t>r?[]:e.slice(t,r-t+1)}e=t.resolve(e).substr(1),r=t.resolve(r).substr(1);for(var i=n(e.split("/")),s=n(r.split("/")),a=Math.min(i.length,s.length),o=a,u=0;u1?t-1:0),n=1;n=s.length)break;u=s[o++]}else{if(o=s.next(),o.done)break;u=o.value}var l=u;if(b.is(l,n)){i=!0;break}}if(!i)throw new TypeError("Property "+t+" of "+e.type+" expected node to be of a type "+(0,m.default)(r)+" but instead got "+(0,m.default)(n&&n.type))}for(var t=arguments.length,r=Array(t),n=0;n=a.length)break;l=a[u++]}else{if(u=a.next(),u.done)break;l=u.value}var c=l;if(i(n)===c||b.is(c,n)){s=!0;break}}if(!s)throw new TypeError("Property "+t+" of "+e.type+" expected node to be of a type "+(0,m.default)(r)+" but instead got "+(0,m.default)(n&&n.type))}for(var t=arguments.length,r=Array(t),n=0;n=e.length)break;i=e[n++]}else{if(n=e.next(),n.done)break;i=n.value}i.apply(void 0,arguments)}}for(var t=arguments.length,r=Array(t),n=0;n1&&void 0!==arguments[1]?arguments[1]:{},r=t.inherits&&D[t.inherits]||{};t.fields=t.fields||r.fields||{},t.visitor=t.visitor||r.visitor||[],t.aliases=t.aliases||r.aliases||[],t.builder=t.builder||r.builder||t.visitor||[],t.deprecatedAlias&&(_[t.deprecatedAlias]=e);for(var n=t.visitor.concat(t.builder),s=Array.isArray(n),a=0,n=s?n:(0,d.default)(n);;){var o;if(s){if(a>=n.length)break;o=n[a++]}else{if(a=n.next(),a.done)break;o=a.value}var u=o;t.fields[u]=t.fields[u]||{}}for(var c in t.fields){var f=t.fields[c];-1===t.builder.indexOf(c)&&(f.optional=!0),void 0===f.default?f.default=null:f.validate||(f.validate=l(i(f.default)))}E[e]=t.visitor,S[e]=t.builder,A[e]=t.fields,x[e]=t.aliases,D[e]=t}t.__esModule=!0,t.DEPRECATED_KEYS=t.BUILDER_KEYS=t.NODE_FIELDS=t.ALIAS_KEYS=t.VISITOR_KEYS=void 0;var p=r(2),d=n(p),h=r(35),m=n(h),y=r(11),v=n(y);t.assertEach=s,t.assertOneOf=a,t.assertNodeType=o,t.assertNodeOrValueType=u,t.assertValueType=l,t.chain=c,t.default=f;var g=r(1),b=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}(g),E=t.VISITOR_KEYS={},x=t.ALIAS_KEYS={},A=t.NODE_FIELDS={},S=t.BUILDER_KEYS={},_=t.DEPRECATED_KEYS={},D={}},function(e,t){"use strict";e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t){"use strict";var r={}.hasOwnProperty;e.exports=function(e,t){return r.call(e,t)}},function(e,t,r){"use strict";var n=r(23),i=r(92);e.exports=r(22)?function(e,t,r){return n.f(e,t,i(1,r))}:function(e,t,r){return e[t]=r,e}},function(e,t,r){"use strict";function n(e){return null==e?void 0===e?u:o:l&&l in Object(e)?s(e):a(e)}var i=r(45),s=r(534),a=r(559),o="[object Null]",u="[object Undefined]",l=i?i.toStringTag:void 0;e.exports=n},function(e,t,r){"use strict";function n(e,t,r,n){var a=!r;r||(r={});for(var o=-1,u=t.length;++o=s.length)break;u=s[o++]}else{if(o=s.next(),o.done)break;u=o.value}var l=u;if(l.container===t)return l.plugin}var c=void 0;if(c="function"==typeof t?t(b):t,"object"===(void 0===c?"undefined":(0,m.default)(c))){var f=new x.default(c,i);return e.memoisedPlugins.push({container:t,plugin:f}),f}throw new TypeError(S.get("pluginNotObject",r,n,void 0===c?"undefined":(0,m.default)(c))+r+n)},e.createBareOptions=function(){var e={};for(var t in M.default){var r=M.default[t];e[t]=(0,O.default)(r.default)}return e},e.normalisePlugin=function(t,r,n,i){if(!((t=t.__esModule?t.default:t)instanceof x.default)){if("function"!=typeof t&&"object"!==(void 0===t?"undefined":(0,m.default)(t)))throw new TypeError(S.get("pluginNotFunction",r,n,void 0===t?"undefined":(0,m.default)(t)));t=e.memoisePluginContainer(t,r,n,i)}return t.init(r,n),t},e.normalisePlugins=function(t,n,i){return i.map(function(i,s){var a=void 0,o=void 0;if(!i)throw new TypeError("Falsy value found in plugins");Array.isArray(i)?(a=i[0],o=i[1]):a=i;var u="string"==typeof a?a:t+"$"+s;if("string"==typeof a){var l=(0,C.default)(a,n);if(!l)throw new ReferenceError(S.get("pluginUnknown",a,t,s,n));a=r(179)(l)}return a=e.normalisePlugin(a,t,s,u),[a,o]})},e.prototype.mergeOptions=function(t){var r=this,i=t.options,s=t.extending,a=t.alias,o=t.loc,u=t.dirname;if(a=a||"foreign",i){("object"!==(void 0===i?"undefined":(0,m.default)(i))||Array.isArray(i))&&this.log.error("Invalid options type for "+a,TypeError);var l=(0,F.default)(i,function(e){if(e instanceof x.default)return e});u=u||n.cwd(),o=o||a;for(var c in l){if(!M.default[c]&&this.log)if(L.default[c])this.log.error("Using removed Babel 5 option: "+a+"."+c+" - "+L.default[c].message,ReferenceError);else{var p="Unknown option: "+a+"."+c+". Check out http://babeljs.io/docs/usage/options/ for more information about options.";this.log.error(p+"\n\nA common cause of this error is the presence of a configuration options object without the corresponding preset name. Example:\n\nInvalid:\n `{ presets: [{option: value}] }`\nValid:\n `{ presets: [['presetName', {option: value}]] }`\n\nFor more detailed information on preset configuration, please see http://babeljs.io/docs/plugins/#pluginpresets-options.",ReferenceError)}}(0,_.normaliseOptions)(l),l.plugins&&(l.plugins=e.normalisePlugins(o,u,l.plugins)),l.presets&&(l.passPerPreset?l.presets=this.resolvePresets(l.presets,u,function(e,t){r.mergeOptions({options:e,extending:e,alias:t,loc:t,dirname:u})}):(this.mergePresets(l.presets,u),delete l.presets)),i===s?(0,f.default)(s,l):(0,R.default)(s||this.options,l)}},e.prototype.mergePresets=function(e,t){var r=this;this.resolvePresets(e,t,function(e,t){r.mergeOptions({options:e,alias:t,loc:t,dirname:G.default.dirname(t||"")})})},e.prototype.resolvePresets=function(e,t,n){return e.map(function(e){var i=void 0;if(Array.isArray(e)){if(e.length>2)throw new Error("Unexpected extra options "+(0,l.default)(e.slice(2))+" passed to preset.");var s=e;e=s[0],i=s[1]}var a=void 0;try{if("string"==typeof e){if(!(a=(0,P.default)(e,t)))throw new Error("Couldn't find preset "+(0,l.default)(e)+" relative to directory "+(0,l.default)(t));e=r(179)(a)}if("object"===(void 0===e?"undefined":(0,m.default)(e))&&e.__esModule)if(e.default)e=e.default;else{var u=e,c=(u.__esModule,(0,o.default)(u,["__esModule"]));e=c}if("object"===(void 0===e?"undefined":(0,m.default)(e))&&e.buildPreset&&(e=e.buildPreset),"function"!=typeof e&&void 0!==i)throw new Error("Options "+(0,l.default)(i)+" passed to "+(a||"a preset")+" which does not accept options.");if("function"==typeof e&&(e=e(b,i,{dirname:t})),"object"!==(void 0===e?"undefined":(0,m.default)(e)))throw new Error("Unsupported preset format: "+e+".");n&&n(e,a)}catch(e){throw a&&(e.message+=" (While processing preset: "+(0,l.default)(a)+")"),e}return e})},e.prototype.normaliseOptions=function(){var e=this.options;for(var t in M.default){var r=M.default[t],n=e[t];!n&&r.optional||(r.alias?e[r.alias]=e[r.alias]||n:e[t]=n)}},e.prototype.init=function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=(0,U.default)(e,this.log),r=Array.isArray(t),n=0,t=r?t:(0,d.default)(t);;){var i;if(r){if(n>=t.length)break;i=t[n++]}else{if(n=t.next(),n.done)break;i=n.value}var s=i;this.mergeOptions(s)}return this.normaliseOptions(e),this.options},e}();t.default=W,W.memoisedPlugins=[],e.exports=t.default}).call(t,r(8))},function(e,t,r){"use strict";e.exports={default:r(405),__esModule:!0}},function(e,t,r){"use strict";function n(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}function i(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var s=r(2),a=i(s),o=r(3),u=i(o),l=r(224),c=n(l),f=r(239),p=i(f),d=r(466),h=i(d),m=r(7),y=i(m),v=r(174),g=i(v),b=r(134),E=i(b),x=r(1),A=n(x),S=r(88),_=(0,p.default)("babel"),D=function(){function e(t,r){(0,u.default)(this,e),this.parent=r,this.hub=t,this.contexts=[],this.data={},this.shouldSkip=!1,this.shouldStop=!1,this.removed=!1,this.state=null,this.opts=null,this.skipKeys=null,this.parentPath=null,this.context=null,this.container=null,this.listKey=null,this.inList=!1,this.parentKey=null,this.key=null,this.node=null,this.scope=null,this.type=null,this.typeAnnotation=null}return e.get=function(t){var r=t.hub,n=t.parentPath,i=t.parent,s=t.container,a=t.listKey,o=t.key;!r&&n&&(r=n.hub),(0,h.default)(i,"To get a node path the parent needs to exist");var u=s[o],l=S.path.get(i)||[];S.path.has(i)||S.path.set(i,l);for(var c=void 0,f=0;f1&&void 0!==arguments[1]?arguments[1]:SyntaxError;return this.hub.file.buildCodeFrameError(this.node,e,t)},e.prototype.traverse=function(e,t){(0,y.default)(this.node,e,this.scope,t,this)},e.prototype.mark=function(e,t){this.hub.file.metadata.marked.push({type:e,message:t,loc:this.node.loc})},e.prototype.set=function(e,t){A.validate(this.node,e,t),this.node[e]=t},e.prototype.getPathLocation=function(){var e=[],t=this;do{var r=t.key;t.inList&&(r=t.listKey+"["+r+"]"),e.unshift(r)}while(t=t.parentPath);return e.join(".")},e.prototype.debug=function(e){_.enabled&&_(this.getPathLocation()+" "+this.type+": "+e())},e}();t.default=D,(0,g.default)(D.prototype,r(368)),(0,g.default)(D.prototype,r(374)),(0,g.default)(D.prototype,r(382)),(0,g.default)(D.prototype,r(372)),(0,g.default)(D.prototype,r(371)),(0,g.default)(D.prototype,r(377)),(0,g.default)(D.prototype,r(370)),(0,g.default)(D.prototype,r(381)),(0,g.default)(D.prototype,r(380)),(0,g.default)(D.prototype,r(373)),(0,g.default)(D.prototype,r(369));for(var C=A.TYPES,w=Array.isArray(C),P=0,C=w?C:(0,a.default)(C);;){var k;if("break"===function(){if(w){if(P>=C.length)return"break";k=C[P++]}else{if(P=C.next(),P.done)return"break";k=P.value}var e=k,t="is"+e;D.prototype[t]=function(e){return A[t](this.node,e)},D.prototype["assert"+e]=function(r){if(!this[t](r))throw new TypeError("Expected node path of type "+e)}}())break}for(var F in c){(function(e){if("_"===e[0])return"continue";A.TYPES.indexOf(e)<0&&A.TYPES.push(e);var t=c[e];D.prototype["is"+e]=function(e){return t.checkPath(this,e)}})(F)}e.exports=t.default},function(e,t,r){"use strict";var n=r(142),i=r(140);e.exports=function(e){return n(i(e))}},function(e,t,r){"use strict";function n(e,t){var r=s(e,t);return i(r)?r:void 0}var i=r(497),s=r(535);e.exports=n},function(e,t){"use strict";e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children=[],e.webpackPolyfill=1),e}},function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function i(e,t,r,n){if(e.selfReference){if(!n.hasBinding(r.name)||n.hasGlobal(r.name)){if(!f.isFunction(t))return;var i=p;t.generator&&(i=d);var s=i({FUNCTION:t,FUNCTION_ID:r,FUNCTION_KEY:n.generateUidIdentifier(r.name)}).expression;s.callee._skipModulesRemap=!0;for(var a=s.callee.body.body[0].params,u=0,l=(0,o.default)(t);u0&&void 0!==arguments[0]?arguments[0]:{},r=arguments[1];(0,p.default)(this,n);var i=(0,h.default)(this,t.call(this));return i.pipeline=r,i.log=new L.default(i,e.filename||"unknown"),i.opts=i.initOptions(e),i.parserOpts={sourceType:i.opts.sourceType,sourceFileName:i.opts.filename,plugins:[]},i.pluginVisitors=[],i.pluginPasses=[],i.buildPluginsForOptions(i.opts),i.opts.passPerPreset&&(i.perPresetOpts=[],i.opts.presets.forEach(function(e){var t=(0,c.default)((0,u.default)(i.opts),e);i.perPresetOpts.push(t),i.buildPluginsForOptions(t)})),i.metadata={usedHelpers:[],marked:[],modules:{imports:[],exports:{exported:[],specifiers:[]}}},i.dynamicImportTypes={},i.dynamicImportIds={},i.dynamicImports=[],i.declarations={},i.usedHelpers={},i.path=null,i.ast={},i.code="",i.shebang="",i.hub=new w.Hub(i),i}return(0,y.default)(n,t),n.prototype.getMetadata=function(){for(var e=!1,t=this.ast.program.body,r=Array.isArray(t),n=0,t=r?t:(0,a.default)(t);;){var i;if(r){if(n>=t.length)break;i=t[n++]}else{if(n=t.next(),n.done)break;i=n.value}var s=i;if(H.isModuleDeclaration(s)){e=!0;break}}e&&this.path.traverse(E,this)},n.prototype.initOptions=function(e){e=new _.default(this.log,this.pipeline).init(e),e.inputSourceMap&&(e.sourceMaps=!0),e.moduleId&&(e.moduleIds=!0),e.basename=q.default.basename(e.filename,q.default.extname(e.filename)),e.ignore=W.arrayify(e.ignore,W.regexify),e.only&&(e.only=W.arrayify(e.only,W.regexify)),(0,M.default)(e,{moduleRoot:e.sourceRoot}),(0,M.default)(e,{sourceRoot:e.moduleRoot}),(0,M.default)(e,{filenameRelative:e.filename});var t=q.default.basename(e.filenameRelative);return(0,M.default)(e,{sourceFileName:t,sourceMapTarget:t}),e},n.prototype.buildPluginsForOptions=function(e){if(Array.isArray(e.plugins)){for(var t=e.plugins.concat(te),r=[],n=[],i=t,s=Array.isArray(i),o=0,i=s?i:(0,a.default)(i);;){var u;if(s){if(o>=i.length)break;u=i[o++]}else{if(o=i.next(),o.done)break;u=o.value}var l=u,c=l[0],f=l[1];r.push(c.visitor),n.push(new C.default(this,c,f)),c.manipulateOptions&&c.manipulateOptions(e,this.parserOpts,this)}this.pluginVisitors.push(r),this.pluginPasses.push(n)}},n.prototype.getModuleName=function(){var e=this.opts;if(!e.moduleIds)return null;if(null!=e.moduleId&&!e.getModuleId)return e.moduleId;var t=e.filenameRelative,r="";if(null!=e.moduleRoot&&(r=e.moduleRoot+"/"),!e.filenameRelative)return r+e.filename.replace(/^\//,"");if(null!=e.sourceRoot){var n=new RegExp("^"+e.sourceRoot+"/?");t=t.replace(n,"")}return t=t.replace(/\.(\w*?)$/,""),r+=t,r=r.replace(/\\/g,"/"),e.getModuleId?e.getModuleId(r)||r:r},n.prototype.resolveModuleSource=function(e){var t=this.opts.resolveModuleSource;return t&&(e=t(e,this.opts.filename)),e},n.prototype.addImport=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:t,n=e+":"+t,i=this.dynamicImportIds[n];if(!i){e=this.resolveModuleSource(e),i=this.dynamicImportIds[n]=this.scope.generateUidIdentifier(r);var s=[];"*"===t?s.push(H.importNamespaceSpecifier(i)):"default"===t?s.push(H.importDefaultSpecifier(i)):s.push(H.importSpecifier(i,H.identifier(t)));var a=H.importDeclaration(s,H.stringLiteral(e));a._blockHoist=3,this.path.unshiftContainer("body",a)}return i},n.prototype.addHelper=function(e){var t=this.declarations[e];if(t)return t;this.usedHelpers[e]||(this.metadata.usedHelpers.push(e),this.usedHelpers[e]=!0);var r=this.get("helperGenerator"),n=this.get("helpersNamespace");if(r){var i=r(e);if(i)return i}else if(n)return H.memberExpression(n,H.identifier(e));var s=(0,g.default)(e),a=this.declarations[e]=this.scope.generateUidIdentifier(e);return H.isFunctionExpression(s)&&!s.id?(s.body._compact=!0,s._generated=!0,s.id=a,s.type="FunctionDeclaration",this.path.unshiftContainer("body",s)):(s._compact=!0,this.scope.push({id:a,init:s,unique:!0})),a},n.prototype.addTemplateObject=function(e,t,r){var n=r.elements.map(function(e){return e.value}),i=e+"_"+r.elements.length+"_"+n.join(","),s=this.declarations[i];if(s)return s;var a=this.declarations[i]=this.scope.generateUidIdentifier("templateObject"),o=this.addHelper(e),u=H.callExpression(o,[t,r]);return u._compact=!0,this.scope.push({id:a,init:u,_blockHoist:1.9}),a},n.prototype.buildCodeFrameError=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:SyntaxError,n=e&&(e.loc||e._loc),i=new r(t);return n?i.loc=n.start:((0,P.default)(e,re,this.scope,i),i.message+=" (This is an error on an internal node. Probably an internal error",i.loc&&(i.message+=". Location has been estimated."),i.message+=")"),i},n.prototype.mergeSourceMap=function(e){var t=this.opts.inputSourceMap;if(t){var r=new F.default.SourceMapConsumer(t),n=new F.default.SourceMapConsumer(e),i=new F.default.SourceMapGenerator({file:r.file,sourceRoot:r.sourceRoot}),s=n.sources[0];r.eachMapping(function(e){var t=n.generatedPositionFor({line:e.generatedLine,column:e.generatedColumn,source:s});null!=t.column&&i.addMapping({source:e.source,original:null==e.source?null:{line:e.originalLine,column:e.originalColumn},generated:t})});var a=i.toJSON();return t.mappings=a.mappings,t}return e},n.prototype.parse=function(t){var n=V.parse,i=this.opts.parserOpts;if(i&&(i=(0,c.default)({},this.parserOpts,i),i.parser)){if("string"==typeof i.parser){var s=q.default.dirname(this.opts.filename)||e.cwd(),a=(0,X.default)(i.parser,s);if(!a)throw new Error("Couldn't find parser "+i.parser+' with "parse" method relative to directory '+s);n=r(178)(a).parse}else n=i.parser;i.parser={parse:function(e){return(0,V.parse)(e,i)}}}this.log.debug("Parse start");var o=n(t,i||this.parserOpts);return this.log.debug("Parse stop"),o},n.prototype._addAst=function(e){this.path=w.NodePath.get({hub:this.hub,parentPath:null,parent:e,container:e,key:"program"}).setContext(),this.scope=this.path.scope,this.ast=e,this.getMetadata()},n.prototype.addAst=function(e){this.log.debug("Start set AST"),this._addAst(e),this.log.debug("End set AST")},n.prototype.transform=function(){for(var e=0;e=r.length)break;s=r[i++]}else{if(i=r.next(),i.done)break;s=i.value}var o=s,u=o.plugin,l=u[e];l&&l.call(o,this)}},n.prototype.parseInputSourceMap=function(e){var t=this.opts;if(!1!==t.inputSourceMap){var r=A.default.fromSource(e);r&&(t.inputSourceMap=r.toObject(),e=A.default.removeComments(e))}return e},n.prototype.parseShebang=function(){var e=ee.exec(this.code);e&&(this.shebang=e[0],this.code=this.code.replace(ee,""))},n.prototype.makeResult=function(e){var t=e.code,r=e.map,n=e.ast,i=e.ignored,s={metadata:null,options:this.opts,ignored:!!i,code:null,ast:null,map:r||null};return this.opts.code&&(s.code=t),this.opts.ast&&(s.ast=n),this.opts.metadata&&(s.metadata=this.metadata),s},n.prototype.generate=function(){var t=this.opts,n=this.ast,i={ast:n};if(!t.code)return this.makeResult(i);var s=O.default;if(t.generatorOpts.generator&&"string"==typeof(s=t.generatorOpts.generator)){var a=q.default.dirname(this.opts.filename)||e.cwd(),o=(0,X.default)(s,a);if(!o)throw new Error("Couldn't find generator "+s+' with "print" method relative to directory '+a);s=r(178)(o).print}this.log.debug("Generation start");var u=s(n,t.generatorOpts?(0,c.default)(t,t.generatorOpts):t,this.code);return i.code=u.code,i.map=u.map,this.log.debug("Generation end"),this.shebang&&(i.code=this.shebang+"\n"+i.code),i.map&&(i.map=this.mergeSourceMap(i.map)),"inline"!==t.sourceMaps&&"both"!==t.sourceMaps||(i.code+="\n"+A.default.fromObject(i.map).toComment()),"inline"===t.sourceMaps&&(i.map=null),this.makeResult(i)},n}(U.default);t.default=ne,t.File=ne}).call(t,r(8))},function(e,t,r){(function(n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function s(e){var t=x[e];return null==t?x[e]=E.default.existsSync(e):t}function a(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments[1],r=e.filename,n=new S(t);return!1!==e.babelrc&&n.findConfigs(r),n.mergeConfig({options:e,alias:"base",dirname:r&&g.default.dirname(r)}),n.configs}t.__esModule=!0;var o=r(87),u=i(o),l=r(3),c=i(l);t.default=a;var f=r(118),p=i(f),d=r(470),h=i(d),m=r(604),y=i(m),v=r(19),g=i(v),b=r(115),E=i(b),x={},A={},S=function(){function e(t){(0,c.default)(this,e),this.resolvedConfigs=[],this.configs=[],this.log=t}return e.prototype.findConfigs=function(e){if(e){(0,y.default)(e)||(e=g.default.join(n.cwd(),e));for(var t=!1,r=!1;e!==(e=g.default.dirname(e));){if(!t){var i=g.default.join(e,".babelrc");s(i)&&(this.addConfig(i),t=!0);var a=g.default.join(e,"package.json");!t&&s(a)&&(t=this.addConfig(a,"babel",JSON))}if(!r){var o=g.default.join(e,".babelignore");s(o)&&(this.addIgnoreConfig(o),r=!0)}if(r&&t)return}}},e.prototype.addIgnoreConfig=function(e){var t=E.default.readFileSync(e,"utf8"),r=t.split("\n");r=r.map(function(e){return e.replace(/#(.*?)$/,"").trim()}).filter(function(e){return!!e}),r.length&&this.mergeConfig({options:{ignore:r},alias:e,dirname:g.default.dirname(e)})},e.prototype.addConfig=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:h.default;if(this.resolvedConfigs.indexOf(e)>=0)return!1
-;this.resolvedConfigs.push(e);var n=E.default.readFileSync(e,"utf8"),i=void 0;try{i=A[n]=A[n]||r.parse(n),t&&(i=i[t])}catch(t){throw t.message=e+": Error while parsing JSON - "+t.message,t}return this.mergeConfig({options:i,alias:e,dirname:g.default.dirname(e)}),!!i},e.prototype.mergeConfig=function(e){var t=e.options,r=e.alias,i=e.loc,s=e.dirname;if(!t)return!1;if(t=(0,u.default)({},t),s=s||n.cwd(),i=i||r,t.extends){var a=(0,p.default)(t.extends,s);a?this.addConfig(a):this.log&&this.log.error("Couldn't resolve extends clause of "+t.extends+" in "+r),delete t.extends}this.configs.push({options:t,alias:r,loc:i,dirname:s});var o=void 0,l=n.env.BABEL_ENV||"production"||"development";t.env&&(o=t.env[l],delete t.env),this.mergeConfig({options:o,alias:r+".env."+l,dirname:s})},e}();e.exports=t.default}).call(t,r(8))},function(e,t,r){"use strict";function n(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};for(var t in e){var r=e[t];if(null!=r){var n=o.default[t];if(n&&n.alias&&(n=o.default[n.alias]),n){var i=s[n.type];i&&(r=i(r)),e[t]=r}}}return e}t.__esModule=!0,t.config=void 0,t.normaliseOptions=n;var i=r(53),s=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}(i),a=r(33),o=function(e){return e&&e.__esModule?e:{default:e}}(a);t.config=o.default},function(e,t,r){"use strict";function n(e){return!!e}function i(e){return l.booleanify(e)}function s(e){return l.list(e)}t.__esModule=!0,t.filename=void 0,t.boolean=n,t.booleanString=i,t.list=s;var a=r(284),o=function(e){return e&&e.__esModule?e:{default:e}}(a),u=r(122),l=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}(u);t.filename=o.default},function(e,t){"use strict";e.exports={auxiliaryComment:{message:"Use `auxiliaryCommentBefore` or `auxiliaryCommentAfter`"},blacklist:{message:"Put the specific transforms you want in the `plugins` option"},breakConfig:{message:"This is not a necessary option in Babel 6"},experimental:{message:"Put the specific transforms you want in the `plugins` option"},externalHelpers:{message:"Use the `external-helpers` plugin instead. Check out http://babeljs.io/docs/plugins/external-helpers/"},extra:{message:""},jsxPragma:{message:"use the `pragma` option in the `react-jsx` plugin . Check out http://babeljs.io/docs/plugins/transform-react-jsx/"},loose:{message:"Specify the `loose` option for the relevant plugin you are using or use a preset that sets the option."},metadataUsedHelpers:{message:"Not required anymore as this is enabled by default"},modules:{message:"Use the corresponding module transform plugin in the `plugins` option. Check out http://babeljs.io/docs/plugins/#modules"},nonStandard:{message:"Use the `react-jsx` and `flow-strip-types` plugins to support JSX and Flow. Also check out the react preset http://babeljs.io/docs/plugins/preset-react/"},optional:{message:"Put the specific transforms you want in the `plugins` option"},sourceMapName:{message:"Use the `sourceMapTarget` option"},stage:{message:"Check out the corresponding stage-x presets http://babeljs.io/docs/plugins/#presets"},whitelist:{message:"Put the specific transforms you want in the `plugins` option"}}},function(e,t,r){"use strict";var n=r(43),i=r(428),s=r(427),a=r(21),o=r(153),u=r(238),l={},c={},f=e.exports=function(e,t,r,f,p){var d,h,m,y,v=p?function(){return e}:u(e),g=n(r,f,t?2:1),b=0;if("function"!=typeof v)throw TypeError(e+" is not iterable!");if(s(v)){for(d=o(e.length);d>b;b++)if((y=t?g(a(h=e[b])[0],h[1]):g(e[b]))===l||y===c)return y}else for(m=v.call(e);!(h=m.next()).done;)if((y=i(m,g,h.value,t))===l||y===c)return y};f.BREAK=l,f.RETURN=c},function(e,t){"use strict";e.exports={}},function(e,t,r){"use strict";var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},i=r(95)("meta"),s=r(16),a=r(28),o=r(23).f,u=0,l=Object.isExtensible||function(){return!0},c=!r(27)(function(){return l(Object.preventExtensions({}))}),f=function(e){o(e,i,{value:{i:"O"+ ++u,w:{}}})},p=function(e,t){if(!s(e))return"symbol"==(void 0===e?"undefined":n(e))?e:("string"==typeof e?"S":"P")+e;if(!a(e,i)){if(!l(e))return"F";if(!t)return"E";f(e)}return e[i].i},d=function(e,t){if(!a(e,i)){if(!l(e))return!0;if(!t)return!1;f(e)}return e[i].w},h=function(e){return c&&m.NEED&&l(e)&&!a(e,i)&&f(e),e},m=e.exports={KEY:i,NEED:!1,fastKey:p,getWeak:d,onFreeze:h}},function(e,t,r){"use strict";var n=r(16);e.exports=function(e,t){if(!n(e)||e._t!==t)throw TypeError("Incompatible receiver, "+t+" required!");return e}},function(e,t,r){"use strict";r(440);for(var n=r(15),i=r(29),s=r(56),a=r(13)("toStringTag"),o="CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split(","),u=0;u=0;c--)a=u[c],"."===a?u.splice(c,1):".."===a?l++:l>0&&(""===a?(u.splice(c+1,l),l=0):(u.splice(c,2),l--));return r=u.join("/"),""===r&&(r=o?"/":"."),s?(s.path=r,i(s)):r}function a(e,t){""===e&&(e="."),""===t&&(t=".");var r=n(t),a=n(e);if(a&&(e=a.path||"/"),r&&!r.scheme)return a&&(r.scheme=a.scheme),i(r);if(r||t.match(v))return t;if(a&&!a.host&&!a.path)return a.host=t,i(a);var o="/"===t.charAt(0)?t:s(e.replace(/\/+$/,"")+"/"+t);return a?(a.path=o,i(a)):o}function o(e,t){""===e&&(e="."),e=e.replace(/\/$/,"");for(var r=0;0!==t.indexOf(e+"/");){var n=e.lastIndexOf("/");if(n<0)return t;if(e=e.slice(0,n),e.match(/^([^\/]+:\/)?\/*$/))return t;++r}return Array(r+1).join("../")+t.substr(e.length+1)}function u(e){return e}function l(e){return f(e)?"$"+e:e}function c(e){return f(e)?e.slice(1):e}function f(e){if(!e)return!1;var t=e.length;if(t<9)return!1;if(95!==e.charCodeAt(t-1)||95!==e.charCodeAt(t-2)||111!==e.charCodeAt(t-3)||116!==e.charCodeAt(t-4)||111!==e.charCodeAt(t-5)||114!==e.charCodeAt(t-6)||112!==e.charCodeAt(t-7)||95!==e.charCodeAt(t-8)||95!==e.charCodeAt(t-9))return!1;for(var r=t-10;r>=0;r--)if(36!==e.charCodeAt(r))return!1;return!0}function p(e,t,r){var n=e.source-t.source;return 0!==n?n:0!==(n=e.originalLine-t.originalLine)?n:0!==(n=e.originalColumn-t.originalColumn)||r?n:0!==(n=e.generatedColumn-t.generatedColumn)?n:(n=e.generatedLine-t.generatedLine,0!==n?n:e.name-t.name)}function d(e,t,r){var n=e.generatedLine-t.generatedLine;return 0!==n?n:0!==(n=e.generatedColumn-t.generatedColumn)||r?n:0!==(n=e.source-t.source)?n:0!==(n=e.originalLine-t.originalLine)?n:(n=e.originalColumn-t.originalColumn,0!==n?n:e.name-t.name)}function h(e,t){return e===t?0:e>t?1:-1}function m(e,t){var r=e.generatedLine-t.generatedLine;return 0!==r?r:0!==(r=e.generatedColumn-t.generatedColumn)?r:0!==(r=h(e.source,t.source))?r:0!==(r=e.originalLine-t.originalLine)?r:(r=e.originalColumn-t.originalColumn,0!==r?r:h(e.name,t.name))}t.getArg=r;var y=/^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/,v=/^data:.+\,.+$/;t.urlParse=n,t.urlGenerate=i,t.normalize=s,t.join=a,t.isAbsolute=function(e){return"/"===e.charAt(0)||!!e.match(y)},t.relative=o;var g=function(){return!("__proto__"in Object.create(null))}();t.toSetString=g?u:l,t.fromSetString=g?u:c,t.compareByOriginalPositions=p,t.compareByGeneratedPositionsDeflated=d,t.compareByGeneratedPositionsInflated=m},function(e,t,r){(function(t){"use strict";function n(e,t){if(e===t)return 0;for(var r=e.length,n=t.length,i=0,s=Math.min(r,n);i=0;o--)if(u[o]!==l[o])return!1;for(o=u.length-1;o>=0;o--)if(a=u[o],!d(e[a],t[a],r,n))return!1;return!0}function y(e,t,r){d(e,t,!0)&&f(e,t,r,"notDeepStrictEqual",y)}function v(e,t){if(!e||!t)return!1;if("[object RegExp]"==Object.prototype.toString.call(t))return t.test(e);try{if(e instanceof t)return!0}catch(e){}return!Error.isPrototypeOf(t)&&!0===t.call({},e)}function g(e){var t;try{e()}catch(e){t=e}return t}function b(e,t,r,n){var i;if("function"!=typeof t)throw new TypeError('"block" argument must be a function');"string"==typeof r&&(n=r,r=null),i=g(t),n=(r&&r.name?" ("+r.name+").":".")+(n?" "+n:"."),e&&!i&&f(i,r,"Missing expected exception"+n);var s="string"==typeof n,a=!e&&x.isError(i),o=!e&&i&&!r;if((a&&s&&v(i,r)||o)&&f(i,r,"Got unwanted exception"+n),e&&i&&r&&!v(i,r)||!e&&i)throw i}var E="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},x=r(117),A=Object.prototype.hasOwnProperty,S=Array.prototype.slice,_=function(){return"foo"===function(){}.name}(),D=e.exports=p,C=/\s*function\s+([^\(\s]*)\s*/;D.AssertionError=function(e){this.name="AssertionError",this.actual=e.actual,this.expected=e.expected,this.operator=e.operator,e.message?(this.message=e.message,this.generatedMessage=!1):(this.message=c(this),this.generatedMessage=!0);var t=e.stackStartFunction||f;if(Error.captureStackTrace)Error.captureStackTrace(this,t);else{var r=new Error;if(r.stack){var n=r.stack,i=o(t),s=n.indexOf("\n"+i);if(s>=0){var a=n.indexOf("\n",s+1);n=n.substring(a+1)}this.stack=n}}},x.inherits(D.AssertionError,Error),D.fail=f,D.ok=p,D.equal=function(e,t,r){e!=t&&f(e,t,r,"==",D.equal)},D.notEqual=function(e,t,r){e==t&&f(e,t,r,"!=",D.notEqual)},D.deepEqual=function(e,t,r){d(e,t,!1)||f(e,t,r,"deepEqual",D.deepEqual)},D.deepStrictEqual=function(e,t,r){d(e,t,!0)||f(e,t,r,"deepStrictEqual",D.deepStrictEqual)},D.notDeepEqual=function(e,t,r){d(e,t,!1)&&f(e,t,r,"notDeepEqual",D.notDeepEqual)},D.notDeepStrictEqual=y,D.strictEqual=function(e,t,r){e!==t&&f(e,t,r,"===",D.strictEqual)},D.notStrictEqual=function(e,t,r){e===t&&f(e,t,r,"!==",D.notStrictEqual)},D.throws=function(e,t,r){b(!0,e,t,r)},D.doesNotThrow=function(e,t,r){b(!1,e,t,r)},D.ifError=function(e){if(e)throw e};var w=Object.keys||function(e){var t=[];for(var r in e)A.call(e,r)&&t.push(r);return t}}).call(t,function(){return this}())},function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var i=r(2),s=n(i),a=r(3),o=n(a),u=r(42),l=n(u),c=r(41),f=n(c),p=r(34),d=n(p),h=r(20),m=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}(h),y=r(119),v=n(y),g=r(7),b=n(g),E=r(174),x=n(E),A=r(109),S=n(A),_=["enter","exit"],D=function(e){function t(r,n){(0,o.default)(this,t);var i=(0,l.default)(this,e.call(this));return i.initialized=!1,i.raw=(0,x.default)({},r),i.key=i.take("name")||n,i.manipulateOptions=i.take("manipulateOptions"),i.post=i.take("post"),i.pre=i.take("pre"),i.visitor=i.normaliseVisitor((0,S.default)(i.take("visitor"))||{}),i}return(0,f.default)(t,e),t.prototype.take=function(e){var t=this.raw[e];return delete this.raw[e],t},t.prototype.chain=function(e,t){if(!e[t])return this[t];if(!this[t])return e[t];var r=[e[t],this[t]];return function(){for(var e=void 0,t=arguments.length,n=Array(t),i=0;i=a.length)break;l=a[u++]}else{if(u=a.next(),u.done)break;l=u.value}var c=l;if(c){var f=c.apply(this,n);null!=f&&(e=f)}}return e}},t.prototype.maybeInherit=function(e){var t=this.take("inherits");t&&(t=d.default.normalisePlugin(t,e,"inherits"),this.manipulateOptions=this.chain(t,"manipulateOptions"),this.post=this.chain(t,"post"),this.pre=this.chain(t,"pre"),this.visitor=b.default.visitors.merge([t.visitor,this.visitor]))},t.prototype.init=function(e,t){if(!this.initialized){this.initialized=!0,this.maybeInherit(e);for(var r in this.raw)throw new Error(m.get("pluginInvalidProperty",e,t,r))}},t.prototype.normaliseVisitor=function(e){for(var t=_,r=Array.isArray(t),n=0,t=r?t:(0,s.default)(t);;){var i;if(r){if(n>=t.length)break;i=t[n++]}else{if(n=t.next(),n.done)break;i=n.value}if(e[i])throw new Error("Plugins aren't allowed to specify catch-all enter/exit handlers. Please target individual nodes.")}return b.default.explode(e),e},t}(v.default);t.default=D,e.exports=t.default},function(e,t,r){"use strict";t.__esModule=!0;var n=r(2),i=function(e){return e&&e.__esModule?e:{default:e}}(n);t.default=function(e){var t=e.messages;return{visitor:{Scope:function(e){var r=e.scope;for(var n in r.bindings){var s=r.bindings[n];if("const"===s.kind||"module"===s.kind)for(var a=s.constantViolations,o=Array.isArray(a),u=0,a=o?a:(0,i.default)(a);;){var l;if(o){if(u>=a.length)break;l=a[u++]}else{if(u=a.next(),u.done)break;l=u.value}var c=l;throw c.buildCodeFrameError(t.get("readOnly",n))}}}}}},e.exports=t.default},function(e,t){"use strict";t.__esModule=!0,t.default=function(){return{manipulateOptions:function(e,t){t.plugins.push("asyncFunctions")}}},e.exports=t.default},function(e,t){"use strict";t.__esModule=!0,t.default=function(e){var t=e.types;return{visitor:{ArrowFunctionExpression:function(e,r){if(r.opts.spec){var n=e.node;if(n.shadow)return;n.shadow={this:!1},n.type="FunctionExpression";var i=t.thisExpression();i._forceShadow=e,e.ensureBlock(),e.get("body").unshiftContainer("body",t.expressionStatement(t.callExpression(r.addHelper("newArrowCheck"),[t.thisExpression(),i]))),e.replaceWith(t.callExpression(t.memberExpression(n,t.identifier("bind")),[t.thisExpression()]))}else e.arrowFunctionToShadowed()}}}},e.exports=t.default},function(e,t,r){"use strict";t.__esModule=!0;var n=r(2),i=function(e){return e&&e.__esModule?e:{default:e}}(n);t.default=function(e){function t(e,t){for(var n=t.get(e),s=n,a=Array.isArray(s),o=0,s=a?s:(0,i.default)(s);;){var u;if(a){if(o>=s.length)break;u=s[o++]}else{if(o=s.next(),o.done)break;u=o.value}var l=u,c=l.node;if(l.isFunctionDeclaration()){var f=r.variableDeclaration("let",[r.variableDeclarator(c.id,r.toExpression(c))]);f._blockHoist=2,c.id=null,l.replaceWith(f)}}}var r=e.types;return{visitor:{BlockStatement:function(e){var n=e.node,i=e.parent;r.isFunction(i,{body:n})||r.isExportDeclaration(i)||t("body",e)},SwitchCase:function(e){t("consequent",e)}}}},e.exports=t.default},function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function i(e){return b.isLoop(e.parent)||b.isCatchClause(e.parent)}function s(e){return!!b.isVariableDeclaration(e)&&(!!e[b.BLOCK_SCOPED_SYMBOL]||("let"===e.kind||"const"===e.kind))}function a(e,t,r,n){var i=arguments.length>4&&void 0!==arguments[4]&&arguments[4];if(t||(t=e.node),!b.isFor(r))for(var s=0;s0&&e.traverse(P,t),e.skip()}},v.visitor]),P=y.default.visitors.merge([{ReferencedIdentifier:function(e,t){var r=t.letReferences[e.node.name];if(r){var n=e.scope.getBindingIdentifier(e.node.name);n&&n!==r||(t.closurify=!0)}}},v.visitor]),k={enter:function(e,t){var r=e.node;e.parent;if(e.isForStatement()){if(o(r.init)){var n=t.pushDeclar(r.init);1===n.length?r.init=n[0]:r.init=b.sequenceExpression(n)}}else if(e.isFor())o(r.left)&&(t.pushDeclar(r.left),r.left=r.left.declarations[0].id);else if(o(r))e.replaceWithMultiple(t.pushDeclar(r).map(function(e){return b.expressionStatement(e)}));else if(e.isFunction())return e.skip()}},F={LabeledStatement:function(e,t){var r=e.node;t.innerLabels.push(r.label.name)}},T={enter:function(e,t){if(e.isAssignmentExpression()||e.isUpdateExpression()){var r=e.getBindingIdentifiers();for(var n in r)t.outsideReferences[n]===e.scope.getBindingIdentifier(n)&&(t.reassignments[n]=!0)}}},O={Loop:function(e,t){var r=t.ignoreLabeless;t.ignoreLabeless=!0,e.traverse(O,t),t.ignoreLabeless=r,e.skip()},Function:function(e){e.skip()},SwitchCase:function(e,t){var r=t.inSwitchCase;t.inSwitchCase=!0,e.traverse(O,t),t.inSwitchCase=r,e.skip()},"BreakStatement|ContinueStatement|ReturnStatement":function(e,t){var r=e.node,n=e.parent,i=e.scope;if(!r[this.LOOP_IGNORE]){var s=void 0,a=u(r);if(a){if(r.label){if(t.innerLabels.indexOf(r.label.name)>=0)return;a=a+"|"+r.label.name}else{if(t.ignoreLabeless)return;if(t.inSwitchCase)return;if(b.isBreakStatement(r)&&b.isSwitchCase(n))return}t.hasBreakContinue=!0,t.map[a]=r,s=b.stringLiteral(a)}e.isReturnStatement()&&(t.hasReturn=!0,s=b.objectExpression([b.objectProperty(b.identifier("v"),r.argument||i.buildUndefinedNode())])),s&&(s=b.returnStatement(s),s[this.LOOP_IGNORE]=!0,e.skip(),e.replaceWith(b.inherits(s,r)))}}},B=function(){function e(t,r,n,i,s){(0,h.default)(this,e),this.parent=n,this.scope=i,this.file=s,this.blockPath=r,this.block=r.node,this.outsideLetReferences=(0,p.default)(null),this.hasLetReferences=!1,this.letReferences=(0,p.default)(null),this.body=[],t&&(this.loopParent=t.parent,this.loopLabel=b.isLabeledStatement(this.loopParent)&&this.loopParent.label,this.loopPath=t,this.loop=t.node)}return e.prototype.run=function(){var e=this.block;if(!e._letDone){e._letDone=!0;var t=this.getLetReferences();if(b.isFunction(this.parent)||b.isProgram(this.block))return void this.updateScopeInfo();if(this.hasLetReferences)return t?this.wrapClosure():this.remap(),this.updateScopeInfo(t),this.loopLabel&&!b.isLabeledStatement(this.loopParent)?b.labeledStatement(this.loopLabel,this.loop):void 0}},e.prototype.updateScopeInfo=function(e){var t=this.scope,r=t.getFunctionParent(),n=this.letReferences;for(var i in n){var s=n[i],a=t.getBinding(s.name);a&&("let"!==a.kind&&"const"!==a.kind||(a.kind="var",e?t.removeBinding(s.name):t.moveBindingTo(s.name,r)))}},e.prototype.remap=function(){var e=this.letReferences,t=this.scope;for(var r in e){var n=e[r];(t.parentHasBinding(r)||t.hasGlobal(r))&&(t.hasOwnBinding(r)&&t.rename(n.name),this.blockPath.scope.hasOwnBinding(r)&&this.blockPath.scope.rename(n.name))}},e.prototype.wrapClosure=function(){if(this.file.opts.throwIfClosureRequired)throw this.blockPath.buildCodeFrameError("Compiling let/const in this block would add a closure (throwIfClosureRequired).");var e=this.block,t=this.outsideLetReferences;if(this.loop)for(var r in t){var n=t[r];(this.scope.hasGlobal(n.name)||this.scope.parentHasBinding(n.name))&&(delete t[n.name],delete this.letReferences[n.name],this.scope.rename(n.name),this.letReferences[n.name]=n,t[n.name]=n)}this.has=this.checkLoop(),this.hoistVarDeclarations();var i=(0,x.default)(t),s=(0,x.default)(t),a=this.blockPath.isSwitchStatement(),o=b.functionExpression(null,i,b.blockStatement(a?[e]:e.body));o.shadow=!0,this.addContinuations(o);var u=o;this.loop&&(u=this.scope.generateUidIdentifier("loop"),this.loopPath.insertBefore(b.variableDeclaration("var",[b.variableDeclarator(u,o)])));var l=b.callExpression(u,s),c=this.scope.generateUidIdentifier("ret");y.default.hasType(o.body,this.scope,"YieldExpression",b.FUNCTION_TYPES)&&(o.generator=!0,l=b.yieldExpression(l,!0)),y.default.hasType(o.body,this.scope,"AwaitExpression",b.FUNCTION_TYPES)&&(o.async=!0,l=b.awaitExpression(l)),this.buildClosure(c,l),a?this.blockPath.replaceWithMultiple(this.body):e.body=this.body},e.prototype.buildClosure=function(e,t){var r=this.has;r.hasReturn||r.hasBreakContinue?this.buildHas(e,t):this.body.push(b.expressionStatement(t))},e.prototype.addContinuations=function(e){var t={reassignments:{},outsideReferences:this.outsideLetReferences};this.scope.traverse(e,T,t);for(var r=0;r=t.length)break;o=t[a++]}else{if(a=t.next(),a.done)break;o=a.value}var u=o;"get"===u.kind||"set"===u.kind?n(e,u):r(e.objId,u,e.body)}}function a(e){for(var s=e.objId,a=e.body,u=e.computedProps,l=e.state,c=u,f=Array.isArray(c),p=0,c=f?c:(0,i.default)(c);;){var d;if(f){if(p>=c.length)break;d=c[p++]}else{if(p=c.next(),p.done)break;d=p.value}var h=d,m=o.toComputedKey(h);if("get"===h.kind||"set"===h.kind)n(e,h);else if(o.isStringLiteral(m,{value:"__proto__"}))r(s,h,a);else{if(1===u.length)return o.callExpression(l.addHelper("defineProperty"),[e.initPropExpression,m,t(h)]);a.push(o.expressionStatement(o.callExpression(l.addHelper("defineProperty"),[s,m,t(h)])))}}}var o=e.types,u=e.template,l=u("\n MUTATOR_MAP_REF[KEY] = MUTATOR_MAP_REF[KEY] || {};\n MUTATOR_MAP_REF[KEY].KIND = VALUE;\n ");return{visitor:{ObjectExpression:{exit:function(e,t){for(var r=e.node,n=e.parent,u=e.scope,l=!1,c=r.properties,f=Array.isArray(c),p=0,c=f?c:(0,i.default)(c);;){var d;if(f){if(p>=c.length)break;d=c[p++]}else{if(p=c.next(),p.done)break;d=p.value}if(l=!0===d.computed)break}if(l){for(var h=[],m=[],y=!1,v=r.properties,g=Array.isArray(v),b=0,v=g?v:(0,i.default)(v);;){var E;if(g){if(b>=v.length)break;E=v[b++]}else{if(b=v.next(),b.done)break;E=b.value}var x=E;x.computed&&(y=!0),y?m.push(x):h.push(x)}var A=u.generateUidIdentifierBasedOnNode(n),S=o.objectExpression(h),_=[];_.push(o.variableDeclaration("var",[o.variableDeclarator(A,S)]));var D=a;t.opts.loose&&(D=s);var C=void 0,w=function(){return C||(C=u.generateUidIdentifier("mutatorMap"),_.push(o.variableDeclaration("var",[o.variableDeclarator(C,o.objectExpression([]))]))),C},P=D({scope:u,objId:A,body:_,computedProps:m,initPropExpression:S,getMutatorId:w,state:t});C&&_.push(o.expressionStatement(o.callExpression(t.addHelper("defineEnumerableProperties"),[A,C]))),P?e.replaceWith(P):(_.push(o.expressionStatement(A)),e.replaceWithMultiple(_))}}}}}},e.exports=t.default},function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var i=r(3),s=n(i),a=r(2),o=n(a);t.default=function(e){function t(e){for(var t=e.declarations,r=Array.isArray(t),i=0,t=r?t:(0,o.default)(t);;){var s;if(r){if(i>=t.length)break;s=t[i++]}else{if(i=t.next(),i.done)break;s=i.value}var a=s;if(n.isPattern(a.id))return!0}return!1}function r(e){for(var t=e.elements,r=Array.isArray(t),i=0,t=r?t:(0,o.default)(t);;){var s;if(r){if(i>=t.length)break;s=t[i++]}else{if(i=t.next(),i.done)break;s=i.value}var a=s;if(n.isRestElement(a))return!0}return!1}var n=e.types,i={ReferencedIdentifier:function(e,t){t.bindings[e.node.name]&&(t.deopt=!0,e.stop())}},a=function(){function e(t){(0,s.default)(this,e),this.blockHoist=t.blockHoist,this.operator=t.operator,this.arrays={},this.nodes=t.nodes||[],this.scope=t.scope,this.file=t.file,this.kind=t.kind}
-return e.prototype.buildVariableAssignment=function(e,t){var r=this.operator;n.isMemberExpression(e)&&(r="=");var i=void 0;return i=r?n.expressionStatement(n.assignmentExpression(r,e,t)):n.variableDeclaration(this.kind,[n.variableDeclarator(e,t)]),i._blockHoist=this.blockHoist,i},e.prototype.buildVariableDeclaration=function(e,t){var r=n.variableDeclaration("var",[n.variableDeclarator(e,t)]);return r._blockHoist=this.blockHoist,r},e.prototype.push=function(e,t){n.isObjectPattern(e)?this.pushObjectPattern(e,t):n.isArrayPattern(e)?this.pushArrayPattern(e,t):n.isAssignmentPattern(e)?this.pushAssignmentPattern(e,t):this.nodes.push(this.buildVariableAssignment(e,t))},e.prototype.toArray=function(e,t){return this.file.opts.loose||n.isIdentifier(e)&&this.arrays[e.name]?e:this.scope.toArray(e,t)},e.prototype.pushAssignmentPattern=function(e,t){var r=this.scope.generateUidIdentifierBasedOnNode(t),i=n.variableDeclaration("var",[n.variableDeclarator(r,t)]);i._blockHoist=this.blockHoist,this.nodes.push(i);var s=n.conditionalExpression(n.binaryExpression("===",r,n.identifier("undefined")),e.right,r),a=e.left;if(n.isPattern(a)){var o=n.expressionStatement(n.assignmentExpression("=",r,s));o._blockHoist=this.blockHoist,this.nodes.push(o),this.push(a,r)}else this.nodes.push(this.buildVariableAssignment(a,s))},e.prototype.pushObjectRest=function(e,t,r,i){for(var s=[],a=0;a=i)break;if(!n.isRestProperty(o)){var u=o.key;n.isIdentifier(u)&&!o.computed&&(u=n.stringLiteral(o.key.name)),s.push(u)}}s=n.arrayExpression(s);var l=n.callExpression(this.file.addHelper("objectWithoutProperties"),[t,s]);this.nodes.push(this.buildVariableAssignment(r.argument,l))},e.prototype.pushObjectProperty=function(e,t){n.isLiteral(e.key)&&(e.computed=!0);var r=e.value,i=n.memberExpression(t,e.key,e.computed);n.isPattern(r)?this.push(r,i):this.nodes.push(this.buildVariableAssignment(r,i))},e.prototype.pushObjectPattern=function(e,t){if(e.properties.length||this.nodes.push(n.expressionStatement(n.callExpression(this.file.addHelper("objectDestructuringEmpty"),[t]))),e.properties.length>1&&!this.scope.isStatic(t)){var r=this.scope.generateUidIdentifierBasedOnNode(t);this.nodes.push(this.buildVariableDeclaration(r,t)),t=r}for(var i=0;it.elements.length)){if(e.elements.length=s.length)break;l=s[u++]}else{if(u=s.next(),u.done)break;l=u.value}var c=l;if(!c)return!1;if(n.isMemberExpression(c))return!1}for(var f=t.elements,p=Array.isArray(f),d=0,f=p?f:(0,o.default)(f);;){var h;if(p){if(d>=f.length)break;h=f[d++]}else{if(d=f.next(),d.done)break;h=d.value}var m=h;if(n.isSpreadElement(m))return!1;if(n.isCallExpression(m))return!1;if(n.isMemberExpression(m))return!1}var y=n.getBindingIdentifiers(e),v={deopt:!1,bindings:y};return this.scope.traverse(t,i,v),!v.deopt}},e.prototype.pushUnpackedArrayPattern=function(e,t){for(var r=0;r=y.length)break;b=y[g++]}else{if(g=y.next(),g.done)break;b=g.value}var E=b,x=m[m.length-1];if(x&&n.isVariableDeclaration(x)&&n.isVariableDeclaration(E)&&x.kind===E.kind){var A;(A=x.declarations).push.apply(A,E.declarations)}else m.push(E)}for(var S=m,_=Array.isArray(S),D=0,S=_?S:(0,o.default)(S);;){var C;if(_){if(D>=S.length)break;C=S[D++]}else{if(D=S.next(),D.done)break;C=D.value}var w=C;if(w.declarations)for(var P=w.declarations,k=Array.isArray(P),F=0,P=k?P:(0,o.default)(P);;){var T;if(k){if(F>=P.length)break;T=P[F++]}else{if(F=P.next(),F.done)break;T=F.value}var O=T,B=O.id.name;s.bindings[B]&&(s.bindings[B].kind=w.kind)}}1===m.length?e.replaceWith(m[0]):e.replaceWithMultiple(m)}}}}},e.exports=t.default},function(e,t){"use strict";t.__esModule=!0,t.default=function(e){function t(e){var t=e.node,r=e.scope,n=[],i=t.right;if(!a.isIdentifier(i)||!r.hasBinding(i.name)){var s=r.generateUidIdentifier("arr");n.push(a.variableDeclaration("var",[a.variableDeclarator(s,i)])),i=s}var u=r.generateUidIdentifier("i"),l=o({BODY:t.body,KEY:u,ARR:i});a.inherits(l,t),a.ensureBlock(l);var c=a.memberExpression(i,u,!0),f=t.left;return a.isVariableDeclaration(f)?(f.declarations[0].init=c,l.body.body.unshift(f)):l.body.body.unshift(a.expressionStatement(a.assignmentExpression("=",f,c))),e.parentPath.isLabeledStatement()&&(l=a.labeledStatement(e.parentPath.node.label,l)),n.push(l),n}function r(e,t){var r=e.node,n=e.scope,s=e.parent,o=r.left,l=void 0,c=void 0;if(a.isIdentifier(o)||a.isPattern(o)||a.isMemberExpression(o))c=o;else{if(!a.isVariableDeclaration(o))throw t.buildCodeFrameError(o,i.get("unknownForHead",o.type));c=n.generateUidIdentifier("ref"),l=a.variableDeclaration(o.kind,[a.variableDeclarator(o.declarations[0].id,c)])}var f=n.generateUidIdentifier("iterator"),p=n.generateUidIdentifier("isArray"),d=u({LOOP_OBJECT:f,IS_ARRAY:p,OBJECT:r.right,INDEX:n.generateUidIdentifier("i"),ID:c});l||d.body.body.shift();var h=a.isLabeledStatement(s),m=void 0;return h&&(m=a.labeledStatement(s.label,d)),{replaceParent:h,declar:l,node:m||d,loop:d}}function n(e,t){var r=e.node,n=e.scope,s=e.parent,o=r.left,u=void 0,c=n.generateUidIdentifier("step"),f=a.memberExpression(c,a.identifier("value"));if(a.isIdentifier(o)||a.isPattern(o)||a.isMemberExpression(o))u=a.expressionStatement(a.assignmentExpression("=",o,f));else{if(!a.isVariableDeclaration(o))throw t.buildCodeFrameError(o,i.get("unknownForHead",o.type));u=a.variableDeclaration(o.kind,[a.variableDeclarator(o.declarations[0].id,f)])}var p=n.generateUidIdentifier("iterator"),d=l({ITERATOR_HAD_ERROR_KEY:n.generateUidIdentifier("didIteratorError"),ITERATOR_COMPLETION:n.generateUidIdentifier("iteratorNormalCompletion"),ITERATOR_ERROR_KEY:n.generateUidIdentifier("iteratorError"),ITERATOR_KEY:p,STEP_KEY:c,OBJECT:r.right,BODY:null}),h=a.isLabeledStatement(s),m=d[3].block.body,y=m[0];return h&&(m[0]=a.labeledStatement(s.label,y)),{replaceParent:h,declar:u,loop:y,node:d}}var i=e.messages,s=e.template,a=e.types,o=s("\n for (var KEY = 0; KEY < ARR.length; KEY++) BODY;\n "),u=s("\n for (var LOOP_OBJECT = OBJECT,\n IS_ARRAY = Array.isArray(LOOP_OBJECT),\n INDEX = 0,\n LOOP_OBJECT = IS_ARRAY ? LOOP_OBJECT : LOOP_OBJECT[Symbol.iterator]();;) {\n var ID;\n if (IS_ARRAY) {\n if (INDEX >= LOOP_OBJECT.length) break;\n ID = LOOP_OBJECT[INDEX++];\n } else {\n INDEX = LOOP_OBJECT.next();\n if (INDEX.done) break;\n ID = INDEX.value;\n }\n }\n "),l=s("\n var ITERATOR_COMPLETION = true;\n var ITERATOR_HAD_ERROR_KEY = false;\n var ITERATOR_ERROR_KEY = undefined;\n try {\n for (var ITERATOR_KEY = OBJECT[Symbol.iterator](), STEP_KEY; !(ITERATOR_COMPLETION = (STEP_KEY = ITERATOR_KEY.next()).done); ITERATOR_COMPLETION = true) {\n }\n } catch (err) {\n ITERATOR_HAD_ERROR_KEY = true;\n ITERATOR_ERROR_KEY = err;\n } finally {\n try {\n if (!ITERATOR_COMPLETION && ITERATOR_KEY.return) {\n ITERATOR_KEY.return();\n }\n } finally {\n if (ITERATOR_HAD_ERROR_KEY) {\n throw ITERATOR_ERROR_KEY;\n }\n }\n }\n ");return{visitor:{ForOfStatement:function(e,i){if(e.get("right").isArrayExpression())return e.parentPath.isLabeledStatement()?e.parentPath.replaceWithMultiple(t(e)):e.replaceWithMultiple(t(e));var s=n;i.opts.loose&&(s=r);var o=e.node,u=s(e,i),l=u.declar,c=u.loop,f=c.body;e.ensureBlock(),l&&f.body.push(l),f.body=f.body.concat(o.body.body),a.inherits(c,o),a.inherits(c.body,o.body),u.replaceParent?(e.parentPath.replaceWithMultiple(u.node),e.remove()):e.replaceWithMultiple(u.node)}}}},e.exports=t.default},function(e,t,r){"use strict";t.__esModule=!0,t.default=function(){return{visitor:{FunctionExpression:{exit:function(e){if("value"!==e.key&&!e.parentPath.isObjectProperty()){var t=(0,i.default)(e);t&&e.replaceWith(t)}}},ObjectProperty:function(e){var t=e.get("value");if(t.isFunction()){var r=(0,i.default)(t);r&&t.replaceWith(r)}}}}};var n=r(40),i=function(e){return e&&e.__esModule?e:{default:e}}(n);e.exports=t.default},function(e,t){"use strict";t.__esModule=!0,t.default=function(){return{visitor:{NumericLiteral:function(e){var t=e.node;t.extra&&/^0[ob]/i.test(t.extra.raw)&&(t.extra=void 0)},StringLiteral:function(e){var t=e.node;t.extra&&/\\[u]/gi.test(t.extra.raw)&&(t.extra=void 0)}}}},e.exports=t.default},function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var i=r(14),s=n(i),a=r(9),o=n(a),u=r(2),l=n(u),c=r(10),f=n(c);t.default=function(){var e=(0,f.default)(),t={ReferencedIdentifier:function(e){var t=e.node.name,r=this.remaps[t];if(r&&this.scope.getBinding(t)===e.scope.getBinding(t)){if(e.parentPath.isCallExpression({callee:e.node}))e.replaceWith(g.sequenceExpression([g.numericLiteral(0),r]));else if(e.isJSXIdentifier()&&g.isMemberExpression(r)){var n=r.object,i=r.property;e.replaceWith(g.JSXMemberExpression(g.JSXIdentifier(n.name),g.JSXIdentifier(i.name)))}else e.replaceWith(r);this.requeueInParent(e)}},AssignmentExpression:function(t){var r=t.node;if(!r[e]){var n=t.get("left");if(n.isIdentifier()){var i=n.node.name,s=this.exports[i];if(!s)return;if(this.scope.getBinding(i)!==t.scope.getBinding(i))return;r[e]=!0;for(var a=s,o=Array.isArray(a),u=0,a=o?a:(0,l.default)(a);;){var c;if(o){if(u>=a.length)break;c=a[u++]}else{if(u=a.next(),u.done)break;c=u.value}r=S(c,r).expression}t.replaceWith(r),this.requeueInParent(t)}else if(n.isObjectPattern())for(var f=n.node.properties,p=Array.isArray(f),d=0,f=p?f:(0,l.default)(f);;){var h;if(p){if(d>=f.length)break;h=f[d++]}else{if(d=f.next(),d.done)break;h=d.value}var m=h,y=m.value.name,v=this.exports[y];if(v){if(this.scope.getBinding(y)!==t.scope.getBinding(y))return;r[e]=!0,t.insertAfter(S(g.identifier(y),g.identifier(y)))}}else if(n.isArrayPattern())for(var b=n.node.elements,E=Array.isArray(b),x=0,b=E?b:(0,l.default)(b);;){var A;if(E){if(x>=b.length)break;A=b[x++]}else{if(x=b.next(),x.done)break;A=x.value}var _=A;if(_){var D=_.name,C=this.exports[D];if(C){if(this.scope.getBinding(D)!==t.scope.getBinding(D))return;r[e]=!0,t.insertAfter(S(g.identifier(D),g.identifier(D)))}}}}},UpdateExpression:function(e){var t=e.get("argument");if(t.isIdentifier()){var r=t.node.name;if(this.exports[r]&&this.scope.getBinding(r)===e.scope.getBinding(r)){var n=g.assignmentExpression(e.node.operator[0]+"=",t.node,g.numericLiteral(1));if(e.parentPath.isExpressionStatement()&&!e.isCompletionRecord()||e.node.prefix)return e.replaceWith(n),void this.requeueInParent(e);var i=[];i.push(n);var s=void 0;s="--"===e.node.operator?"+":"-",i.push(g.binaryExpression(s,t.node,g.numericLiteral(1))),e.replaceWithMultiple(g.sequenceExpression(i))}}}};return{inherits:y.default,visitor:{ThisExpression:function(e,t){this.ranCommonJS||!0===t.opts.allowTopLevelThis||e.findParent(function(e){return!e.is("shadow")&&D.indexOf(e.type)>=0})||e.replaceWith(g.identifier("undefined"))},Program:{exit:function(e){function r(t,r){var n=C[t];if(n)return n;var i=e.scope.generateUidIdentifier((0,p.basename)(t,(0,p.extname)(t))),s=g.variableDeclaration("var",[g.variableDeclarator(i,b(g.stringLiteral(t)).expression)]);return h[t]&&(s.loc=h[t].loc),"number"==typeof r&&r>0&&(s._blockHoist=r),v.push(s),C[t]=i}function n(e,t,r){var n=e[t]||[];e[t]=n.concat(r)}this.ranCommonJS=!0;var i=!!this.opts.strict,a=!!this.opts.noInterop,u=e.scope;u.rename("module"),u.rename("exports"),u.rename("require");for(var c=!1,f=!1,d=e.get("body"),h=(0,o.default)(null),m=(0,o.default)(null),y=(0,o.default)(null),v=[],D=(0,o.default)(null),C=(0,o.default)(null),w=d,P=Array.isArray(w),k=0,w=P?w:(0,l.default)(w);;){var F;if(P){if(k>=w.length)break;F=w[k++]}else{if(k=w.next(),k.done)break;F=k.value}var T=F;if(T.isExportDeclaration()){c=!0;for(var O=[].concat(T.get("declaration"),T.get("specifiers")),B=O,R=Array.isArray(B),I=0,B=R?B:(0,l.default)(B);;){var M;if(R){if(I>=B.length)break;M=B[I++]}else{if(I=B.next(),I.done)break;M=I.value}var N=M;if(N.getBindingIdentifiers().__esModule)throw N.buildCodeFrameError('Illegal export "__esModule"')}}if(T.isImportDeclaration()){var L;f=!0;var j=T.node.source.value,U=h[j]||{specifiers:[],maxBlockHoist:0,loc:T.node.loc};(L=U.specifiers).push.apply(L,T.node.specifiers),"number"==typeof T.node._blockHoist&&(U.maxBlockHoist=Math.max(T.node._blockHoist,U.maxBlockHoist)),h[j]=U,T.remove()}else if(T.isExportDefaultDeclaration()){var V=T.get("declaration");if(V.isFunctionDeclaration()){var G=V.node.id,W=g.identifier("default");G?(n(m,G.name,W),v.push(S(W,G)),T.replaceWith(V.node)):(v.push(S(W,g.toExpression(V.node))),T.remove())}else if(V.isClassDeclaration()){var Y=V.node.id,q=g.identifier("default");Y?(n(m,Y.name,q),T.replaceWithMultiple([V.node,S(q,Y)])):(T.replaceWith(S(q,g.toExpression(V.node))),T.parentPath.requeue(T.get("expression.left")))}else T.replaceWith(S(g.identifier("default"),V.node)),T.parentPath.requeue(T.get("expression.left"))}else if(T.isExportNamedDeclaration()){var K=T.get("declaration");if(K.node){if(K.isFunctionDeclaration()){var H=K.node.id;n(m,H.name,H),v.push(S(H,H)),T.replaceWith(K.node)}else if(K.isClassDeclaration()){var J=K.node.id;n(m,J.name,J),T.replaceWithMultiple([K.node,S(J,J)]),y[J.name]=!0}else if(K.isVariableDeclaration()){for(var X=K.get("declarations"),z=X,$=Array.isArray(z),Q=0,z=$?z:(0,l.default)(z);;){var Z;if($){if(Q>=z.length)break;Z=z[Q++]}else{if(Q=z.next(),Q.done)break;Z=Q.value}var ee=Z,te=ee.get("id"),re=ee.get("init"),ne=[];if(re.node||re.replaceWith(g.identifier("undefined")),te.isIdentifier())n(m,te.node.name,te.node),re.replaceWith(S(te.node,re.node).expression),y[te.node.name]=!0;else if(te.isObjectPattern())for(var ie=0;ie=he.length)break;ve=he[ye++]}else{if(ye=he.next(),ye.done)break;ve=ye.value}var ge=ve;ge.isExportNamespaceSpecifier()||ge.isExportDefaultSpecifier()||ge.isExportSpecifier()&&(a||"default"!==ge.node.local.name?v.push(x(g.stringLiteral(ge.node.exported.name),g.memberExpression(de,ge.node.local))):v.push(x(g.stringLiteral(ge.node.exported.name),g.memberExpression(g.callExpression(this.addHelper("interopRequireDefault"),[de]),ge.node.local))),y[ge.node.exported.name]=!0)}else for(var be=ce,Ee=Array.isArray(be),xe=0,be=Ee?be:(0,l.default)(be);;){var Ae;if(Ee){if(xe>=be.length)break;Ae=be[xe++]}else{if(xe=be.next(),xe.done)break;Ae=xe.value}var Se=Ae;Se.isExportSpecifier()&&(n(m,Se.node.local.name,Se.node.exported),y[Se.node.exported.name]=!0,fe.push(S(Se.node.exported,Se.node.local)))}T.replaceWithMultiple(fe)}else if(T.isExportAllDeclaration()){var _e=_({OBJECT:r(T.node.source.value,T.node._blockHoist)});_e.loc=T.node.loc,v.push(_e),T.remove()}}for(var De in h){var Ce=h[De],O=Ce.specifiers,we=Ce.maxBlockHoist;if(O.length){for(var Pe=r(De,we),ke=void 0,Fe=0;Fe0&&(Oe._blockHoist=we),v.push(Oe)}ke=Te.local}else g.isImportDefaultSpecifier(Te)&&(O[Fe]=g.importSpecifier(Te.local,g.identifier("default")))}for(var Be=O,Re=Array.isArray(Be),Ie=0,Be=Re?Be:(0,l.default)(Be);;){var Me;if(Re){if(Ie>=Be.length)break;Me=Be[Ie++]}else{if(Ie=Be.next(),Ie.done)break;Me=Ie.value}var Ne=Me;if(g.isImportSpecifier(Ne)){var Le=Pe;if("default"===Ne.imported.name)if(ke)Le=ke;else if(!a){Le=ke=e.scope.generateUidIdentifier(Pe.name);var je=g.variableDeclaration("var",[g.variableDeclarator(Le,g.callExpression(this.addHelper("interopRequireDefault"),[Pe]))]);we>0&&(je._blockHoist=we),v.push(je)}D[Ne.local.name]=g.memberExpression(Le,g.cloneWithoutLoc(Ne.imported))}}}else{var Ue=b(g.stringLiteral(De));Ue.loc=h[De].loc,v.push(Ue)}}if(f&&(0,s.default)(y).length)for(var Ve=(0,s.default)(y),Ge=0;Ge=l.length)break;p=l[f++]}else{if(f=l.next(),f.done)break;p=f.value}var d=p;d.isObjectProperty()&&(d=d.get("value")),t(d,d.node,e.scope,o,i)}a&&(e.scope.push({id:a}),e.replaceWith(r.assignmentExpression("=",a,e.node)))}}}}}};var u=r(193),l=n(u);e.exports=t.default},function(e,t,r){"use strict";function n(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}t.__esModule=!0;var i=r(2),s=function(e){return e&&e.__esModule?e:{default:e}}(i);t.default=function(){return{visitor:a.visitors.merge([{ArrowFunctionExpression:function(e){for(var t=e.get("params"),r=t,n=Array.isArray(r),i=0,r=n?r:(0,s.default)(r);;){var a;if(n){if(i>=r.length)break;a=r[i++]}else{if(i=r.next(),i.done)break;a=i.value}var o=a;if(o.isRestElement()||o.isAssignmentPattern()){e.arrowFunctionToShadowed();break}}}},u.visitor,p.visitor,c.visitor])}};var a=r(7),o=r(334),u=n(o),l=r(333),c=n(l),f=r(335),p=n(f);e.exports=t.default},function(e,t,r){"use strict";t.__esModule=!0,t.default=function(){return{visitor:{ObjectMethod:function(e){var t=e.node;if("method"===t.kind){var r=i.functionExpression(null,t.params,t.body,t.generator,t.async);r.returnType=t.returnType,e.replaceWith(i.objectProperty(t.key,r,t.computed))}},ObjectProperty:function(e){var t=e.node;t.shorthand&&(t.shorthand=!1)}}}};var n=r(1),i=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}(n);e.exports=t.default},function(e,t,r){"use strict";t.__esModule=!0;var n=r(2),i=function(e){return e&&e.__esModule?e:{default:e}}(n);t.default=function(e){function t(e,t,r){return r.opts.loose&&!s.isIdentifier(e.argument,{name:"arguments"})?e.argument:t.toArray(e.argument,!0)}function r(e){for(var t=0;t=l.length)break;p=l[f++]}else{if(f=l.next(),f.done)break;p=f.value}var d=p;s.isSpreadElement(d)?(a(),o.push(t(d,r,n))):u.push(d)}return a(),o}var s=e.types;return{visitor:{ArrayExpression:function(e,t){var i=e.node,a=e.scope,o=i.elements;if(r(o)){var u=n(o,a,t),l=u.shift();s.isArrayExpression(l)||(u.unshift(l),l=s.arrayExpression([])),e.replaceWith(s.callExpression(s.memberExpression(l,s.identifier("concat")),u))}},CallExpression:function(e,t){var i=e.node,a=e.scope,o=i.arguments;if(r(o)){var u=e.get("callee");if(!u.isSuper()){var l=s.identifier("undefined");i.arguments=[];var c=void 0;c=1===o.length&&"arguments"===o[0].argument.name?[o[0].argument]:n(o,a,t);var f=c.shift();c.length?i.arguments.push(s.callExpression(s.memberExpression(f,s.identifier("concat")),c)):i.arguments.push(f);var p=i.callee;if(u.isMemberExpression()){var d=a.maybeGenerateMemoised(p.object);d?(p.object=s.assignmentExpression("=",d,p.object),l=d):l=p.object,s.appendToMemberExpression(p,s.identifier("apply"))}else i.callee=s.memberExpression(i.callee,s.identifier("apply"));s.isSuper(l)&&(l=s.thisExpression()),i.arguments.unshift(l)}}},NewExpression:function(e,t){var i=e.node,a=e.scope,o=i.arguments;if(r(o)){var u=n(o,a,t),l=s.arrayExpression([s.nullLiteral()]);o=s.callExpression(s.memberExpression(l,s.identifier("concat")),u),e.replaceWith(s.newExpression(s.callExpression(s.memberExpression(s.memberExpression(s.memberExpression(s.identifier("Function"),s.identifier("prototype")),s.identifier("bind")),s.identifier("apply")),[i.callee,o]),[]))}}}}},e.exports=t.default},function(e,t,r){"use strict";function n(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}t.__esModule=!0,t.default=function(){return{visitor:{RegExpLiteral:function(e){var t=e.node;s.is(t,"y")&&e.replaceWith(o.newExpression(o.identifier("RegExp"),[o.stringLiteral(t.pattern),o.stringLiteral(t.flags)]))}}}};var i=r(192),s=n(i),a=r(1),o=n(a);e.exports=t.default},function(e,t,r){"use strict";t.__esModule=!0;var n=r(2),i=function(e){return e&&e.__esModule?e:{default:e}}(n);t.default=function(e){function t(e){return n.isLiteral(e)&&"string"==typeof e.value}function r(e,t){return n.binaryExpression("+",e,t)}var n=e.types;return{visitor:{TaggedTemplateExpression:function(e,t){for(var r=e.node,s=r.quasi,a=[],o=[],u=[],l=s.quasis,c=Array.isArray(l),f=0,l=c?l:(0,i.default)(l);;){var p;if(c){if(f>=l.length)break;p=l[f++]}else{if(f=l.next(),f.done)break;p=f.value}var d=p;o.push(n.stringLiteral(d.value.cooked)),u.push(n.stringLiteral(d.value.raw))}o=n.arrayExpression(o),u=n.arrayExpression(u);var h="taggedTemplateLiteral";t.opts.loose&&(h+="Loose");var m=t.file.addTemplateObject(h,o,u);a.push(m),a=a.concat(s.expressions),e.replaceWith(n.callExpression(r.tag,a))},TemplateLiteral:function(e,s){for(var a=[],o=e.get("expressions"),u=e.node.quasis,l=Array.isArray(u),c=0,u=l?u:(0,i.default)(u);;){var f;if(l){if(c>=u.length)break;f=u[c++]}else{if(c=u.next(),c.done)break;f=c.value}var p=f;a.push(n.stringLiteral(p.value.cooked));var d=o.shift();d&&(!s.opts.spec||d.isBaseType("string")||d.isBaseType("number")?a.push(d.node):a.push(n.callExpression(n.identifier("String"),[d.node])))}if(a=a.filter(function(e){return!n.isLiteral(e,{value:""})}),t(a[0])||t(a[1])||a.unshift(n.stringLiteral("")),a.length>1){for(var h=r(a.shift(),a.shift()),m=a,y=Array.isArray(m),v=0,m=y?m:(0,i.default)(m);;){var g;if(y){if(v>=m.length)break;g=m[v++]}else{if(v=m.next(),v.done)break;g=v.value}h=r(h,g)}e.replaceWith(h)}else e.replaceWith(a[0])}}}},e.exports=t.default},function(e,t,r){"use strict";t.__esModule=!0;var n=r(10),i=function(e){return e&&e.__esModule?e:{default:e}}(n);t.default=function(e){var t=e.types,r=(0,i.default)();return{visitor:{Scope:function(e){var t=e.scope;t.getBinding("Symbol")&&t.rename("Symbol")},UnaryExpression:function(e){var n=e.node,i=e.parent;if(!n[r]&&!e.find(function(e){return e.node&&!!e.node._generated})){if(e.parentPath.isBinaryExpression()&&t.EQUALITY_BINARY_OPERATORS.indexOf(i.operator)>=0){var s=e.getOpposite();if(s.isLiteral()&&"symbol"!==s.node.value&&"object"!==s.node.value)return}if("typeof"===n.operator){var a=t.callExpression(this.addHelper("typeof"),[n.argument]);if(e.get("argument").isIdentifier()){var o=t.stringLiteral("undefined"),u=t.unaryExpression("typeof",n.argument);u[r]=!0,e.replaceWith(t.conditionalExpression(t.binaryExpression("===",u,o),o,a))}else e.replaceWith(a)}}}}}},e.exports=t.default},function(e,t,r){"use strict";t.__esModule=!0,t.default=function(){return{visitor:{RegExpLiteral:function(e){var t=e.node;a.is(t,"u")&&(t.pattern=(0,i.default)(t.pattern,t.flags),a.pullFlag(t,"u"))}}}};var n=r(612),i=function(e){return e&&e.__esModule?e:{default:e}}(n),s=r(192),a=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}(s);e.exports=t.default},function(e,t,r){"use strict";e.exports=r(606)},function(e,t,r){"use strict";e.exports={default:r(408),__esModule:!0}},function(e,t,r){"use strict";function n(){i(),s()}function i(){t.path=u=new o.default}function s(){t.scope=l=new o.default}t.__esModule=!0,t.scope=t.path=void 0;var a=r(364),o=function(e){return e&&e.__esModule?e:{default:e}}(a);t.clear=n,t.clearPath=i,t.clearScope=s;var u=t.path=new o.default,l=t.scope=new o.default},function(e,t){"use strict";function r(e){return e=e.split(" "),function(t){return e.indexOf(t)>=0}}function n(e,t){for(var r=65536,n=0;ne)return!1;if((r+=t[n+1])>=e)return!0}}function i(e){return e<65?36===e:e<91||(e<97?95===e:e<123||(e<=65535?e>=170&&x.test(String.fromCharCode(e)):n(e,S)))}function s(e){return e<48?36===e:e<58||!(e<65)&&(e<91||(e<97?95===e:e<123||(e<=65535?e>=170&&A.test(String.fromCharCode(e)):n(e,S)||n(e,_))))}function a(e){var t={};for(var r in D)t[r]=e&&r in e?e[r]:D[r];return t}function o(e){return 10===e||13===e||8232===e||8233===e}function u(e,t){for(var r=1,n=0;;){N.lastIndex=n;var i=N.exec(e);if(!(i&&i.index>10),56320+(e-65536&1023))}function c(e,t,r,n){return e.type=t,e.end=r,e.loc.end=n,this.processComment(e),e}function f(e){return e[e.length-1]}function p(e){return e&&"Property"===e.type&&"init"===e.kind&&!1===e.method}function d(e){return"JSXIdentifier"===e.type?e.name:"JSXNamespacedName"===e.type?e.namespace.name+":"+e.name.name:"JSXMemberExpression"===e.type?d(e.object)+"."+d(e.property):void 0}function h(e,t){return new J(t,e).parse()}function m(e,t){var r=new J(t,e);return r.options.strictMode&&(r.state.strict=!0),r.getExpression()}var y="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};Object.defineProperty(t,"__esModule",{value:!0});var v={6:r("enum await"),strict:r("implements interface let package private protected public static yield"),strictBind:r("eval arguments")
-},g=r("break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this let const class extends export import yield super"),b="ªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠ-ࢴࢶ-ࢽऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡૹଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘ-ౚౠౡಀಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൔ-ൖൟ-ൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏽᏸ-ᏽᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛮ-ᛸᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᲀ-ᲈᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕ℘-ℝℤΩℨK-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞ々-〇〡-〩〱-〵〸-〼ぁ-ゖ゛-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿕ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛯꜗ-ꜟꜢ-ꞈꞋ-ꞮꞰ-ꞷꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꣽꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭥꭰ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ",E="·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-٩ٰۖ-ۜ۟-۪ۤۧۨ-ۭ۰-۹ܑܰ-݊ަ-ް߀-߉߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣔ-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣ०-९ঁ-ঃ়া-ৄেৈো-্ৗৢৣ০-৯ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑ੦-ੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣ૦-૯ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣ୦-୯ஂா-ூெ-ைொ-்ௗ௦-௯ఀ-ఃా-ౄె-ైొ-్ౕౖౢౣ౦-౯ಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣ೦-೯ഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣ൦-൯ංඃ්ා-ුූෘ-ෟ෦-෯ෲෳัิ-ฺ็-๎๐-๙ັິ-ູົຼ່-ໍ໐-໙༘༙༠-༩༹༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှ၀-၉ၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏ-ႝ፝-፟፩-፱ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝០-៩᠋-᠍᠐-᠙ᢩᤠ-ᤫᤰ-᤻᥆-᥏᧐-᧚ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼-᪉᪐-᪙᪰-᪽ᬀ-ᬄ᬴-᭄᭐-᭙᭫-᭳ᮀ-ᮂᮡ-ᮭ᮰-᮹᯦-᯳ᰤ-᰷᱀-᱉᱐-᱙᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꘠-꘩꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣐-꣙꣠-꣱꤀-꤉ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀꧐-꧙ꧥ꧰-꧹ꨩ-ꨶꩃꩌꩍ꩐-꩙ꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭꯰-꯹ﬞ︀-️︠-︯︳︴﹍-﹏0-9_",x=new RegExp("["+b+"]"),A=new RegExp("["+b+E+"]");b=E=null;var S=[0,11,2,25,2,18,2,1,2,14,3,13,35,122,70,52,268,28,4,48,48,31,17,26,6,37,11,29,3,35,5,7,2,4,43,157,19,35,5,35,5,39,9,51,157,310,10,21,11,7,153,5,3,0,2,43,2,1,4,0,3,22,11,22,10,30,66,18,2,1,11,21,11,25,71,55,7,1,65,0,16,3,2,2,2,26,45,28,4,28,36,7,2,27,28,53,11,21,11,18,14,17,111,72,56,50,14,50,785,52,76,44,33,24,27,35,42,34,4,0,13,47,15,3,22,0,2,0,36,17,2,24,85,6,2,0,2,3,2,14,2,9,8,46,39,7,3,1,3,21,2,6,2,1,2,4,4,0,19,0,13,4,159,52,19,3,54,47,21,1,2,0,185,46,42,3,37,47,21,0,60,42,86,25,391,63,32,0,449,56,264,8,2,36,18,0,50,29,881,921,103,110,18,195,2749,1070,4050,582,8634,568,8,30,114,29,19,47,17,3,32,20,6,18,881,68,12,0,67,12,65,0,32,6124,20,754,9486,1,3071,106,6,12,4,8,8,9,5991,84,2,70,2,1,3,0,3,1,3,3,2,11,2,0,2,6,2,64,2,3,3,7,2,6,2,27,2,3,2,4,2,0,4,6,2,339,3,24,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,7,4149,196,60,67,1213,3,2,26,2,1,2,0,3,0,2,9,2,3,2,0,2,0,7,0,5,0,2,0,2,0,2,2,2,1,2,0,3,0,2,0,2,0,2,0,2,0,2,1,2,0,3,3,2,6,2,3,2,3,2,0,2,9,2,16,6,2,2,4,2,16,4421,42710,42,4148,12,221,3,5761,10591,541],_=[509,0,227,0,150,4,294,9,1368,2,2,1,6,3,41,2,5,0,166,1,1306,2,54,14,32,9,16,3,46,10,54,9,7,2,37,13,2,9,52,0,13,2,49,13,10,2,4,9,83,11,7,0,161,11,6,9,7,3,57,0,2,6,3,1,3,2,10,0,11,1,3,6,4,4,193,17,10,9,87,19,13,9,214,6,3,8,28,1,83,16,16,9,82,12,9,9,84,14,5,9,423,9,838,7,2,7,17,9,57,21,2,13,19882,9,135,4,60,6,26,9,1016,45,17,3,19723,1,5319,4,4,5,9,7,3,6,31,3,149,2,1418,49,513,54,5,49,9,0,15,0,23,4,2,14,1361,6,2,16,3,6,2,1,2,4,2214,6,110,6,6,9,792487,239],D={sourceType:"script",sourceFilename:void 0,startLine:1,allowReturnOutsideFunction:!1,allowImportExportEverywhere:!1,allowSuperOutsideMethod:!1,plugins:[],strictMode:null},C="function"==typeof Symbol&&"symbol"===y(Symbol.iterator)?function(e){return void 0===e?"undefined":y(e)}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":void 0===e?"undefined":y(e)},w=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},P=function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+(void 0===t?"undefined":y(t)));e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)},k=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!==(void 0===t?"undefined":y(t))&&"function"!=typeof t?e:t},F=!0,T=function e(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};w(this,e),this.label=t,this.keyword=r.keyword,this.beforeExpr=!!r.beforeExpr,this.startsExpr=!!r.startsExpr,this.rightAssociative=!!r.rightAssociative,this.isLoop=!!r.isLoop,this.isAssign=!!r.isAssign,this.prefix=!!r.prefix,this.postfix=!!r.postfix,this.binop=r.binop||null,this.updateContext=null},O=function(e){function t(r){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return w(this,t),n.keyword=r,k(this,e.call(this,r,n))}return P(t,e),t}(T),B=function(e){function t(r,n){return w(this,t),k(this,e.call(this,r,{beforeExpr:F,binop:n}))}return P(t,e),t}(T),R={num:new T("num",{startsExpr:!0}),regexp:new T("regexp",{startsExpr:!0}),string:new T("string",{startsExpr:!0}),name:new T("name",{startsExpr:!0}),eof:new T("eof"),bracketL:new T("[",{beforeExpr:F,startsExpr:!0}),bracketR:new T("]"),braceL:new T("{",{beforeExpr:F,startsExpr:!0}),braceBarL:new T("{|",{beforeExpr:F,startsExpr:!0}),braceR:new T("}"),braceBarR:new T("|}"),parenL:new T("(",{beforeExpr:F,startsExpr:!0}),parenR:new T(")"),comma:new T(",",{beforeExpr:F}),semi:new T(";",{beforeExpr:F}),colon:new T(":",{beforeExpr:F}),doubleColon:new T("::",{beforeExpr:F}),dot:new T("."),question:new T("?",{beforeExpr:F}),arrow:new T("=>",{beforeExpr:F}),template:new T("template"),ellipsis:new T("...",{beforeExpr:F}),backQuote:new T("`",{startsExpr:!0}),dollarBraceL:new T("${",{beforeExpr:F,startsExpr:!0}),at:new T("@"),eq:new T("=",{beforeExpr:F,isAssign:!0}),assign:new T("_=",{beforeExpr:F,isAssign:!0}),incDec:new T("++/--",{prefix:!0,postfix:!0,startsExpr:!0}),prefix:new T("prefix",{beforeExpr:F,prefix:!0,startsExpr:!0}),logicalOR:new B("||",1),logicalAND:new B("&&",2),bitwiseOR:new B("|",3),bitwiseXOR:new B("^",4),bitwiseAND:new B("&",5),equality:new B("==/!=",6),relational:new B(">",7),bitShift:new B("<>>",8),plusMin:new T("+/-",{beforeExpr:F,binop:9,prefix:!0,startsExpr:!0}),modulo:new B("%",10),star:new B("*",10),slash:new B("/",10),exponent:new T("**",{beforeExpr:F,binop:11,rightAssociative:!0})},I={break:new O("break"),case:new O("case",{beforeExpr:F}),catch:new O("catch"),continue:new O("continue"),debugger:new O("debugger"),default:new O("default",{beforeExpr:F}),do:new O("do",{isLoop:!0,beforeExpr:F}),else:new O("else",{beforeExpr:F}),finally:new O("finally"),for:new O("for",{isLoop:!0}),function:new O("function",{startsExpr:!0}),if:new O("if"),return:new O("return",{beforeExpr:F}),switch:new O("switch"),throw:new O("throw",{beforeExpr:F}),try:new O("try"),var:new O("var"),let:new O("let"),const:new O("const"),while:new O("while",{isLoop:!0}),with:new O("with"),new:new O("new",{beforeExpr:F,startsExpr:!0}),this:new O("this",{startsExpr:!0}),super:new O("super",{startsExpr:!0}),class:new O("class"),extends:new O("extends",{beforeExpr:F}),export:new O("export"),import:new O("import",{startsExpr:!0}),yield:new O("yield",{beforeExpr:F,startsExpr:!0}),null:new O("null",{startsExpr:!0}),true:new O("true",{startsExpr:!0}),false:new O("false",{startsExpr:!0}),in:new O("in",{beforeExpr:F,binop:7}),instanceof:new O("instanceof",{beforeExpr:F,binop:7}),typeof:new O("typeof",{beforeExpr:F,prefix:!0,startsExpr:!0}),void:new O("void",{beforeExpr:F,prefix:!0,startsExpr:!0}),delete:new O("delete",{beforeExpr:F,prefix:!0,startsExpr:!0})};Object.keys(I).forEach(function(e){R["_"+e]=I[e]});var M=/\r\n?|\n|\u2028|\u2029/,N=new RegExp(M.source,"g"),L=/[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/,j=function e(t,r,n,i){w(this,e),this.token=t,this.isExpr=!!r,this.preserveSpace=!!n,this.override=i},U={braceStatement:new j("{",!1),braceExpression:new j("{",!0),templateQuasi:new j("${",!0),parenStatement:new j("(",!1),parenExpression:new j("(",!0),template:new j("`",!0,!0,function(e){return e.readTmplToken()}),functionExpression:new j("function",!0)};R.parenR.updateContext=R.braceR.updateContext=function(){if(1===this.state.context.length)return void(this.state.exprAllowed=!0);var e=this.state.context.pop();e===U.braceStatement&&this.curContext()===U.functionExpression?(this.state.context.pop(),this.state.exprAllowed=!1):e===U.templateQuasi?this.state.exprAllowed=!0:this.state.exprAllowed=!e.isExpr},R.name.updateContext=function(e){this.state.exprAllowed=!1,e!==R._let&&e!==R._const&&e!==R._var||M.test(this.input.slice(this.state.end))&&(this.state.exprAllowed=!0)},R.braceL.updateContext=function(e){this.state.context.push(this.braceIsBlock(e)?U.braceStatement:U.braceExpression),this.state.exprAllowed=!0},R.dollarBraceL.updateContext=function(){this.state.context.push(U.templateQuasi),this.state.exprAllowed=!0},R.parenL.updateContext=function(e){var t=e===R._if||e===R._for||e===R._with||e===R._while;this.state.context.push(t?U.parenStatement:U.parenExpression),this.state.exprAllowed=!0},R.incDec.updateContext=function(){},R._function.updateContext=function(){this.curContext()!==U.braceStatement&&this.state.context.push(U.functionExpression),this.state.exprAllowed=!1},R.backQuote.updateContext=function(){this.curContext()===U.template?this.state.context.pop():this.state.context.push(U.template),this.state.exprAllowed=!1};var V=function e(t,r){w(this,e),this.line=t,this.column=r},G=function e(t,r){w(this,e),this.start=t,this.end=r},W=function(){function e(){w(this,e)}return e.prototype.init=function(e,t){return this.strict=!1!==e.strictMode&&"module"===e.sourceType,this.input=t,this.potentialArrowAt=-1,this.inMethod=this.inFunction=this.inGenerator=this.inAsync=this.inPropertyName=this.inType=this.inClassProperty=this.noAnonFunctionType=!1,this.labels=[],this.decorators=[],this.tokens=[],this.comments=[],this.trailingComments=[],this.leadingComments=[],this.commentStack=[],this.pos=this.lineStart=0,this.curLine=e.startLine,this.type=R.eof,this.value=null,this.start=this.end=this.pos,this.startLoc=this.endLoc=this.curPosition(),this.lastTokEndLoc=this.lastTokStartLoc=null,this.lastTokStart=this.lastTokEnd=this.pos,this.context=[U.braceStatement],this.exprAllowed=!0,this.containsEsc=this.containsOctal=!1,this.octalPosition=null,this.invalidTemplateEscapePosition=null,this.exportedIdentifiers=[],this},e.prototype.curPosition=function(){return new V(this.curLine,this.pos-this.lineStart)},e.prototype.clone=function(t){var r=new e;for(var n in this){var i=this[n];t&&"context"!==n||!Array.isArray(i)||(i=i.slice()),r[n]=i}return r},e}(),Y=function e(t){w(this,e),this.type=t.type,this.value=t.value,this.start=t.start,this.end=t.end,this.loc=new G(t.startLoc,t.endLoc)},q=function(){function e(t,r){w(this,e),this.state=new W,this.state.init(t,r)}return e.prototype.next=function(){this.isLookahead||this.state.tokens.push(new Y(this.state)),this.state.lastTokEnd=this.state.end,this.state.lastTokStart=this.state.start,this.state.lastTokEndLoc=this.state.endLoc,this.state.lastTokStartLoc=this.state.startLoc,this.nextToken()},e.prototype.eat=function(e){return!!this.match(e)&&(this.next(),!0)},e.prototype.match=function(e){return this.state.type===e},e.prototype.isKeyword=function(e){return g(e)},e.prototype.lookahead=function(){var e=this.state;this.state=e.clone(!0),this.isLookahead=!0,this.next(),this.isLookahead=!1;var t=this.state.clone(!0);return this.state=e,t},e.prototype.setStrict=function(e){if(this.state.strict=e,this.match(R.num)||this.match(R.string)){for(this.state.pos=this.state.start;this.state.pos