diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 3e1ad21b..d6247c70 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -22,6 +22,9 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
+ - name: Login to GitHub Package Registry
+ run: echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> ~/.npmrc
+
- name: Build
run: |
npm install
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 00000000..029b6d6b
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+@sim-phi:registry=https://npm.pkg.github.com
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 399ba92b..73d0c805 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+### [1.5.7] - 2023.12.2
+
+#### 优化
+
+- 现在读取超过 100 条判定线的谱面会弹出警告
+
### [1.5.6] - 2023.10.27
#### 优化
@@ -28,7 +34,7 @@
#### 更改
- 调整部分窗口尺寸
-- 现在能够正常响应相同文件的连续上传,~~方便调试qwq~~
+- 现在能够正常响应相同文件的连续上传,~~方便调试 qwq~~
- 背景模糊的逻辑由上传时执行改为选择并使用时执行,也许会提升性能
### [1.5.4] - 2023.10.2
@@ -41,7 +47,7 @@
#### 新内容
-- 画面右上角:在分数上方显示当前谱面格式标记,例如`PGS`(本体)、`PEC`(PhiEdit)等
+- 画面右上角:在分数上方显示当前谱面格式标记,例如`PGS(1)`(本体格式 1)、`PGS(3)`(本体格式 3) 等
### [1.5.2] - 2023.9.9
@@ -54,20 +60,19 @@
#### 修复
- 修复部分谱面`Note`因浮点误差显示异常的问题
-- 修复部分谱面判定线不透明度大于1时不显示的问题
+- 修复部分谱面判定线不透明度大于 1 时不显示的问题
### [1.5.0] - 2023.7.31
#### 新内容
-- 对pec谱面自带的配置文件提供支持:`Settings.txt`/`info.txt`
- 新增`音效音量`下拉框(实时生效)
- 支持播放背景视频(`音乐`下拉框选中支持的视频文件时生效)
- 新增`更多设置`按钮,以支持更多的设置项
- 新增`显示Acc`复选框(实时生效),该项选中时:
- 画面右上角分数下方显示实时`Acc`
- 新增`显示统计`复选框(实时生效),该项选中时:
- - 画面左方显示实时偏移`DSP`和平均偏移`AVG`(单位ms)
+ - 画面左方显示实时偏移`DSP`和平均偏移`AVG`(单位 ms)
- 画面右方显示不同类型判定实时的触发数目
- 画面左下角显示不同类型`Note`的出现数目
- 新增`低分辨率`复选框(实时生效),该项选中时:
@@ -77,11 +82,10 @@
#### 优化
-- 兼容Safari 12.5.7+ (仅运行/不保证性能)
+- 兼容 Safari 12.5.7+ (仅运行/不保证性能)
- 感谢 [@cgluWxh](https://space.bilibili.com/343672879) 提供 `iPhone 6 (2014)` 设备支持
- 重构文件读取模块,也许会提升性能
- 重构谱面预处理和判定模块,也许会提升性能
-- 重构pec谱面读取模块,为支持导入RPE格式的谱面做准备
- 针对判定区域重叠的情况进行优化:
- 对于多押`Tap`、`Hold`和`Flick`,优先判定物理距离最近的音符
- 现在符合特定条件的`Drag`和`Flick`会阻挡其后的`Tap`判定
@@ -102,18 +106,18 @@
- 调整`line.csv`部分属性:
- 图片缩放与拉伸由`Vert`/`Horz`改为`Scale`/`Aspect`(默认均为`1`)
- 跟随背景变暗由`IsDark`调整为`UseBackgroundDim`
- - 新增`UseLineColor`(跟随FC/AP指示器着色)
+ - 新增`UseLineColor`(跟随 FC/AP 指示器着色)
- 新增`UseLineScale`(使用判定线缩放策略)
- 调整选项卡文案:`信息`调整为`日志`
- 微调判定范围:水平判定范围由画面宽度的`23.555%`改为`23.625%`
-- 微调UI,同时更改部分资源的引用路径或读取方式,移除本仓库的官方素材
+- 微调 UI,同时更改部分资源的引用路径或读取方式,移除本仓库的官方素材
- 文件上传:背景音乐由必需改为可选(无音乐时替换为时长为`谱面时长+0.5s`的空音乐)
#### 修复
-- 修复读取部分zip文件时非utf8编码文件名显示乱码的问题
-- 现在加载zip组件会正常显示加载文案,而不是无任何提示
-- 修复空文件被错误识别为pec谱面的问题
+- 修复读取部分 zip 文件时非 utf8 编码文件名显示乱码的问题
+- 现在加载 zip 组件会正常显示加载文案,而不是无任何提示
+- 修复空文件被错误识别为谱面的问题
- 现在切换全屏会清除当前键盘操作
- 修复了缩放窗口时画面闪烁的问题
- 现在也许能正确读取文件夹内的配置文件
@@ -128,25 +132,24 @@
#### 新内容
-- 重构文件上传模块,支持多文件上传和嵌套zip
+- 重构文件上传模块,支持多文件上传和嵌套 zip
#### 优化
- 优化信息显示:识别网址并自动转换成超链接
-- 优化第三方js的加载逻辑和相关提示
-- 兼容性检测现在由原生js实现
+- 优化第三方 js 的加载逻辑和相关提示
+- 兼容性检测现在由原生 js 实现
- 优化全屏幕相关判断
- 优化绘制逻辑,提升性能
#### 更改
- ~~`Autoplay`复选框名称改为`预览模式`~~
-- 更改部分UI贴图引用路径,移除本仓库的官方贴图素材
+- 更改部分 UI 贴图引用路径,移除本仓库的官方贴图素材
#### 修复
- 修复存在触摸点时新的触摸点移入画面会报错的问题
-- 修复pec谱面`Note`因浮点误差显示异常的问题
### [1.4.20] - 2022.8.30
@@ -160,7 +163,7 @@
#### 更改
-- 微调`Note`默认大小(缩小约0.99%)
+- 微调`Note`默认大小(缩小约 0.99%)
- 将`info.csv`的按键缩放由`ScaleRatio`调整为`NoteScale`(数值越大`Note`越大,默认`1`)
#### 修复
@@ -172,7 +175,7 @@
#### 优化
-- ~~优化UA识别策略 (针对iPad/Safari)~~
+- ~~优化 UA 识别策略 (针对 iPad/Safari)~~
#### 修复
@@ -196,7 +199,7 @@
#### 优化
-- ~~针对UA标识为iOS和MacOS的设备禁用图片缓存~~
+- ~~针对 UA 标识为 iOS 和 MacOS 的设备禁用图片缓存~~
### [1.4.16] - 2022.5.1
@@ -207,13 +210,13 @@
#### 优化
-- 优化第三方js的加载逻辑和相关提示
+- 优化第三方 js 的加载逻辑和相关提示
- 现在文本输入溢出时会缩排(即超过一定长度时缩小字号)
#### 更改
- `显示定位点`:`Note`定位文字改为`判定线序号±Note序号+Note类型`
- - (例如0号判定线上方的0号Note,如果是`Tap`则定位文字为`0+0t`)
+ - (例如 0 号判定线上方的 0 号 Note,如果是`Tap`则定位文字为`0+0t`)
#### 修复
@@ -223,8 +226,7 @@
#### 新内容
-- 新增pec谱面`Note`方向数值检测
-- ~~没有别的更新了qwq~~
+- ~~没有别的更新了 qwq~~
#### 修复
@@ -232,7 +234,7 @@
#### 已删除
-- 移除HyperMode
+- 移除 HyperMode
### [1.4.14] - 2021-12-23
@@ -240,7 +242,7 @@
- 新增功能:`谱面镜像`(实时生效)
- 新增功能:`音乐变速`(非实时生效,~~启用时无法存档~~)
-- 新增Early/Late特效复选框(测试功能)
+- 新增 Early/Late 特效复选框(测试功能)
#### 更改
@@ -250,9 +252,9 @@
#### 新内容
-- ~~新增HyperMode:更加严格的判定和结算(测试功能)~~
-- ~~新增Great判定:40-80ms(显示为绿色特效,HyperMode特有)~~
-- 新增Early/Late统计(结算界面点击切换)
+- ~~新增 HyperMode:更加严格的判定和结算(测试功能)~~
+- ~~新增 Great 判定:40-80ms(显示为绿色特效,HyperMode 特有)~~
+- 新增 Early/Late 统计(结算界面点击切换)
#### 更改
@@ -267,24 +269,19 @@
#### 新内容
-- 新增zip模块兼容性检测(测试功能)
+- 新增 zip 模块兼容性检测(测试功能)
#### 修复
- 修复画面高度小于一定值时全屏触摸点向下错位的问题
-- 修复pec谱面结尾的n指令无法正常读取的问题
#### 已删除
-- 移除可选链操作符和WebAssembly兼容性检测
+- 移除可选链操作符和 WebAssembly 兼容性检测
- 移除视频录制功能
### [1.4.11] - 2021-10-23
-#### 新内容
-
-- 新增pec谱面事件时间检测(测试功能)
-
#### 更改
- 为`Hold`添加多押高亮(与本体`v2.0.0`一致)
@@ -292,7 +289,6 @@
#### 修复
- 修复画面高度大于一定值时部分`Note`无法正常显示的问题
-- ~~修复pec谱面结尾的n指令无法正常读取的问题~~
### [1.4.10] - 2021-10-10
@@ -309,11 +305,11 @@
#### 更改
-- 画面左上角播放进度:当分钟小于10时不再补0
+- 画面左上角播放进度:当分钟小于 10 时不再补 0
#### 修复
-- 修复因上版本播放逻辑造成音频撕裂的问题(将播放逻辑回退至1.4.7)
+- 修复因上版本播放逻辑造成音频撕裂的问题(将播放逻辑回退至 1.4.7)
- 修复过渡动画结束前判定时间内的`Note`可以被打击的问题
- ~~修复进入结算界面之前重新开始仍会结算的问题~~
@@ -334,29 +330,27 @@
#### 新内容
- 新增浏览器兼容性检测(测试功能)
-- 新增pec谱面异常`Note`和负数`Alpha`检测(测试功能)
#### 优化
-- 适配iPhone全面屏
+- 适配 iPhone 全面屏
### [1.4.6] - 2021-09-15
#### 新内容
-- 隐藏文件:zip内包含以`.`开头的文件将被隐藏,不会显示在选项框
+- 隐藏文件:zip 内包含以`.`开头的文件将被隐藏,不会显示在选项框
#### 优化
- 优化图像和字体渲染
-- 为Safari兼容全屏功能
+- 为 Safari 兼容全屏功能
#### 更改
- `Hold`连续打击特效间隔现在与判定线`bpm`成反比
- `Autoplay`开启时,结算画面不再显示"+0"
-- 产生Bad判定的`Note`现在会跟随判定线
-- pec谱面默认偏移由-150ms改为-175ms
+- 产生 Bad 判定的`Note`现在会跟随判定线
### [1.4.5] - 2021-09-10
@@ -366,12 +360,12 @@
#### 优化
-- 为Safari兼容离屏自动暂停功能
+- 为 Safari 兼容离屏自动暂停功能
#### 更改
-- 若Bad判定范围内的`Tap`前方存在未打击的`Drag`或`Flick`将不触发Bad判定
-- 暂停快捷键由`Space`(空格)改为`Shift`(左右Shift均可)
+- 若 Bad 判定范围内的`Tap`前方存在未打击的`Drag`或`Flick`将不触发 Bad 判定
+- 暂停快捷键由`Space`(空格)改为`Shift`(左右 Shift 均可)
- 调整结算界面`AUTO PLAY`的显示位置
### [1.4.4] - 2021-09-09
@@ -383,7 +377,7 @@
#### 优化
-- 对Safari进行部分兼容
+- 对 Safari 进行部分兼容
- 优化文件读取逻辑,现在能够支持更多文件类型
- 优化图像渲染
@@ -397,7 +391,7 @@
- 新增功能:`谱面延时`(非实时生效)
- 添加防止模拟器泛滥的提示
-- 新增错误提示(现在导入不包含音乐的zip文件也会报错)
+- 新增错误提示(现在导入不包含音乐的 zip 文件也会报错)
#### 优化
@@ -406,7 +400,7 @@
#### 更改
- `Hold`渲染由倒序改为顺序(其余`Note`渲染顺序仍为倒序)
-- 导入zip:背景图片由必需改为可选(无图片时替换为纯白背景)
+- 导入 zip:背景图片由必需改为可选(无图片时替换为纯白背景)
- 过渡动画由逐帧控制改为计时器控制
#### 修复
@@ -421,7 +415,7 @@
#### 修复
-- 修复无法判定`HoldTime`小于0.2s的`Hold`的问题
+- 修复无法判定`HoldTime`小于 0.2s 的`Hold`的问题
### [1.4.1] - 2021-09-01
@@ -439,7 +433,7 @@
#### 修复
-- 修复开启`显示定位点`时无法统计note数目的问题
+- 修复开启`显示定位点`时无法统计 note 数目的问题
- 修复逐帧判定导致严重吃音的问题
- 修复结算界面"φ"评级有几率错误地显示为“V”的问题
@@ -450,7 +444,7 @@
- 新增`FC/AP指示器`复选框(实时生效)
- 新增游玩模式(测试功能,需关闭`Autoplay`复选框)
- 新增结算界面(测试功能,在歌曲结束后出现,需开启`过渡动画`复选框)
-- 新增错误提示(导入非zip文件或不包含谱面的zip文件均会报错)
+- 新增错误提示(导入非 zip 文件或不包含谱面的 zip 文件均会报错)
#### 优化
@@ -459,13 +453,9 @@
#### 更改
- 打击特效动画更加接近本体`v1.6.11`
-- 单个打击特效时长由30tick改为500ms
+- 单个打击特效时长由 30tick 改为 500ms
- 切换全屏的方式改为双击画面右下角
-#### 修复
-
-- 修复关于pec谱面第一个bp指令延时不为0导致谱面错误偏移的问题
-
### [1.3.2] - 2021-08-16
#### 优化
@@ -478,7 +468,7 @@
#### 更改
-- `Note`定位文字改为`判定线序号±Note序号`(例如0号判定线上方的第0个Note定位文字为`0+0`)
+- `Note`定位文字改为`判定线序号±Note序号`(例如 0 号判定线上方的第 0 个 Note 定位文字为`0+0`)
### [1.3.1] - 2021-08-11
@@ -496,7 +486,6 @@
#### 新内容
-- 对pec谱面的bpm变速功能进行适配
- 新增功能:过渡动画(包含开头淡入和结尾淡出,不包含结算)
- 新增输入框(曲绘、谱师),对应过渡动画
- 通过添加`line.csv`以修改判定线贴图(测试功能)
@@ -504,24 +493,23 @@
#### 优化
-- 优化zip文件读取逻辑(数组→对象)
+- 优化 zip 文件读取逻辑(数组 → 对象)
- 优化判定线和背景的绘制顺序
-- 调整背景和UI对不同尺寸屏幕的适配
+- 调整背景和 UI 对不同尺寸屏幕的适配
- 打击特效动画采用缓动函数
- 调整打击音效与本体`v1.6.10`一致
- 优化`播放/停止`和`暂停/继续`按钮相关逻辑
#### 更改
-- `宽高比`下拉列表新增`10:7`(适配iPad Pro 11),将`256:175`改为`19:13`(适配PhiEdit)
+- `宽高比`下拉列表新增`10:7`(适配 iPad Pro 11),将`256:175`改为`19:13`
- `显示定位点`:判定线数字透明度随判定线透明度变化,`Note`打击后变为半透明
-- `Hold`尾判调整为提前0.2秒得分
-- `Hold`连续打击特效间隔由12tick缩短为9tick
+- `Hold`尾判调整为提前 0.2 秒得分
+- `Hold`连续打击特效间隔由 12tick 缩短为 9tick
#### 修复
- 修复部分谱面`Note`打击时间异常的问题
-- 修复pec谱面由于判定线事件time不精确导致排序错误的问题
- 修复点击`停止`按钮后按住的`Hold`无法关闭导致连击得分溢出的问题
# [Phixos](https://lchzh3473.github.io/sim-phi/index)
## 简介
$\text{Phi\color{red}x\color{green}os}=\color{red}\xcancel{\text{\color{black}Phi{g}ros}}\text{ \color{green}Online Simulator}$
-> 这不是Phigros,这是在线的模拟器!
+> 这不是 Phigros,这是在线的模拟器!
如题,用 JS/Canvas 还原 [Phigros](https://www.taptap.com/app/165287) 游戏画面。
@@ -14,14 +14,14 @@ $\text{Phi\color{red}x\color{green}os}=\color{red}\xcancel{\text{\color{black}Ph
### 特别感谢
-- [@luch4736](https://github.com/luch4736) 的 iPad (Safari on iOS 15.6+)
-- [@cgluWxh](https://github.com/cgluWxh) 的 iPhone (Safari on iOS 12.5.7)
+- [@luch4736](https://github.com/luch4736) 提供 iPad (Safari on iOS 15.6+)
+- [@cgluWxh](https://github.com/cgluWxh) 提供 iPhone (Safari on iOS 12.5.7)
- [@AsakuraMizu](https://github.com/AsakuraMizu) 强力推荐的 [Vite](https://vitejs.dev) 驱动!
- 以及看到这个页面的你!
## 文档
-访问 [docs.lchzh.net](https://docs.lchzh.net/project/sim-phi-core) (Working in progress)
+访问 [docs.lchzh.net](https://docs.lchzh.net/project/sim-phi-core) (Working in progress)
## 更新日志
diff --git a/package.json b/package.json
index 69ae2027..e517d134 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "sim-phi-vite",
"private": true,
- "version": "1.5.6",
+ "version": "1.5.7",
"type": "module",
"scripts": {
"dev": "vite",
@@ -11,24 +11,26 @@
"eslint:format": "eslint --fix \"src/**/*.{js,ts}\""
},
"dependencies": {
+ "@sim-phi/extends": "^0.3.2",
"jszip": "^3.10.1",
"md5": "^2.3.0",
"stackblur-canvas": "^2.6.0"
},
"devDependencies": {
- "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-terser": "^0.4.4",
- "@types/eslint": "^8.44.6",
- "@types/md5": "^2.3.4",
- "@types/prompts": "^2.4.7",
- "@typescript-eslint/eslint-plugin": "^6.9.0",
- "@typescript-eslint/parser": "^6.9.0",
- "eslint": "^8.52.0",
+ "@stylistic/eslint-plugin": "^1.4.1",
+ "@types/eslint": "^8.44.7",
+ "@types/md5": "^2.3.5",
+ "@types/prompts": "^2.4.9",
+ "@typescript-eslint/eslint-plugin": "^6.12.0",
+ "@typescript-eslint/parser": "^6.12.0",
+ "eslint": "^8.54.0",
"eslint-plugin-rulesdir": "^0.2.2",
"picocolors": "^1.0.0",
"prompts": "^2.4.2",
- "typescript": "^5.2.2",
- "vite": "^4.5.0"
+ "typescript": "^5.3.2",
+ "vite": "^5.0.2"
}
}
diff --git a/public/index.html b/public/index.html
index fcc9bd2c..5a22db90 100644
--- a/public/index.html
+++ b/public/index.html
@@ -45,7 +45,7 @@
-
+
@@ -174,12 +174,14 @@
-
-
-
资源管理
+
-
+
diff --git a/scripts/meta.json b/scripts/meta.json
index 5940d308..93b3d9e4 100644
--- a/scripts/meta.json
+++ b/scripts/meta.json
@@ -1,5 +1,5 @@
{
- "version": "1.5.6",
+ "version": "1.5.7",
"pubdate": 1611795955,
- "lastupdate": 1698412304
+ "lastupdate": 1701531841
}
diff --git a/src/components/HitImage.ts b/src/components/HitImage.ts
index 0efaafb3..8ca28d2b 100644
--- a/src/components/HitImage.ts
+++ b/src/components/HitImage.ts
@@ -1,5 +1,4 @@
-import type { NoteExtends } from 'src/core';
-import { noteRender } from '../index';
+import type { Renderer } from '@/core';
import type { ScaledNote } from './ScaledNote';
export class HitImage {
public offsetX: number;
@@ -10,7 +9,7 @@ export class HitImage {
public direction: number[][];
public color: string;
public constructor(offsetX: number, offsetY: number, n1: string, n3: string) {
- const packs = noteRender.hitFX[n1];
+ const packs = hook.noteRender.hitFX[n1]; // TODO: Refactor
this.offsetX = offsetX || 0;
this.offsetY = offsetY || 0;
this.time = performance.now();
@@ -19,11 +18,11 @@ export class HitImage {
this.direction = Array(packs.numOfParts || 0).fill(0).map(() => [Math.random() * 80 + 185, Math.random() * 2 * Math.PI]);
this.color = n3;
}
- public static perfect(offsetX: number, offsetY: number, _note: NoteExtends): HitImage {
+ public static perfect(offsetX: number, offsetY: number, _note: Renderer.Note): HitImage {
// console.log(note);
return new HitImage(offsetX, offsetY, 'Perfect', '#ffeca0');
}
- public static good(offsetX: number, offsetY: number, _note: NoteExtends): HitImage {
+ public static good(offsetX: number, offsetY: number, _note: Renderer.Note): HitImage {
// console.log(note);
return new HitImage(offsetX, offsetY, 'Good', '#b4e1ff');
}
diff --git a/src/core.ts b/src/core.ts
index 266da1f5..f60a0efd 100644
--- a/src/core.ts
+++ b/src/core.ts
@@ -1,11 +1,10 @@
-// import { JudgeLineEvent } from './utils/Chart';
interface SpeedEventExtends extends SpeedEvent {
floorPosition: number;
floorPosition2: number;
startSeconds: number;
endSeconds: number;
}
-export interface NoteExtends extends Note {
+interface NoteExtends extends Note {
maxVisiblePos: number;
offsetX: number;
offsetY: number;
@@ -39,7 +38,7 @@ interface LineEventExtends extends JudgeLineEvent {
startSeconds: number;
endSeconds: number;
}
-export interface JudgeLineExtends extends JudgeLine {
+interface JudgeLineExtends extends JudgeLine {
lineId: number;
offsetX: number;
offsetY: number;
@@ -509,6 +508,10 @@ export class Renderer {
this.lineScale = canvasfg.width > canvasfg.height * 0.75 ? canvasfg.height / 18.75 : canvasfg.width / 14.0625; // 判定线、文字缩放
}
}
+export namespace Renderer {
+ export type Note = NoteExtends;
+ export type JudgeLine = JudgeLineExtends;
+}
// // 规范判定线事件
// function normalizeLineEvent(events: JudgeLineEvent[]) {
// const oldEvents = events.map(i => new JudgeLineEvent(i as unknown as Record
)); // 深拷贝
diff --git a/src/extends/format.d.ts b/src/extends/format.d.ts
index c0ddbd36..ccfe7042 100644
--- a/src/extends/format.d.ts
+++ b/src/extends/format.d.ts
@@ -1,8 +1,8 @@
-type Chart = import('../utils/Chart').Chart;
-type Note = import('../utils/Chart').Note;
-type JudgeLine = import('../utils/Chart').JudgeLine;
-type SpeedEvent = import('../utils/Chart').SpeedEvent;
-type JudgeLineEvent = import('../utils/Chart').JudgeLineEvent;
+type Chart = import('@/utils/Chart').Chart;
+type Note = import('@/utils/Chart').Note;
+type JudgeLine = import('@/utils/Chart').JudgeLine;
+type SpeedEvent = import('@/utils/Chart').SpeedEvent;
+type JudgeLineEvent = import('@/utils/Chart').JudgeLineEvent;
interface ChartPGS {
formatVersion?: number;
offset: number;
diff --git a/src/extends/pec/BpmList.ts b/src/extends/pec/BpmList.ts
deleted file mode 100644
index 5bfc8b27..00000000
--- a/src/extends/pec/BpmList.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-interface BpmEventPec {
- start: number;
- end: number;
- bpm: number;
- value: number;
-}
-export type BeatArray = [number, number, number];
-export class BpmList {
- public baseBpm: number;
- private accTime: number;
- private readonly list: BpmEventPec[];
- public constructor(baseBpm: unknown) {
- this.baseBpm = Number(baseBpm) || 120;
- this.accTime = 0;
- this.list = []; // 存放bpm变速事件
- }
- public push(start: number, end: number, bpm: number): void {
- const value = this.accTime;
- this.list.push({ start, end, bpm, value });
- this.accTime += (end - start) / bpm;
- }
- public calc(beat: number): number {
- let time = 0;
- for (const i of this.list) {
- if (beat > i.end) continue;
- if (beat < i.start) break;
- time = Math.round(((beat - i.start) / i.bpm + i.value) * this.baseBpm * 32);
- }
- return time;
- }
- public calc2(time: BeatArray): number {
- return this.calc(time[0] + time[1] / time[2]);
- }
-}
diff --git a/src/extends/pec/LinePec.ts b/src/extends/pec/LinePec.ts
deleted file mode 100644
index 8fe42412..00000000
--- a/src/extends/pec/LinePec.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-import { easing } from './easing.js';
-interface SpeedEventPec {
- time: number;
- value: number;
-}
-interface NotePecExtends {
- type: number;
- time: number;
- positionX: number;
- holdTime: number;
- speed: number;
- isAbove: boolean;
- isFake: boolean;
-}
-interface LineEventPec {
- startTime: number;
- endTime: number;
- value: number;
- motionType: number;
-}
-interface LineEventPecExtends extends LineEventPec {
- value2: number;
-}
-export class LinePec {
- private readonly bpm: number;
- private readonly speedEvents: SpeedEventPec[];
- private readonly notes: NotePecExtends[];
- private readonly alphaEvents: LineEventPec[];
- private readonly moveEvents: LineEventPecExtends[];
- private readonly rotateEvents: LineEventPec[];
- public constructor(bpm: number) {
- this.bpm = 120;
- this.speedEvents = [];
- this.notes = [];
- this.alphaEvents = [];
- this.moveEvents = [];
- this.rotateEvents = [];
- if (!isNaN(bpm)) this.bpm = bpm;
- }
- public pushNote(type: number, time: number, positionX: number, holdTime: number, speed: number, isAbove: boolean, isFake: boolean): void {
- this.notes.push({ type, time, positionX, holdTime, speed, isAbove, isFake });
- }
- public pushSpeedEvent(time: number, value: number): void {
- this.speedEvents.push({ time, value });
- }
- public pushAlphaEvent(startTime: number, endTime: number, value: number, motionType: number): void {
- this.alphaEvents.push({ startTime, endTime, value, motionType });
- }
- public pushMoveEvent(startTime: number, endTime: number, value: number, value2: number, motionType: number): void {
- this.moveEvents.push({ startTime, endTime, value, value2, motionType });
- }
- public pushRotateEvent(startTime: number, endTime: number, value: number, motionType: number): void {
- this.rotateEvents.push({ startTime, endTime, value, motionType });
- }
- public format(): JudgeLinePGS {
- const sortFn = (a: { time: number }, b: { time: number }) => a.time - b.time;
- const sortFn2 = (a: { startTime: number; endTime: number }, b: { startTime: number; endTime: number }) => a.startTime - b.startTime + (a.endTime - b.endTime);
- // 不单独判断以避免误差
- const result = {
- formatVersion: 3,
- offset: 0,
- bpm: this.bpm,
- speedEvents: [] as SpeedEventPGS[],
- numOfNotes: 0,
- numOfNotesAbove: 0,
- numOfNotesBelow: 0,
- notesAbove: [] as NotePGS[],
- notesBelow: [] as NotePGS[],
- judgeLineDisappearEvents: [] as JudgeLineEventPGS[],
- judgeLineMoveEvents: [] as JudgeLineEventPGS[],
- judgeLineRotateEvents: [] as JudgeLineEventPGS[]
- };
- const pushDisappearEvent = (startTime: number, endTime: number, start: number, end: number) => {
- result.judgeLineDisappearEvents.push({ startTime, endTime, start, end, start2: 0, end2: 0 });
- };
- const pushMoveEvent = (startTime: number, endTime: number, start: number, end: number, start2: number, end2: number) => {
- result.judgeLineMoveEvents.push({ startTime, endTime, start, end, start2, end2 });
- };
- const pushRotateEvent = (startTime: number, endTime: number, start: number, end: number) => {
- result.judgeLineRotateEvents.push({ startTime, endTime, start, end, start2: 0, end2: 0 });
- };
- // cv和floorPosition一并处理
- const cvp = this.speedEvents.sort(sortFn);
- let s1 = 0;
- for (let i = 0; i < cvp.length; i++) {
- const startTime = Math.max(cvp[i].time, 0);
- const endTime = i < cvp.length - 1 ? cvp[i + 1].time : 1e9;
- const { value } = cvp[i];
- const floorPosition = s1;
- s1 += (endTime - startTime) * value / this.bpm * 1.875;
- s1 = Math.fround(s1);
- result.speedEvents.push({ startTime, endTime, value, floorPosition });
- }
- for (const i of this.notes.sort(sortFn)) {
- const { time } = i;
- let v1 = 0;
- let v2 = 0;
- let v3 = 0;
- for (const e of result.speedEvents) {
- if (time > e.endTime) continue;
- if (time < e.startTime) break;
- v1 = e.floorPosition!;
- v2 = e.value;
- v3 = time - e.startTime;
- }
- const note = {
- type: i.type,
- time: time + (i.isFake ? 1e9 : 0),
- positionX: i.positionX,
- holdTime: i.holdTime,
- speed: i.speed * (i.type === 3 ? v2 : 1),
- floorPosition: Math.fround(v1 + v2 * v3 / this.bpm * 1.875)
- };
- if (i.isAbove) {
- result.notesAbove.push(note);
- if (i.isFake) continue;
- result.numOfNotes++;
- result.numOfNotesAbove++;
- } else {
- result.notesBelow.push(note);
- if (i.isFake) continue;
- result.numOfNotes++;
- result.numOfNotesBelow++;
- }
- }
- // 整合motionType
- let dt = 0;
- let d1 = 0;
- for (const e of this.alphaEvents.sort(sortFn2)) {
- pushDisappearEvent(dt, e.startTime, d1, d1);
- const easingFn = easing(e.motionType);
- if (easingFn != null) {
- const t1 = e.value - d1;
- let x1 = 0;
- let x2 = 0;
- for (let i = e.startTime; i < e.endTime; i++) {
- x1 = x2;
- x2 = easingFn((i + 1 - e.startTime) / (e.endTime - e.startTime));
- pushDisappearEvent(i, i + 1, d1 + x1 * t1, d1 + x2 * t1);
- }
- } else if (e.motionType) pushDisappearEvent(e.startTime, e.endTime, d1, e.value);
- dt = e.endTime;
- d1 = e.value;
- }
- pushDisappearEvent(dt, 1e9, d1, d1);
- //
- let mt = 0;
- let m1 = 0;
- let m2 = 0;
- for (const e of this.moveEvents.sort(sortFn2)) {
- pushMoveEvent(mt, e.startTime, m1, m1, m2, m2);
- const easingFn = easing(e.motionType);
- if (easingFn != null) {
- const t1 = e.value - m1;
- const t2 = e.value2 - m2;
- let x1 = 0;
- let x2 = 0;
- for (let i = e.startTime; i < e.endTime; i++) {
- x1 = x2;
- x2 = easingFn((i + 1 - e.startTime) / (e.endTime - e.startTime));
- pushMoveEvent(i, i + 1, m1 + x1 * t1, m1 + x2 * t1, m2 + x1 * t2, m2 + x2 * t2);
- }
- } else if (e.motionType === 1) pushMoveEvent(e.startTime, e.endTime, m1, e.value, m2, e.value2);
- mt = e.endTime;
- m1 = e.value;
- m2 = e.value2;
- }
- pushMoveEvent(mt, 1e9, m1, m1, m2, m2);
- //
- let rt = 0;
- let r1 = 0;
- for (const e of this.rotateEvents.sort(sortFn2)) {
- pushRotateEvent(rt, e.startTime, r1, r1);
- const easingFn = easing(e.motionType);
- if (easingFn != null) {
- const t1 = e.value - r1;
- let x1 = 0;
- let x2 = 0;
- for (let i = e.startTime; i < e.endTime; i++) {
- x1 = x2;
- x2 = easingFn((i + 1 - e.startTime) / (e.endTime - e.startTime));
- pushRotateEvent(i, i + 1, r1 + x1 * t1, r1 + x2 * t1);
- }
- } else if (e.motionType === 1) pushRotateEvent(e.startTime, e.endTime, r1, e.value);
- rt = e.endTime;
- r1 = e.value;
- }
- pushRotateEvent(rt, 1e9, r1, r1);
- return result;
- }
-}
diff --git a/src/extends/pec/cubicBezier.js b/src/extends/pec/cubicBezier.js
deleted file mode 100644
index 6b02cffb..00000000
--- a/src/extends/pec/cubicBezier.js
+++ /dev/null
@@ -1,66 +0,0 @@
-export function cubicBezier(p1x, p1y, p2x, p2y) {
- const ZERO_LIMIT = 1e-6;
- // Calculate the polynomial coefficients,
- // implicit first and last control points are (0,0) and (1,1).
- const ax = 3 * p1x - 3 * p2x + 1;
- const bx = 3 * p2x - 6 * p1x;
- const cx = 3 * p1x;
- const ay = 3 * p1y - 3 * p2y + 1;
- const by = 3 * p2y - 6 * p1y;
- const cy = 3 * p1y;
- function sampleCurveDerivativeX(t) {
- // `ax t^3 + bx t^2 + cx t` expanded using Horner's rule
- return (3 * ax * t + 2 * bx) * t + cx;
- }
- function sampleCurveX(t) {
- return ((ax * t + bx) * t + cx) * t;
- }
- function sampleCurveY(t) {
- return ((ay * t + by) * t + cy) * t;
- }
- // Given an x value, find a parametric value it came from.
- function solveCurveX(x) {
- let t2 = x;
- let derivative, x2;
- // https://trac.webkit.org/browser/trunk/Source/WebCore/platform/animation
- // first try a few iterations of Newton's method -- normally very fast.
- // http://en.wikipedia.org/wikiNewton's_method
- for (let i = 0; i < 8; i++) {
- // f(t) - x = 0
- x2 = sampleCurveX(t2) - x;
- if (Math.abs(x2) < ZERO_LIMIT) {
- return t2;
- }
- derivative = sampleCurveDerivativeX(t2);
- // == 0, failure
- if (Math.abs(derivative) < ZERO_LIMIT) {
- break;
- }
- t2 -= x2 / derivative;
- }
- // Fall back to the bisection method for reliability.
- // bisection
- // http://en.wikipedia.org/wiki/Bisection_method
- let t1 = 1;
- let t0 = 0;
- t2 = x;
- while (t1 > t0) {
- x2 = sampleCurveX(t2) - x;
- if (Math.abs(x2) < ZERO_LIMIT) {
- return t2;
- }
- if (x2 > 0) {
- t1 = t2;
- } else {
- t0 = t2;
- }
- t2 = (t1 + t0) / 2;
- }
- // Failure
- return t2;
- }
- function solve(x) {
- return sampleCurveY(solveCurveX(x));
- }
- return solve;
-}
diff --git a/src/extends/pec/easing.js b/src/extends/pec/easing.js
deleted file mode 100644
index 1cccca76..00000000
--- a/src/extends/pec/easing.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/* eslint-disable no-param-reassign */
-const tl = [
- null, // 0
- null, // 1
- t => Math.sin(t * Math.PI / 2), // 2
- t => 1 - Math.cos(t * Math.PI / 2), // 3
- t => 1 - (t - 1) ** 2, // 4
- t => t ** 2, // 5
- t => (1 - Math.cos(t * Math.PI)) / 2, // 6
- t => ((t *= 2) < 1 ? t ** 2 : -((t - 2) ** 2 - 2)) / 2, // 7
- t => 1 + (t - 1) ** 3, // 8
- t => t ** 3, // 9
- t => 1 - (t - 1) ** 4, // 10
- t => t ** 4, // 11
- t => ((t *= 2) < 1 ? t ** 3 : (t - 2) ** 3 + 2) / 2, // 12
- t => ((t *= 2) < 1 ? t ** 4 : -((t - 2) ** 4 - 2)) / 2, // 13
- t => 1 + (t - 1) ** 5, // 14
- t => t ** 5, // 15
- t => 1 - 2 ** (-10 * t), // 16
- t => 2 ** (10 * (t - 1)), // 17
- t => Math.sqrt(1 - (t - 1) ** 2), // 18
- t => 1 - Math.sqrt(1 - t ** 2), // 19
- t => (2.70158 * t - 1) * (t - 1) ** 2 + 1, // 20
- t => (2.70158 * t - 1.70158) * t ** 2, // 21
- t => ((t *= 2) < 1 ? 1 - Math.sqrt(1 - t ** 2) : Math.sqrt(1 - (t - 2) ** 2) + 1) / 2, // 22
- t => t < 0.5 ? (14.379638 * t - 5.189819) * t ** 2 : (14.379638 * t - 9.189819) * (t - 1) ** 2 + 1, // 23
- t => 1 - 2 ** (-10 * t) * Math.cos(t * Math.PI / 0.15), // 24
- t => 2 ** (10 * (t - 1)) * Math.cos((t - 1) * Math.PI / 0.15), // 25
- t => ((t *= 11) < 4 ? t ** 2 : t < 8 ? (t - 6) ** 2 + 12 : t < 10 ? (t - 9) ** 2 + 15 : (t - 10.5) ** 2 + 15.75) / 16, // 26
- t => 1 - tl[26](1 - t), // 27
- t => (t *= 2) < 1 ? tl[26](t) / 2 : tl[27](t - 1) / 2 + 0.5, // 28
- t => t < 0.5 ? 2 ** (20 * t - 11) * Math.sin((160 * t + 1) * Math.PI / 18) : 1 - 2 ** (9 - 20 * t) * Math.sin((160 * t + 1) * Math.PI / 18) // 29
-];
-function isLinear(index) {
- index = Math.trunc(index);
- return index < 2 || index > 29;
-}
-/**
- * @param {number} index
- * @returns {((t: number) => number) | null}
- */
-function easing(index) {
- index = Math.trunc(index);
- return tl[index];
-}
-export { isLinear, easing };
diff --git a/src/extends/pec/index.ts b/src/extends/pec/index.ts
deleted file mode 100644
index fafd8c76..00000000
--- a/src/extends/pec/index.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { parse } from './pec2json';
-import { parse as parseRPE } from './rpe2json';
-// 读取info.txt
-function readInfo(text: string): Record[] {
- const lines = String(text).split(/\r?\n/);
- const result = [];
- let current = {} as Record;
- for (const i of lines) {
- if (i.startsWith('#')) {
- if (Object.keys(current).length) result.push(current);
- current = {};
- } else {
- let [key, value] = i.split(/:(.+)/).map(s => s.trim());
- if (key === 'Song') key = 'Music';
- if (key === 'Picture') key = 'Image';
- if (key) current[key] = value;
- }
- }
- if (Object.keys(current).length) result.push(current);
- return result;
-}
-export default { parse, parseRPE, readInfo };
diff --git a/src/extends/pec/pec2json.ts b/src/extends/pec/pec2json.ts
deleted file mode 100644
index e92f7871..00000000
--- a/src/extends/pec/pec2json.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import { isLinear } from './easing.js';
-import { BpmList } from './BpmList';
-import { LinePec } from './LinePec';
-interface BpmCommand {
- time: number;
- bpm: number;
-}
-interface NoteCommand {
- type: number;
- lineId: number;
- time: number;
- time2: number;
- offsetX: number;
- isAbove: number;
- isFake: number;
- text: string;
- speed: number;
- size: number;
-}
-interface LineCommand {
- type: string;
- lineId: number;
- time: number;
- time2: number;
- speed: number;
- offsetX: number;
- offsetY: number;
- rotation: number;
- alpha: number;
- motionType: number;
- text: string;
-}
-interface PecParseResult {
- data: ChartPGS;
- messages: string[];
- format: string;
-}
-export function parse(pec: string, filename: string): PecParseResult {
- const data = pec.split(/\s+/); // 切分pec文本
- const data2 = {
- offset: 0,
- bpmList: [] as BpmCommand[],
- notes: [] as NoteCommand[],
- lines: [] as LineCommand[]
- };
- const result = {
- formatVersion: 3,
- offset: 0,
- numOfNotes: 0,
- judgeLineList: [] as JudgeLinePGS[]
- };
- const warnings = []; //
- let ptr = 0;
- data2.offset = isNaN(Number(data[ptr])) ? 0 : Number(data[ptr++]);
- while (ptr < data.length) {
- const command = data[ptr++];
- if (command === '') continue;
- if (command === 'bp') {
- const time = Number(data[ptr++]);
- const bpm = Number(data[ptr++]);
- data2.bpmList.push({ time, bpm });
- } else if (command.startsWith('n')) {
- if (!'1234'.includes(command[1])) throw new Error(`Unsupported Command: ${command}`);
- const cmd = {} as NoteCommand;
- const type = command[1];
- cmd.type = Number(type);
- cmd.lineId = Number(data[ptr++]);
- cmd.time = Number(data[ptr++]);
- cmd.time2 = '2'.includes(type) ? Number(data[ptr++]) : cmd.time;
- cmd.offsetX = Number(data[ptr++]);
- cmd.isAbove = Number(data[ptr++]);
- cmd.isFake = Number(data[ptr++]);
- cmd.text = `n${Object.values(cmd).join(' ')}`;
- cmd.speed = (data[ptr++] || '').startsWith('#') ? Number(data[ptr++]) : (ptr--, 1);
- cmd.size = (data[ptr++] || '').startsWith('&') ? Number(data[ptr++]) : (ptr--, 1);
- data2.notes.push(cmd);
- } else if (command.startsWith('c')) {
- if (!'vpdamrf'.includes(command[1])) throw new Error(`Unsupported Command: ${command}`);
- const cmd = {} as LineCommand;
- const type = command[1];
- cmd.type = type;
- cmd.lineId = Number(data[ptr++]);
- cmd.time = Number(data[ptr++]);
- if ('v'.includes(type)) cmd.speed = Number(data[ptr++]);
- cmd.time2 = 'mrf'.includes(type) ? Number(data[ptr++]) : cmd.time;
- if ('pm'.includes(type)) cmd.offsetX = Number(data[ptr++]);
- if ('pm'.includes(type)) cmd.offsetY = Number(data[ptr++]);
- if ('dr'.includes(type)) cmd.rotation = Number(data[ptr++]);
- if ('af'.includes(type)) cmd.alpha = Number(data[ptr++]);
- if ('mr'.includes(type)) cmd.motionType = Number(data[ptr++]);
- cmd.text = `c${Object.values(cmd).join(' ')}`;
- if ('pdaf'.includes(type)) cmd.motionType = 1;
- data2.lines.push(cmd);
- } else throw new Error(`Unexpected Command: ${command}`);
- }
- result.offset = data2.offset / 1e3 - 0.175; // v18x固定延迟
- // bpm变速
- if (!data2.bpmList.length) throw new Error('Invalid pec file');
- const bpmList = new BpmList(data2.bpmList[0].bpm); // FIXME: 考虑合适的bpm范围
- data2.bpmList.sort((a, b) => a.time - b.time).forEach((i, idx, arr) => {
- if (arr[idx + 1]?.time <= 0) return; // 过滤负数
- bpmList.push(i.time < 0 ? 0 : i.time, arr[idx + 1]?.time ?? 1e9, i.bpm);
- });
- // note和判定线事件
- const linesPec = [] as LinePec[];
- for (const i of data2.notes) {
- const type = [0, 1, 4, 2, 3].indexOf(i.type);
- const time = bpmList.calc(i.time);
- const holdTime = bpmList.calc(i.time2) - time;
- const speed = isNaN(i.speed) ? 1 : i.speed;
- linesPec[i.lineId] ??= new LinePec(bpmList.baseBpm);
- linesPec[i.lineId].pushNote(type, time, i.offsetX / 115.2, holdTime, speed, i.isAbove === 1, i.isFake !== 0); // 102.4
- // if (i.isAbove !== 1 && i.isAbove !== 2) warnings.push(`检测到非法方向:${i.isAbove}(将被视为2)\n位于:"${i.text}"\n来自${filename}`);
- if (i.isFake !== 0) warnings.push(`检测到FakeNote(可能无法正常显示)\n位于:"${i.text}"\n来自${filename}`);
- if (i.size !== 1) warnings.push(`检测到异常Note(可能无法正常显示)\n位于:"${i.text}"\n来自${filename}`);
- }
- const isMotion = (i: number) => !isLinear(i) || i === 1;
- for (const i of data2.lines) {
- const t1 = bpmList.calc(i.time);
- const t2 = bpmList.calc(i.time2);
- if (t1 > t2) {
- warnings.push(`检测到开始时间大于结束时间(将禁用此事件)\n位于:"${i.text}"\n来自${filename}`);
- continue;
- }
- linesPec[i.lineId] ??= new LinePec(bpmList.baseBpm);
- // 变速
- if (i.type === 'v') linesPec[i.lineId].pushSpeedEvent(t1, i.speed / 7.0);
- // 不透明度
- if (i.type === 'a' || i.type === 'f') {
- linesPec[i.lineId].pushAlphaEvent(t1, t2, Math.max(i.alpha / 255, 0), i.motionType); // 暂不支持alpha值扩展
- if (i.alpha < 0) warnings.push(`检测到负数Alpha:${i.alpha}(将被视为0)\n位于:"${i.text}"\n来自${filename}`);
- }
- // 移动
- if (i.type === 'p' || i.type === 'm') {
- linesPec[i.lineId].pushMoveEvent(t1, t2, i.offsetX / 2048, i.offsetY / 1400, isMotion(i.motionType) ? i.motionType : 1);
- if (!isMotion(i.motionType)) warnings.push(`未知的缓动类型:${i.motionType}(将被视为1)\n位于:"${i.text}"\n来自${filename}`);
- }
- // 旋转
- if (i.type === 'd' || i.type === 'r') {
- linesPec[i.lineId].pushRotateEvent(t1, t2, -i.rotation, isMotion(i.motionType) ? i.motionType : 1);
- if (!isMotion(i.motionType)) warnings.push(`未知的缓动类型:${i.motionType}(将被视为1)\n位于:"${i.text}"\n来自${filename}`);
- }
- }
- for (const i of linesPec) {
- const judgeLine = i.format();
- result.judgeLineList.push(judgeLine);
- result.numOfNotes += judgeLine.numOfNotes!;
- }
- return { data: result, messages: warnings, format: 'PEC' };
-}
diff --git a/src/extends/pec/rpe2json.ts b/src/extends/pec/rpe2json.ts
deleted file mode 100644
index 459e1da9..00000000
--- a/src/extends/pec/rpe2json.ts
+++ /dev/null
@@ -1,653 +0,0 @@
-/* eslint-disable @typescript-eslint/no-unnecessary-condition */
-/* eslint-disable @typescript-eslint/naming-convention */
-import { type BeatArray, BpmList } from './BpmList';
-import { easing } from './easing.js';
-import { cubicBezier } from './cubicBezier.js';
-interface BpmEventRPE {
- startTime: BeatArray;
- bpm: number;
-}
-interface BpmEventRPEExtends extends BpmEventRPE {
- time: number;
-}
-interface LineEventPec {
- startTime: number;
- endTime: number;
- start: number;
- end: number;
-}
-interface LineEventPec2 extends LineEventPec {
- start2: number;
- end2: number;
-}
-interface LineEventPecDelta extends LineEventPec {
- delta: number;
-}
-interface SpeedEventPec {
- time: number;
- value: number;
-}
-interface LineEventRPE {
- startTime: BeatArray;
- endTime: BeatArray;
- start: number;
- end: number;
- easingType: number;
- linkgroup?: number;
- easingLeft?: number;
- easingRight?: number;
- bezier?: number;
- bezierPoints?: [number, number, number, number];
-}
-interface LineEventRPE2 {
- startTime: number;
- endTime: number;
- start: number;
- end: number;
- easingFn?: (t: number) => number;
-}
-const enum EasingCode {
- NoError = 0,
- EqualsLinear = 1,
- LeftEqualsRight = -1,
- TypeNotSupported = -2,
- ValueNotFinite = -3
-}
-interface EasingResult {
- code: EasingCode;
- fn?: (t: number) => number;
-}
-function getEasingFn(le: LineEventRPE, startTime: number, endTime: number): EasingResult {
- const { start, end, bezier = 0 } = le;
- if (Math.floor(bezier) === 1) return bezierFn();
- return clipFn();
- function clipFn() {
- const { easingType, easingLeft = 0, easingRight = 1 } = le;
- if (easingLeft === easingRight) return { code: EasingCode.LeftEqualsRight };
- if (start === end) return { code: EasingCode.EqualsLinear };
- const easingFn = easing(easingType);
- if (easingFn == null) return { code: easingType === 1 ? EasingCode.EqualsLinear : EasingCode.TypeNotSupported };
- const eHead = easingFn(easingLeft);
- const eTail = easingFn(easingRight);
- const eSpeed = (easingRight - easingLeft) / (endTime - startTime);
- const e1Delta = (end - start) / (eTail - eHead);
- if (!isFinite(e1Delta)) return { code: EasingCode.ValueNotFinite };
- return { code: EasingCode.NoError, fn: (t: number) => (easingFn((t - startTime) * eSpeed + easingLeft) - eHead) * e1Delta };
- }
- function bezierFn() {
- if (start === end) return { code: EasingCode.EqualsLinear };
- const { bezierPoints: [p1x, p1y, p2x, p2y] = [0, 0, 0, 0] } = le;
- if (p1x === p1y && p2x === p2y) return { code: EasingCode.EqualsLinear };
- const eSpeed = 1 / (endTime - startTime);
- const e1Delta = end - start;
- const easingFn = cubicBezier(p1x, p1y, p2x, p2y);
- return { code: EasingCode.NoError, fn: (t: number) => easingFn((t - startTime) * eSpeed) * e1Delta };
- }
-}
-function pushLineEvent(ls: LineEventPecDelta[], le: LineEventRPE2) {
- const { startTime, endTime, start, end, easingFn } = le;
- const delta = (end - start) / (endTime - startTime);
- // 插入之前考虑事件时间的相互关系
- for (let i = ls.length - 1; i >= 0; i--) {
- const e = ls[i];
- if (e.endTime < startTime) { // 相离:补全空隙
- ls[i + 1] = { startTime: e.endTime, endTime: startTime, start: e.end, end: e.end, delta: 0 };
- break;
- }
- if (e.startTime === startTime) { // 相切:直接截断
- ls.length = i;
- break;
- }
- if (e.startTime < startTime) { // 相交:截断交点以后的部分
- e.end = e.start + (startTime - e.startTime) * e.delta;
- e.endTime = startTime;
- e.delta = (e.end - e.start) / (startTime - e.startTime);
- ls.length = i + 1;
- break;
- }
- }
- // 插入新事件
- if (startTime >= endTime) {
- ls.push({ startTime, endTime: startTime, start: end, end, delta: 0 });
- return;
- }
- if (easingFn == null) {
- ls.push({ startTime, endTime, start, end, delta });
- } else {
- let v1 = 0;
- let v2 = 0;
- for (let j = startTime; j < endTime; j++) {
- v1 = v2;
- v2 = easingFn(j + 1);
- ls.push({ startTime: j, endTime: j + 1, start: start + v1, end: start + v2, delta: v2 - v1 });
- }
- }
-}
-function toSpeedEvent(le: LineEventRPE2[]) {
- const result = [];
- for (const i of le) {
- const { startTime, endTime, start, end } = i;
- result.push({ time: startTime, value: start });
- if (start !== end) { // 暂未考虑开始时间大于结束时间的情况
- const t1 = (end - start) / (endTime - startTime);
- for (let j = startTime; j < endTime; j++) {
- const x = j + 0.5 - startTime;
- result.push({ time: j, value: start + x * t1 });
- }
- result.push({ time: endTime, value: end });
- }
- }
- return result;
-}
-function getEventsValue(e: LineEventPecDelta[], t: number, d: boolean) {
- let result = e[0]?.start ?? 0;
- for (const i of e) {
- const { startTime, endTime, start, end, delta } = i;
- if (t < startTime) break;
- if (d && t === startTime) break;
- if (t >= endTime) result = end;
- else result = start + (t - startTime) * delta;
- }
- return result;
-}
-function getMoveValue(e: LineEventPec2[], t: number, d: boolean) {
- let result = e[0]?.start ?? 0;
- let result2 = e[0]?.start2 ?? 0;
- for (const i of e) {
- const { startTime, endTime, start, end, start2, end2 } = i;
- if (t < startTime) break;
- if (d && t === startTime) break;
- if (t >= endTime) {
- result = end;
- result2 = end2;
- } else {
- result = start + (t - startTime) * (end - start) / (endTime - startTime);
- result2 = start2 + (t - startTime) * (end2 - start2) / (endTime - startTime);
- }
- }
- return [result, result2];
-}
-function getRotateValue(e: LineEventRPE2[], t: number, d: boolean) {
- let result = e[0]?.start ?? 0;
- for (const i of e) {
- const { startTime, endTime, start, end } = i;
- if (t < startTime) break;
- if (d && t === startTime) break;
- if (t >= endTime) result = end;
- else result = start + (t - startTime) * (end - start) / (endTime - startTime);
- }
- return result;
-}
-function combineXYEvents(xe: LineEventPecDelta[], ye: LineEventPecDelta[]) {
- const le = [];
- const splits = [];
- for (const i of xe) splits.push(i.startTime, i.endTime);
- for (const i of ye) splits.push(i.startTime, i.endTime);
- splits.sort((a, b) => a - b);
- for (let i = 0; i < splits.length - 1; i++) {
- const startTime = splits[i];
- const endTime = splits[i + 1];
- if (startTime === endTime) continue;
- const startX = getEventsValue(xe, startTime, false);
- const endX = getEventsValue(xe, endTime, true);
- const startY = getEventsValue(ye, startTime, false);
- const endY = getEventsValue(ye, endTime, true);
- le.push({ startTime, endTime, start: startX, end: endX, start2: startY, end2: endY });
- }
- return le;
-}
-function combineMultiEvents(es: LineEventPecDelta[][]) {
- const le = [];
- const splits = [];
- for (const e of es) {
- for (const i of e) splits.push(i.startTime, i.endTime);
- }
- splits.sort((a, b) => a - b);
- for (let i = 0; i < splits.length - 1; i++) {
- const startTime = splits[i];
- const endTime = splits[i + 1];
- if (startTime === endTime) continue;
- const start = es.reduce((n, e) => n + getEventsValue(e, startTime, false), 0);
- const end = es.reduce((n, e) => n + getEventsValue(e, endTime, true), 0);
- le.push({ startTime, endTime, start, end, delta: (end - start) / (endTime - startTime) });
- }
- return le;
-}
-function mergeFather(child: LineRPE1, father: LineRPE1) {
- const moveEvents = [];
- const splits = [];
- for (const i of father.moveEvents!) splits.push(i.startTime, i.endTime);
- for (const i of father.rotateEvents!) splits.push(i.startTime, i.endTime);
- for (const i of child.moveEvents!) splits.push(i.startTime, i.endTime);
- splits.sort((a, b) => a - b);
- for (let i = splits[0]; i < splits[splits.length - 1]; i++) {
- const startTime = i;
- const endTime = i + 1;
- if (startTime === endTime) continue;
- // 计算父级移动和旋转
- const [fatherX, fatherY] = getMoveValue(father.moveEvents!, startTime, false);
- const fatherR = getRotateValue(father.rotateEvents!, startTime, false) * -Math.PI / 180;
- const [fatherX2, fatherY2] = getMoveValue(father.moveEvents!, endTime, true);
- const fatherR2 = getRotateValue(father.rotateEvents!, endTime, true) * -Math.PI / 180;
- // 计算子级移动
- const [childX, childY] = getMoveValue(child.moveEvents!, startTime, false);
- const [childX2, childY2] = getMoveValue(child.moveEvents!, endTime, true);
- // 坐标转换
- const start = fatherX + childX * Math.cos(fatherR) - childY * Math.sin(fatherR);
- const end = fatherX2 + childX2 * Math.cos(fatherR2) - childY2 * Math.sin(fatherR2);
- const start2 = fatherY + childX * Math.sin(fatherR) + childY * Math.cos(fatherR);
- const end2 = fatherY2 + childX2 * Math.sin(fatherR2) + childY2 * Math.cos(fatherR2);
- moveEvents.push({ startTime, endTime, start, end, start2, end2 });
- }
- child.moveEvents = moveEvents;
-}
-interface EventLayer {
- moveXEvents?: LineEventRPE[];
- moveYEvents?: LineEventRPE[];
- rotateEvents?: LineEventRPE[];
- alphaEvents?: LineEventRPE[];
- speedEvents?: LineEventRPE[];
-}
-class EventLayer1 {
- public moveXEvents: LineEventRPE2[];
- public moveYEvents: LineEventRPE2[];
- public rotateEvents: LineEventRPE2[];
- public alphaEvents: LineEventRPE2[];
- public speedEvents: LineEventRPE2[];
- public constructor() {
- this.moveXEvents = [];
- this.moveYEvents = [];
- this.rotateEvents = [];
- this.alphaEvents = [];
- this.speedEvents = [];
- }
- public pushMoveXEvent(startTime: number, endTime: number, start: number, end: number, easingFn?: (t: number) => number) {
- this.moveXEvents.push({ startTime, endTime, start, end, easingFn });
- }
- public pushMoveYEvent(startTime: number, endTime: number, start: number, end: number, easingFn?: (t: number) => number) {
- this.moveYEvents.push({ startTime, endTime, start, end, easingFn });
- }
- public pushRotateEvent(startTime: number, endTime: number, start: number, end: number, easingFn?: (t: number) => number) {
- this.rotateEvents.push({ startTime, endTime, start, end, easingFn });
- }
- public pushAlphaEvent(startTime: number, endTime: number, start: number, end: number, easingFn?: (t: number) => number) {
- this.alphaEvents.push({ startTime, endTime, start, end, easingFn });
- }
- public pushSpeedEvent(startTime: number, endTime: number, start: number, end: number) {
- this.speedEvents.push({ startTime, endTime, start, end });
- }
-}
-interface NoteRPE {
- type: number;
- startTime: BeatArray;
- endTime: BeatArray;
- positionX: number;
- above: number;
- isFake: number;
- speed: number;
- size: number;
- yOffset: number;
- visibleTime: number;
- alpha: number;
-}
-interface NoteRPE1 {
- type: number;
- time: number;
- positionX: number;
- holdTime: number;
- speed: number;
- isAbove: boolean;
- isFake: boolean;
- isHidden: boolean;
-}
-class LineRPE1 {
- public bpm: number;
- public notes: NoteRPE1[];
- public eventLayers: EventLayer1[];
- public id?: number;
- public father: LineRPE1 | null;
- public moveEvents?: LineEventPec2[];
- public rotateEvents?: LineEventPec[];
- public alphaEvents?: LineEventPec[];
- public speedEvents?: SpeedEventPec[];
- private settled: boolean;
- private merged: boolean;
- public constructor(bpm: number) {
- this.bpm = 120;
- this.notes = [];
- this.eventLayers = [];
- this.father = null;
- this.settled = false;
- this.merged = false;
- if (!isNaN(bpm)) this.bpm = bpm;
- }
- public pushNote(type: number, time: number, positionX: number, holdTime: number, speed: number, isAbove: boolean, isFake: boolean, isHide: boolean) {
- this.notes.push({ type, time, positionX, holdTime, speed, isAbove, isFake, isHidden: isHide });
- }
- public setId(id = NaN) {
- this.id = id;
- }
- public setFather(fatherLine: LineRPE1 | null) {
- this.father = fatherLine;
- }
- public preset() {
- const sortFn2 = (a: { startTime: number }, b: { startTime: number }) => a.startTime - b.startTime;
- const events = [];
- for (const e of this.eventLayers) {
- const moveXEvents: LineEventPecDelta[] = [];
- const moveYEvents: LineEventPecDelta[] = [];
- const rotateEvents: LineEventPecDelta[] = [];
- const alphaEvents: LineEventPecDelta[] = [];
- const speedEvents: LineEventPecDelta[] = [];
- for (const i of e.moveXEvents.sort(sortFn2)) pushLineEvent(moveXEvents, i);
- for (const i of e.moveYEvents.sort(sortFn2)) pushLineEvent(moveYEvents, i);
- for (const i of e.rotateEvents.sort(sortFn2)) pushLineEvent(rotateEvents, i);
- for (const i of e.alphaEvents.sort(sortFn2)) pushLineEvent(alphaEvents, i);
- for (const i of e.speedEvents.sort(sortFn2)) pushLineEvent(speedEvents, i); // TODO: 特殊处理
- events.push({ moveXEvents, moveYEvents, rotateEvents, alphaEvents, speedEvents });
- }
- const moveXEvents = combineMultiEvents(events.map(i => i.moveXEvents));
- const moveYEvents = combineMultiEvents(events.map(i => i.moveYEvents));
- this.moveEvents = combineXYEvents(moveXEvents, moveYEvents);
- this.rotateEvents = combineMultiEvents(events.map(i => i.rotateEvents));
- this.alphaEvents = combineMultiEvents(events.map(i => i.alphaEvents));
- this.speedEvents = toSpeedEvent(combineMultiEvents(events.map(i => i.speedEvents)));
- this.settled = true;
- }
- public fitFather(stack: LineRPE1[] = [], onwarning = console.warn) {
- if (!this.settled) this.preset();
- if (stack.includes(this)) {
- onwarning(`检测到循环继承:${stack.concat(this).map(i => i.id).join('->')}(对应的father将被视为-1)`);
- stack.map(i => i.setFather(null));
- return;
- }
- if (this.father) this.father.fitFather(stack.concat(this), onwarning);
- // this.father可能会在上一行被修改成null
- if (this.father && !this.merged) {
- mergeFather(this, this.father);
- this.merged = true;
- }
- }
- public format({ onwarning = console.warn } = {}) {
- this.fitFather([], onwarning);
- const result = {
- bpm: this.bpm,
- speedEvents: [] as SpeedEventPGS[],
- numOfNotes: 0,
- numOfNotesAbove: 0,
- numOfNotesBelow: 0,
- notesAbove: [] as NotePGS[],
- notesBelow: [] as NotePGS[],
- judgeLineDisappearEvents: [] as JudgeLineEventPGS[],
- judgeLineMoveEvents: [] as JudgeLineEventPGS[],
- judgeLineRotateEvents: [] as JudgeLineEventPGS[]
- };
- for (const i of this.moveEvents!) {
- result.judgeLineMoveEvents.push({
- startTime: i.startTime,
- endTime: i.endTime,
- start: (i.start + 675) / 1350,
- end: (i.end + 675) / 1350,
- start2: (i.start2 + 450) / 900,
- end2: (i.end2 + 450) / 900
- });
- }
- for (const i of this.rotateEvents!) {
- result.judgeLineRotateEvents.push({
- startTime: i.startTime,
- endTime: i.endTime,
- start: -i.start,
- end: -i.end,
- start2: 0,
- end2: 0
- });
- }
- for (const i of this.alphaEvents!) {
- result.judgeLineDisappearEvents.push({
- startTime: i.startTime,
- endTime: i.endTime,
- start: Math.max(0, i.start / 255),
- end: Math.max(0, i.end / 255),
- start2: 0,
- end2: 0
- });
- }
- // 添加floorPosition
- let floorPos = 0;
- let minPos = 0;
- const speedEvents = this.speedEvents!;
- for (let i = 0; i < speedEvents.length; i++) {
- const startTime = Math.max(speedEvents[i].time, 0);
- const endTime = i < speedEvents.length - 1 ? speedEvents[i + 1].time : 1e9;
- const value = speedEvents[i].value * 2 / 9;
- result.speedEvents.push({ startTime, endTime, value, floorPosition: floorPos, floorPositionMin: minPos });
- floorPos += (endTime - startTime) * value / this.bpm * 1.875;
- floorPos = Math.fround(floorPos);
- minPos = Math.min(minPos, floorPos);
- }
- // 处理notes
- const sortFn = (a: { time: number }, b: { time: number }) => a.time - b.time;
- const getPositionValues = (time: number) => {
- let v1 = 0;
- let v2 = 0;
- let v3 = 0;
- let vmin = 0;
- for (const e of result.speedEvents) {
- if (time > e.endTime) continue;
- if (time < e.startTime) break;
- v1 = e.floorPosition!;
- v2 = e.value;
- v3 = time - e.startTime;
- vmin = e.floorPositionMin!;
- }
- return { v1, v4: v2 * v3, vmin };
- };
- const getHoldSpeedValue = (time: number, holdTime: number) => {
- const start = getPositionValues(time);
- const end = getPositionValues(time + holdTime);
- return ((end.v1 - start.v1) / 1.875 * this.bpm + (end.v4 - start.v4)) / holdTime;
- };
- for (const i of this.notes.sort(sortFn)) {
- const { v1, v4, vmin } = getPositionValues(i.time);
- const speedFactor = i.type === 3 ? getHoldSpeedValue(i.time, i.holdTime) : 1;
- const floorPosition = Math.fround(v1 + v4 / this.bpm * 1.875);
- const note = {
- type: i.type,
- time: i.time + (i.isFake ? 1e9 : 0),
- positionX: i.positionX,
- holdTime: i.holdTime,
- speed: i.speed * speedFactor,
- floorPosition
- };
- if (i.isHidden) {
- note.speed = 0;
- note.floorPosition = Math.min(vmin, floorPosition) - 1;
- }
- if (i.isAbove) {
- result.notesAbove.push(note);
- if (i.isFake) continue;
- result.numOfNotes++;
- result.numOfNotesAbove++;
- } else {
- result.notesBelow.push(note);
- if (i.isFake) continue;
- result.numOfNotes++;
- result.numOfNotesBelow++;
- }
- }
- return result;
- }
-}
-interface RPEMeta {
- RPEVersion: string;
- song: string;
- background: string;
- name: string;
- composer: string;
- charter: string;
- level: string;
- offset: number;
-}
-interface RPEDataBase {
- BPMList: BpmEventRPEExtends[];
- judgeLineList: JudgeLineRPEExtends[];
-}
-type RPEDataLegacy = RPEDataBase & RPEMeta & { META: undefined };
-type RPEDataCurrent = RPEDataBase & { META: RPEMeta };
-type RPEData = RPEDataCurrent | RPEDataLegacy;
-interface JudgeLineRPE {
- numOfNotes: number;
- isCover: number;
- Texture: string;
- eventLayers: (EventLayer | null)[];
- extended?: {
- scaleXEvents: LineEventRPE[];
- scaleYEvents: LineEventRPE[];
- } | null;
- notes: NoteRPE[] | null;
- Group: number;
- Name: string;
- zOrder: number;
- bpmfactor: number;
- father: number;
-}
-interface JudgeLineRPEExtends extends JudgeLineRPE {
- LineId: number;
- judgeLineRPE: LineRPE1;
-}
-export function parse(pec: string, filename: string): {
- data: ChartPGS;
- messages: BetterMessage[];
- info: Record;
- line: Record[];
- format: string;
-} {
- const data = JSON.parse(pec) as RPEData;
- const meta = data.META || data;
- if (!meta?.RPEVersion) throw new Error('Invalid rpe file');
- const result = { formatVersion: 3, offset: 0, numOfNotes: 0, judgeLineList: [] } as ChartPGS;
- const warnings = [] as BetterMessage[];
- const warn = (code: number, name: string, message: string) => warnings.push({ host: 'RPE2JSON', code, name, message, target: filename });
- warn(0, 'RPEVersionNotice', `RPE谱面兼容建设中...\n检测到RPE版本:${meta.RPEVersion}`);
- const format = `RPE(${meta.RPEVersion})`;
- // 谱面信息
- const info: Record = {};
- info.Chart = filename;
- info.Music = meta.song;
- info.Image = meta.background;
- info.Name = meta.name;
- info.Artist = meta.composer;
- info.Charter = meta.charter;
- info.Level = meta.level;
- result.offset = meta.offset / 1e3;
- // 判定线贴图(WIP)
- const line: Record[] = [];
- data.judgeLineList.forEach((i: JudgeLineRPEExtends, index: number) => {
- i.LineId = index;
- const texture = String(i.Texture).replace(/\0/g, '');
- if (texture === 'line.png') return;
- const { extended } = i;
- const scaleX = extended?.scaleXEvents ? extended.scaleXEvents[extended.scaleXEvents.length - 1].end : 1;
- const scaleY = extended?.scaleYEvents ? extended.scaleYEvents[extended.scaleYEvents.length - 1].end : 1;
- line.push({
- Chart: filename,
- LineId: index.toString(),
- Image: texture,
- Scale: scaleY.toString(),
- Aspect: (scaleX / scaleY).toString(),
- UseBackgroundDim: '0',
- UseLineColor: '1',
- UseLineScale: '1'
- });
- });
- // bpm变速
- const bpmList = new BpmList(data.BPMList[0].bpm);
- for (const i of data.BPMList) i.time = i.startTime[0] + i.startTime[1] / i.startTime[2];
- data.BPMList.sort((a: { time: number }, b: { time: number }) => a.time - b.time).forEach((i, idx, arr) => {
- if (arr[idx + 1]?.time <= 0) return; // 过滤负数
- bpmList.push(i.time < 0 ? 0 : i.time, arr[idx + 1]?.time ?? 1e9, i.bpm);
- });
- for (const i of data.judgeLineList) {
- if (i.zOrder === undefined) i.zOrder = 0;
- if (i.bpmfactor === undefined) i.bpmfactor = 1;
- if (i.father === undefined) i.father = -1;
- if (i.isCover !== 1) warn(1, 'ImplementionWarning', `未兼容isCover=${i.isCover}(可能无法正常显示)\n位于${i.LineId}号判定线`);
- if (i.zOrder !== 0) warn(1, 'ImplementionWarning', `未兼容zOrder=${i.zOrder}(可能无法正常显示)\n位于${i.LineId}号判定线`);
- if (i.bpmfactor !== 1) warn(1, 'ImplementionWarning', `未兼容bpmfactor=${i.bpmfactor}(可能无法正常显示)\n位于${i.LineId}号判定线`);
- const lineRPE = new LineRPE1(bpmList.baseBpm);
- lineRPE.setId(i.LineId);
- if (i.notes) {
- for (const note of i.notes) {
- if (note.alpha === undefined) note.alpha = 255;
- if (note.above !== 1 && note.above !== 2) warn(1, 'NoteSideWarning', `检测到非法方向:${note.above}(将被视为2)\n位于:"${JSON.stringify(note)}"`);
- if (note.isFake !== 0) warn(1, 'NoteFakeWarning', `检测到FakeNote(可能无法正常显示)\n位于:"${JSON.stringify(note)}"`);
- if (note.size !== 1) warn(1, 'ImplementionWarning', `未兼容size=${note.size}(可能无法正常显示)\n位于:"${JSON.stringify(note)}"`);
- if (note.yOffset !== 0) warn(1, 'ImplementionWarning', `未兼容yOffset=${note.yOffset}(可能无法正常显示)\n位于:"${JSON.stringify(note)}"`);
- if (note.visibleTime !== 999999) warn(1, 'ImplementionWarning', `未兼容visibleTime=${note.visibleTime}(可能无法正常显示)\n位于:"${JSON.stringify(note)}"`);
- if (note.alpha !== 255 && note.alpha !== 0) warn(1, 'ImplementionWarning', `未兼容alpha=${note.alpha}(可能无法正常显示)\n位于:"${JSON.stringify(note)}"`);
- const type = [0, 1, 4, 2, 3].indexOf(note.type);
- const time = bpmList.calc2(note.startTime);
- const holdTime = bpmList.calc2(note.endTime) - time;
- const { speed } = note;
- const positionX = note.positionX / 75.375;
- lineRPE.pushNote(type, time, positionX, holdTime, speed, note.above === 1, note.isFake !== 0, note.alpha === 0);
- }
- }
- for (const e of i.eventLayers) {
- if (!e) continue; // 有可能是null
- const layer = new EventLayer1();
- for (const j of e.moveXEvents || []) {
- const startTime = bpmList.calc2(j.startTime);
- const endTime = bpmList.calc2(j.endTime);
- const { fn, code } = getEasingFn(j, startTime, endTime);
- getWarning(code, j);
- layer.pushMoveXEvent(startTime, endTime, j.start, j.end, fn);
- }
- for (const j of e.moveYEvents || []) {
- const startTime = bpmList.calc2(j.startTime);
- const endTime = bpmList.calc2(j.endTime);
- const { fn, code } = getEasingFn(j, startTime, endTime);
- getWarning(code, j);
- layer.pushMoveYEvent(startTime, endTime, j.start, j.end, fn);
- }
- for (const j of e.rotateEvents || []) {
- const startTime = bpmList.calc2(j.startTime);
- const endTime = bpmList.calc2(j.endTime);
- const { fn, code } = getEasingFn(j, startTime, endTime);
- getWarning(code, j);
- layer.pushRotateEvent(startTime, endTime, j.start, j.end, fn);
- }
- for (const j of e.alphaEvents || []) {
- const startTime = bpmList.calc2(j.startTime);
- const endTime = bpmList.calc2(j.endTime);
- const { fn, code } = getEasingFn(j, startTime, endTime);
- getWarning(code, j);
- layer.pushAlphaEvent(startTime, endTime, j.start, j.end, fn);
- }
- for (const j of e.speedEvents || []) {
- const startTime = bpmList.calc2(j.startTime);
- const endTime = bpmList.calc2(j.endTime);
- layer.pushSpeedEvent(startTime, endTime, j.start, j.end);
- }
- lineRPE.eventLayers.push(layer);
- }
- i.judgeLineRPE = lineRPE;
- }
- for (const i of data.judgeLineList) {
- const lineRPE = i.judgeLineRPE; // TODO: 待优化
- const father = data.judgeLineList[i.father];
- lineRPE.setFather(father?.judgeLineRPE);
- }
- for (const i of data.judgeLineList) {
- const lineRPE = i.judgeLineRPE; // TODO: 待优化
- const judgeLine = lineRPE.format({ onwarning: (msg: string) => warn(1, 'OtherWarning', `${msg}`) });
- result.judgeLineList.push(judgeLine);
- result.numOfNotes! += judgeLine.numOfNotes;
- }
- return { data: result, messages: warnings, info, line, format };
- function getWarning(code: EasingCode, le: LineEventRPE) {
- if (code === EasingCode.TypeNotSupported) warn(1, 'EasingTypeWarning', `未知的缓动类型:${le.easingType}(将被视为1)\n位于:"${JSON.stringify(le)}"`);
- if (code === EasingCode.LeftEqualsRight) warn(1, 'EasingClipWarning', `检测到easingLeft等于easingRight(将被视为线性)\n位于:"${JSON.stringify(le)}"`);
- if (code === EasingCode.ValueNotFinite) warn(1, 'EasingClipWarning', `非法的缓动函数(将被视为线性)\n位于:"${JSON.stringify(le)}"`);
- }
-}
diff --git a/src/index.ts b/src/index.ts
index d71f84b6..03dba97f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,38 +1,41 @@
import './style.css';
import { lastupdate, pubdate, version } from '../scripts/meta.json';
-import { full, orientation } from './js/common.js';
-import { FrameAnimater } from './components/FrameAnimater';
-import { FrameTimer } from './components/FrameTimer';
-import { Timer } from './components/Timer';
-import { FileEmitter, ZipReader, reader } from './utils/reader';
-import { type JudgeLineExtends, type NoteExtends, Renderer } from './core';
-import { HitManager, JudgeEvent } from './components/HitManager';
-import { Stat } from './components/Stat';
-import { InteractProxy, audio } from './external';
-import createCtx from './utils/createCtx';
-import { ImgAny, imgBlur, imgPainter, imgShader, imgSplit } from './utils/ImageTools';
-import { Checkbox, HitEvents, HitFeedback, HitImage, HitWord, ScaledNote, StatusManager } from './components';
-import { Stage } from './components/Stage';
-import { checkSupport } from './utils/checkSupport';
-import { adjustSize } from './utils/adjustSize';
-import { fixme } from './utils/fixme';
-import { MessageHandler } from './components/MessageHandler';
+import { full, orientation } from '@/js/common.js';
+import { FrameAnimater } from '@/components/FrameAnimater';
+import { FrameTimer } from '@/components/FrameTimer';
+import { Timer } from '@/components/Timer';
+import { FileEmitter, ZipReader, reader } from '@/utils/reader';
+import { Renderer } from '@/core';
+import { HitManager, JudgeEvent } from '@/components/HitManager';
+import { Stat } from '@/components/Stat';
+import { InteractProxy, audio } from '@/external';
+import createCtx from '@/utils/createCtx';
+import { ImgAny, imgBlur, imgPainter, imgShader, imgSplit } from '@/utils/ImageTools';
+import { Checkbox, HitEvents, HitFeedback, HitImage, HitWord, ScaledNote, StatusManager } from '@/components';
+import { Stage } from '@/components/Stage';
+import { checkSupport } from '@/utils/checkSupport';
+import { adjustSize } from '@/utils/adjustSize';
+import { fixme } from '@/utils/fixme';
+import { MessageHandler } from '@/components/MessageHandler';
self._i = ['Phixos', version.split('.'), pubdate, lastupdate];
const $id = (query: string): HTMLElement => document.getElementById(query) || (() => { throw new Error(`Cannot find element: ${query}`) })();
const $ = (query: string) => document.body.querySelector(query);
const $$ = (query: string) => document.body.querySelectorAll(query);
-// const viewRsmg = $id('view-rsmg') as HTMLDivElement;
const viewNav = $id('view-nav') as HTMLDivElement;
const viewCfg = $id('view-cfg') as HTMLDivElement;
const viewMsg = $id('view-msg') as HTMLDivElement;
+const viewNav2 = $id('view-nav2') as HTMLDivElement;
+const viewRmg = $id('view-rmg') as HTMLDivElement;
+const viewExt = $id('view-ext') as HTMLDivElement;
const coverDark = $id('cover-dark') as HTMLDivElement;
-const coverRsmg = $id('cover-rsmg') as HTMLDivElement;
+const coverRmg = $id('cover-rmg') as HTMLDivElement;
const coverView = $id('cover-view') as HTMLDivElement;
-const buttonRsmg = $id('btn-rsmg') as HTMLInputElement;
+const buttonRmg = $id('btn-rmg') as HTMLInputElement;
const buttonDocs = $id('btn-docs') as HTMLInputElement;
const buttonMore = $id('btn-more') as HTMLInputElement;
const anchorCfg = $id('nav-cfg') as HTMLAnchorElement;
const anchorMsg = $id('nav-msg') as HTMLAnchorElement;
+const anchorRmg = $id('nav-rmg') as HTMLAnchorElement;
const strongOut = $id('msg-out');
const blockUploader = $id('uploader') as HTMLDivElement;
const stageEl = $id('stage') as HTMLDivElement;
@@ -131,14 +134,22 @@ for (const i of viewNav.children) {
viewMsg.classList.toggle('hide', this.id !== 'nav-msg');
});
}
+for (const i of viewNav2.children) {
+ i.addEventListener('click', function(this: HTMLElement) {
+ for (const j of viewNav2.children) j.classList.toggle('active', j === this);
+ viewRmg.classList.toggle('hide', this.id !== 'nav-rmg');
+ viewExt.classList.toggle('hide', this.id !== 'nav-ext');
+ });
+}
coverDark.addEventListener('click', () => {
coverDark.classList.add('fade');
- coverRsmg.classList.add('fade');
+ coverRmg.classList.add('fade');
coverView.classList.add('fade');
});
-buttonRsmg.addEventListener('click', () => {
+buttonRmg.addEventListener('click', () => {
coverDark.classList.remove('fade');
- coverRsmg.classList.remove('fade');
+ coverRmg.classList.remove('fade');
+ anchorRmg.click();
});
buttonDocs.addEventListener('click', () => {
main.fireModal('提示
', '点击此处查看使用说明
');
@@ -400,7 +411,7 @@ const uploader = new FileEmitter();
}
}());
main.uploader = uploader;
-import('@/demo/index.js').then(a => a.default());
+import('@/plugins/demo/index.js').then(a => a.default());
// Hit start
const hitManager = new HitManager();
const exitFull = () => {
@@ -454,12 +465,12 @@ const specialClick = {
if (timeEnd.second > 0) main.pressTime = main.pressTime > 0 ? -timeEnd.second : timeEnd.second;
}
};
-function getJudgeOffset(judgeEvent: JudgeEvent, note: NoteExtends) {
+function getJudgeOffset(judgeEvent: JudgeEvent, note: Renderer.Note) {
const { offsetX, offsetY } = judgeEvent;
const { offsetX: x, offsetY: y, cosr, sinr } = note;
return Math.abs((offsetX - x) * cosr + (offsetY - y) * sinr) || 0;
}
-function getJudgeDistance(judgeEvent: JudgeEvent, note: NoteExtends) {
+function getJudgeDistance(judgeEvent: JudgeEvent, note: Renderer.Note) {
const { offsetX, offsetY } = judgeEvent;
const { offsetX: x, offsetY: y, cosr, sinr } = note;
return Math.abs((offsetX - x) * cosr + (offsetY - y) * sinr) + Math.abs((offsetX - x) * sinr - (offsetY - y) * cosr) || 0;
@@ -548,7 +559,7 @@ const hitWordList = new HitEvents({
});
const judgeManager = {
list: [] as JudgeEvent[],
- addEvent(notes: NoteExtends[], seconds: number) {
+ addEvent(notes: Renderer.Note[], seconds: number) {
const { list } = this;
list.length = 0;
if (app.playMode === 1) {
@@ -575,7 +586,7 @@ const judgeManager = {
}
}
},
- execute(notes: NoteExtends[], seconds: number, width: number) {
+ execute(notes: Renderer.Note[], seconds: number, width: number) {
const { list } = this;
for (const note of notes) {
if (note.scored) continue; // 跳过已判分的Note
@@ -845,7 +856,7 @@ function getPos(obj: MouseEvent | Touch) {
};
}
// Hit end
-export const noteRender = {
+const noteRender = {
note: {} as Record,
hitFX: {} as Record,
async update(name: string, img: ImageBitmap, scale: number, compacted = false): Promise {
@@ -888,7 +899,7 @@ window.addEventListener('load', (): void => {
lockOri.label.textContent += '(当前设备或浏览器不支持)';
}
})) return;
- await import('./utils/reader-');
+ await import('@/utils/reader-');
const raw = await loadResource(atob('aHR0cHM6Ly9sY2h6aC5uZXQvZGF0YS9wYWNrLmpzb24=')).catch(() => null) || {
// const raw = await loadResource('local/ptres.json').catch(() => null) || {
image: {} as Record,
@@ -1140,7 +1151,6 @@ function loopNoCanvas() {
tmps.customForeDraw = null;
tmps.customBackDraw = null;
}
-// import { app, adjustSize, isInEnd, main, atIn, stat, drawLine, atOut, drawNotes, showPoint, timeChart, hitImageList, showCE2, hitWordList, tween, res, tmps, fillTextNode, lineColor, showAcc, nowTimeMS, checkFeedback, hitFeedbackList, time2Str, duration0, timeBgm, status2, frameTimer, showStat } from './index';
function loopCanvas() {
const { lineScale, wlen, hlen } = app;
const { bgImage, bgVideo } = tmps;
@@ -1416,7 +1426,7 @@ function drawNotes() {
for (const i of app.tapsReversed) drawTap(i);
for (const i of app.flicksReversed) drawFlick(i);
}
-function drawTap(note: NoteExtends) {
+function drawTap(note: Renderer.Note) {
const HL = note.isMulti && app.multiHint;
const nsr = app.noteScaleRatio;
if (!note.visible || note.scored && note.badTime == null) return;
@@ -1431,7 +1441,7 @@ function drawTap(note: NoteExtends) {
noteRender.note.TapBad.full(ctxfg);
}
}
-function drawDrag(note: NoteExtends) {
+function drawDrag(note: Renderer.Note) {
const HL = note.isMulti && app.multiHint;
const nsr = app.noteScaleRatio;
if (!note.visible || note.scored && note.badTime == null) return;
@@ -1444,7 +1454,7 @@ function drawDrag(note: NoteExtends) {
// Nothing to do
}
}
-function drawHold(note: NoteExtends, seconds: number) {
+function drawHold(note: Renderer.Note, seconds: number) {
const HL = note.isMulti && app.multiHint;
const nsr = app.noteScaleRatio;
if (!note.visible || note.seconds + note.holdSeconds < seconds) return; // 不绘制时空超界的Hold
@@ -1459,7 +1469,7 @@ function drawHold(note: NoteExtends, seconds: number) {
} else noteRender.note[HL ? 'HoldHL' : 'Hold'].body(ctxfg, -holdLength, holdLength - baseLength * (seconds - note.seconds));
noteRender.note.HoldEnd.tail(ctxfg, -holdLength);
}
-function drawFlick(note: NoteExtends) {
+function drawFlick(note: Renderer.Note) {
const HL = note.isMulti && app.multiHint;
const nsr = app.noteScaleRatio;
if (!note.visible || note.scored && note.badTime == null) return;
@@ -1687,7 +1697,7 @@ async function loadLineData({
onwarn('未指定判定线id');
continue;
}
- const line = app.lines[Number(i.lineId)] as JudgeLineExtends | null;
+ const line = app.lines[Number(i.lineId)] as Renderer.JudgeLine | null;
if (line == null) {
onwarn(`指定id的判定线不存在:${i.lineId}`);
continue;
@@ -1796,14 +1806,14 @@ main.use = async m => {
console.log(module);
return module;
};
-main.use(import('@/phizone.js') as unknown as Promise);
-main.use(import('@/tips.js') as unknown as Promise);
-main.use(import('@/filter.js') as unknown as Promise);
-main.use(import('@/skin.js') as unknown as Promise);
-main.use(import('@/export.js') as unknown as Promise);
-main.use(import('@/gauge.js') as unknown as Promise);
-main.use(import('@/dynamic-score.js') as unknown as Promise);
-main.use(import('@/video-recorder.js') as unknown as Promise);
+main.use(import('@/plugins/phizone.js') as unknown as Promise);
+main.use(import('@/plugins/tips.js') as unknown as Promise);
+main.use(import('@/plugins/filter.js') as unknown as Promise);
+main.use(import('@/plugins/skin.js') as unknown as Promise);
+main.use(import('@/plugins/export.js') as unknown as Promise);
+main.use(import('@/plugins/gauge.js') as unknown as Promise);
+main.use(import('@/plugins/dynamic-score.js') as unknown as Promise);
+main.use(import('@/plugins/video-recorder.js') as unknown as Promise);
// debug
self.hook = main;
export const hook = main;
diff --git a/src/lib.d.ts b/src/lib.d.ts
index c7f88f32..50a0a54b 100644
--- a/src/lib.d.ts
+++ b/src/lib.d.ts
@@ -1,4 +1,3 @@
-// import type { ZipReader } from './reader';
/* eslint-disable @typescript-eslint/naming-convention */
interface Navigator {
/** Available only in iOS */
diff --git a/src/plugins/demo/index.js b/src/plugins/demo/index.js
index bedece65..d3508017 100644
--- a/src/plugins/demo/index.js
+++ b/src/plugins/demo/index.js
@@ -1,5 +1,5 @@
-import { ImgAny } from '../../utils/ImageTools';
-import { waitForElementById } from '../../utils/waitForElementById';
+import { ImgAny } from '@/utils/ImageTools';
+import { waitForElementById } from '@/utils/waitForElementById';
const $ = query => document.body.querySelector(query);
const flag0 = 'flag{\x71w\x71}';
export default function() {
diff --git a/src/plugins/skin.js b/src/plugins/skin.js
index 85442a7e..5fe55821 100644
--- a/src/plugins/skin.js
+++ b/src/plugins/skin.js
@@ -1,6 +1,5 @@
-import { stringify } from '../utils/stringify';
-import { waitForElementById } from '../utils/waitForElementById';
-import { audio } from '../external';
+import { stringify } from '@/utils/stringify';
+import { waitForElementById } from '@/utils/waitForElementById';
export default hook.define({
name: 'Skin',
description: 'Customize skin',
@@ -11,6 +10,7 @@ export default hook.define({
}
]
});
+const { audio } = hook;
function skin() {
const id = `skin${Date.now()}`;
/** @type {ByteData[]} */
diff --git a/src/plugins/tips.js b/src/plugins/tips.js
index def3b885..f604c430 100644
--- a/src/plugins/tips.js
+++ b/src/plugins/tips.js
@@ -11,9 +11,10 @@ export default hook.define({
const brain = {
firstTip: null,
tips: [],
- addTip(tip, { first = false } = {}) {
- if (first) this.firstTip = tip;
- this.tips.push(tip);
+ addTip(content, { first = false } = {}) {
+ if (first) this.firstTip = content;
+ const weight = this.tips.reduce((acc, cur) => Math.max(acc, cur.weight), 0);
+ this.tips.push({ content, weight: weight || 1 });
},
getTip() {
if (this.firstTip) {
@@ -21,7 +22,18 @@ const brain = {
this.firstTip = null;
return tip;
}
- return this.tips[Math.floor(Math.random() * this.tips.length)];
+ if (this.tips.every(tip => tip.weight < 1)) this.tips.forEach(tip => tip.weight *= 2);
+ const total = this.tips.reduce((acc, cur) => acc + cur.weight, 0);
+ const rand = Math.random() * total;
+ let acc = 0;
+ for (const tip of this.tips) {
+ acc += tip.weight;
+ if (rand < acc) {
+ tip.weight *= 0.5;
+ return tip.content;
+ }
+ }
+ throw new Error('NoIdeaException');
}
};
// 2022.5.8
@@ -87,7 +99,7 @@ function fireTip(elem) {
(function helloworld() {
let pressTime = null;
longPress(elem, () => {
- if (pressTime === null) pressTime = performance.now();
+ if (pressTime == null) pressTime = performance.now();
if (performance.now() - pressTime > 3473) return 1;
return 0;
}, () => {
diff --git a/src/style.css b/src/style.css
index 9c3945ed..fc579e40 100644
--- a/src/style.css
+++ b/src/style.css
@@ -137,6 +137,7 @@ body {
}
#view-msg,
+#view-ext,
#view-cfg {
width: 100%;
height: 99%;
@@ -204,9 +205,8 @@ body {
flex-grow: 1;
}
-/* MessageHandler */
-#view-msg:empty::after {
- content: '日志将会显示在这里';
+.view-content > :empty:after {
+ content: '此功能正在建设中...';
display: flow-root;
text-align: center;
padding: 0.9em;
@@ -216,6 +216,11 @@ body {
-moz-user-select: text;
}
+/* MessageHandler */
+#view-msg:empty::after {
+ content: '日志将会显示在这里';
+}
+
.msgbox {
display: flow-root;
margin: 0.9em 0;
diff --git a/src/utils/Chart.ts b/src/utils/Chart.ts
index 353c403a..1128f53c 100644
--- a/src/utils/Chart.ts
+++ b/src/utils/Chart.ts
@@ -85,7 +85,11 @@ export class Chart {
return Type.obj(this, Chart);
}
}
-export function structChart(chart: ChartPGS): Chart {
+interface ChartData {
+ data: Chart;
+ messages: BetterMessage[];
+}
+export function structChart(chart: ChartPGS, filename: string): ChartData {
const result = Type.obj(chart, Chart);
switch (result.formatVersion) {
case 1:
@@ -104,20 +108,47 @@ export function structChart(chart: ChartPGS): Chart {
throw new Error(`Unsupported formatVersion: ${result.formatVersion}`);
}
const errors = [];
+ const messages: BetterMessage[] = [];
const numOfLines = result.judgeLineList.length;
// if (result.offset < 0) throw new Error('Offset must be non-negative');
// if (result.numOfNotes <= 0) throw new Error('At least one note is required');
+ const warn = (code: number, name: string, message: string) => messages.push({ host: 'Core', code, name, message, target: filename });
+ if (numOfLines > 100) warn(1, 'LineCountWarning', `Expected at most 100 items in judgeLineList, but got ${numOfLines}`);
+ let maxNoteSeconds = 0;
+ for (const { bpm, notesAbove, notesBelow } of result.judgeLineList) {
+ for (const { type, time, holdTime } of notesAbove) {
+ const seconds = (time % 1e9 + (type === 3 && holdTime > 0 ? holdTime : 0)) / bpm * 1.875;
+ if (seconds > maxNoteSeconds) maxNoteSeconds = seconds;
+ }
+ for (const { type, time, holdTime } of notesBelow) {
+ const seconds = (time % 1e9 + (type === 3 && holdTime > 0 ? holdTime : 0)) / bpm * 1.875;
+ if (seconds > maxNoteSeconds) maxNoteSeconds = seconds;
+ }
+ }
for (let i = 0; i < numOfLines; i++) {
const line = result.judgeLineList[i];
const subErrors = [];
- if (line.bpm <= 0) subErrors.push(`Expected bpm > 0, but got ${line.bpm}`);
- if (!line.speedEvents.length) subErrors.push('Expected at least 1 item in speedEvents, but got 0');
- if (!line.judgeLineDisappearEvents.length) subErrors.push('Expected at least 1 item in judgeLineDisappearEvents, but got 0');
- if (!line.judgeLineMoveEvents.length) subErrors.push('Expected at least 1 item in judgeLineMoveEvents, but got 0');
- if (!line.judgeLineRotateEvents.length) subErrors.push('Expected at least 1 item in judgeLineRotateEvents, but got 0');
+ if (line.bpm > 0) {
+ const maxNoteTime = Math.ceil(maxNoteSeconds * line.bpm / 1.875);
+ type KeyOfType = { [K in keyof T]: T[K] extends U ? K : never }[keyof T];
+ type KeyOfJudgeLine = KeyOfType;
+ const checkMaxEventTime = (key: KeyOfJudgeLine) => {
+ if (line[key].length) {
+ let maxEndTime = 0;
+ for (const { endTime } of line[key]) {
+ if (endTime > maxEndTime) maxEndTime = endTime;
+ }
+ if (maxEndTime < maxNoteTime) subErrors.push(`Maximum time for ${key} is too small`);
+ } else subErrors.push(`Expected at least 1 item in ${key}`);
+ };
+ checkMaxEventTime('speedEvents');
+ checkMaxEventTime('judgeLineDisappearEvents');
+ checkMaxEventTime('judgeLineMoveEvents');
+ checkMaxEventTime('judgeLineRotateEvents');
+ } else subErrors.push(`Expected bpm > 0, but got ${line.bpm}`);
if (subErrors.length) errors.push(`JudgeLine ${i}:\n${subErrors.map(e => ` ${e}`).join('\n')}`);
}
if (errors.length) throw new Error(`Invalid chart input\n${errors.map(e => ` ${e.split('\n').join('\n ')}`).join('\n')}`);
if (!numOfLines) throw new Error('No judge lines available');
- return result;
+ return { data: result, messages };
}
diff --git a/src/utils/checkSupport.ts b/src/utils/checkSupport.ts
index 7150d01d..26b3ca70 100644
--- a/src/utils/checkSupport.ts
+++ b/src/utils/checkSupport.ts
@@ -1,5 +1,5 @@
-import { getConstructorName, isUndefined, loadJS, orientation } from '../js/common.js';
-import { audio } from '../external';
+import { getConstructorName, isUndefined, loadJS, orientation } from '@/js/common.js';
+import { audio } from '@/external';
export async function checkSupport({
messageCallback = (_msg: string) => {},
warnCallback = (_msg: string) => {},
diff --git a/src/utils/fixme.ts b/src/utils/fixme.ts
index d439a39c..0e4cc21d 100644
--- a/src/utils/fixme.ts
+++ b/src/utils/fixme.ts
@@ -1,5 +1,5 @@
import createCtx from './createCtx';
-import { audio } from '../external';
+import { audio } from '@/external';
export async function fixme(raw: Record, res: Record): Promise {
const entries = ['Tap', 'TapHL', 'Drag', 'DragHL', 'HoldHead', 'HoldHeadHL', 'Hold', 'HoldHL', 'HoldEnd', 'Flick', 'FlickHL', 'HitFXRaw'];
if (raw.image == null) raw.image = {};
diff --git a/src/utils/reader-.ts b/src/utils/reader-.ts
index 4230a1cf..308ae213 100644
--- a/src/utils/reader-.ts
+++ b/src/utils/reader-.ts
@@ -1,4 +1,4 @@
-import Pec from '../extends/pec/index';
+import { PEC } from '@sim-phi/extends';
import { reader } from './reader';
import { structChart } from './Chart';
import { structInfoData, structLineData } from './structInfo';
@@ -7,22 +7,24 @@ reader.use({
pattern: /\.(json|pec)$/i,
type: 'json',
read(i: ByteData, path: string) {
- const rpeData = Pec.parseRPE(i.text!, i.name/* , path */); // TODO: path
- const data = structChart(rpeData.data);
+ const rpeData = PEC.parseRPE(i.text!, i.name/* , path */); // TODO: path
+ const { data, messages } = structChart(rpeData.data, i.name);
const info = structInfoData([rpeData.info], path);
const line = structLineData(rpeData.line, path);
const { messages: msg, format } = rpeData;
- return { type: 'chart', name: i.name, md5: md5(i.text!), data, msg, info, line, format };
+ messages.push(...msg);
+ return { type: 'chart', name: i.name, md5: md5(i.text!), data, msg: messages, info, line, format };
}
});
reader.use({
pattern: /\.pec$/i,
type: 'text',
- read(i: ByteData) {
- const pecData = Pec.parse(i.text!, i.name);
- const data = structChart(pecData.data);
+ read(i: ByteData/* , path */) {
+ const pecData = PEC.parse(i.text!, i.name);
+ const { data, messages } = structChart(pecData.data, i.name);
const { messages: msg, format } = pecData;
- return { type: 'chart', name: i.name, md5: md5(i.text!), data, msg, format };
+ (messages as (BetterMessage | string)[]).push(...msg);
+ return { type: 'chart', name: i.name, md5: md5(i.text!), data, msg: messages, format };
}
});
reader.use({
@@ -31,7 +33,7 @@ reader.use({
mustMatch: true,
read(i: ByteData, path: string) {
const data = i.text!;
- const chartInfo = structInfoData(Pec.readInfo(data), path);
+ const chartInfo = structInfoData(PEC.readInfo(data), path);
return { type: 'info' as const, data: chartInfo };
}
});
diff --git a/src/utils/reader.ts b/src/utils/reader.ts
index 5d03f980..35f007f9 100644
--- a/src/utils/reader.ts
+++ b/src/utils/reader.ts
@@ -4,7 +4,7 @@ import { structChart } from './Chart';
import { structInfoData, structLineData } from './structInfo';
import md5 from 'md5';
// @ts-expect-error: vite-plugin-worker-loader
-import Zip from '../zip.worker?worker';
+import Zip from '@/zip.worker?worker';
export class FileEmitter extends EventTarget {
private readonly input: HTMLInputElement;
public constructor() {
@@ -138,12 +138,12 @@ const readerInit: ReaderInit[] = [
}, {
pattern: /\.json$/i,
type: 'json',
- read(i: ByteData) {
+ read(i: ByteData/* , path */) {
const text = i.text!;
const json = JSON.parse(text, (_, value) => typeof value === 'number' ? Math.fround(value) : value as unknown) as ChartPGS;
- const jsonData = structChart(json);
+ const { data: jsonData, messages } = structChart(json, i.name);
const format = `PGS(${jsonData.formatVersion})`;
- return { type: 'chart', name: i.name, md5: md5(text), data: jsonData, format };
+ return { type: 'chart', name: i.name, md5: md5(text), data: jsonData, msg: messages, format };
}
}, {
pattern: /\.(png|jpg|jpeg|gif|bmp|webp|svg)$/i,
diff --git a/tools/eslint-config-ts.cjs b/tools/eslint-config-ts.cjs
index 5122a693..4e632d29 100644
--- a/tools/eslint-config-ts.cjs
+++ b/tools/eslint-config-ts.cjs
@@ -1,6 +1,9 @@
'use strict';
module.exports = {
- extends: ['plugin:@typescript-eslint/all'],
+ extends: [
+ 'plugin:@typescript-eslint/all',
+ './eslint-config.cjs'
+ ],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
@@ -10,34 +13,36 @@ module.exports = {
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-nocheck': 'allow-with-description' }],
- '@typescript-eslint/brace-style': ['error', '1tbs', { allowSingleLine: true }],
'@typescript-eslint/consistent-type-imports': ['error', { disallowTypeAnnotations: false }],
'@typescript-eslint/explicit-function-return-type': 'off',
- '@typescript-eslint/indent': ['error', 2, { SwitchCase: 1 }],
- '@typescript-eslint/lines-around-comment': ['error', { beforeBlockComment: false }],
- '@typescript-eslint/lines-between-class-members': ['error', 'never'],
+ // '@typescript-eslint/explicit-function-return-type': ['error', { allowExpressions: true }],
'@typescript-eslint/max-params': 'off',
'@typescript-eslint/member-ordering': ['error', { default: memberOrdering() }],
'@typescript-eslint/naming-convention': ['error', ...defaultNamingConvention(), { selector: 'import', format: null }],
'@typescript-eslint/no-confusing-void-expression': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-explicit-any': ['error', { fixToUnknown: true, ignoreRestArgs: true }],
- '@typescript-eslint/no-extra-parens': ['error', 'all', { enforceForSequenceExpressions: false }],
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-invalid-void-type': ['error', { allowAsThisParameter: true }],
'@typescript-eslint/no-magic-numbers': 'off',
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-type-alias': 'off',
+ // '@typescript-eslint/no-unnecessary-condition': 'off', // qwq
+ // '@typescript-eslint/no-unsafe-call': 'off', // qwq
+ // '@typescript-eslint/no-unsafe-member-access': 'off', // qwq
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-use-before-define': ['error', 'nofunc'],
- '@typescript-eslint/object-curly-spacing': ['error', 'always'],
'@typescript-eslint/prefer-destructuring': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/prefer-readonly-parameter-types': 'off',
- '@typescript-eslint/quotes': ['error', 'single'],
- '@typescript-eslint/semi': ['error', 'always', { omitLastInOneLineBlock: true }],
- '@typescript-eslint/space-before-function-paren': ['error', 'never']
+ 'default-param-last': 'off', // qwq
+ 'no-invalid-this': 'off', // qwq
+ 'no-redeclare': 'off', // qwq
+ 'no-shadow': 'off', // qwq
+ 'no-undef': 'off', // qwq
+ 'no-use-before-define': 'off', // qwq
+ 'require-await': 'off' // qwq
}
};
function memberOrdering() {
diff --git a/tools/eslint-config.cjs b/tools/eslint-config.cjs
index 49a17cf3..8572dce8 100644
--- a/tools/eslint-config.cjs
+++ b/tools/eslint-config.cjs
@@ -1,7 +1,11 @@
'use strict';
module.exports = {
- extends: ['eslint:all'],
- plugins: ['rulesdir'],
+ extends: [
+ 'eslint:all',
+ 'plugin:@stylistic/disable-legacy',
+ 'plugin:@stylistic/all-extends'
+ ],
+ plugins: ['@stylistic', 'rulesdir'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
@@ -9,9 +13,30 @@ module.exports = {
project: 'tsconfig.json'
},
rules: {
- 'array-element-newline': ['error', 'consistent'],
- 'arrow-parens': ['error', 'as-needed'],
- 'brace-style': ['error', '1tbs', { allowSingleLine: true }],
+ '@stylistic/array-element-newline': ['error', 'consistent'],
+ '@stylistic/arrow-parens': ['error', 'as-needed'],
+ '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }],
+ '@stylistic/function-call-argument-newline': ['error', 'consistent'],
+ '@stylistic/indent': ['error', 2, { SwitchCase: 1 }],
+ '@stylistic/lines-around-comment': ['error', { beforeBlockComment: false }],
+ '@stylistic/lines-between-class-members': ['error', 'never'],
+ '@stylistic/max-len': 'off',
+ '@stylistic/max-statements-per-line': 'off', // qwq
+ '@stylistic/multiline-ternary': ['error', 'never'],
+ '@stylistic/newline-per-chained-call': 'off', // qwq
+ '@stylistic/no-confusing-arrow': 'off', // qwq
+ '@stylistic/no-extra-parens': ['error', 'all', { enforceForSequenceExpressions: false }],
+ '@stylistic/no-mixed-operators': 'off', // qwq
+ '@stylistic/no-multiple-empty-lines': ['error', { max: 0 }],
+ '@stylistic/object-curly-spacing': ['error', 'always'],
+ '@stylistic/object-property-newline': ['error', { allowAllPropertiesOnSameLine: true }],
+ '@stylistic/padded-blocks': ['error', 'never'],
+ '@stylistic/quote-props': ['error', 'consistent-as-needed'],
+ '@stylistic/quotes': ['error', 'single'],
+ '@stylistic/semi': ['error', 'always', { omitLastInOneLineBlock: true }],
+ '@stylistic/space-before-function-paren': ['error', 'never'],
+ '@stylistic/spaced-comment': ['error', 'always', { block: { balanced: true } }],
+ '@stylistic/wrap-regex': 'off', // someday will be never
'camelcase': ['error', { properties: 'never' }],
'capitalized-comments': 'off',
'complexity': 'off', // qwq
@@ -19,28 +44,19 @@ module.exports = {
'eqeqeq': ['error', 'always', { null: 'ignore' }],
'func-names': ['error', 'never'],
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
- 'function-call-argument-newline': ['error', 'never'],
'grouped-accessor-pairs': ['error', 'getBeforeSet'],
'id-length': 'off',
- 'indent': ['error', 2, { SwitchCase: 1 }],
'init-declarations': 'off',
'line-comment-position': 'off',
- 'lines-around-comment': ['error', { beforeBlockComment: false }],
- 'lines-between-class-members': ['error', 'never'],
'max-classes-per-file': 'off',
'max-depth': 'off', // qwq
- 'max-len': 'off',
- 'max-lines-per-function': 'off', // ?
+ 'max-lines-per-function': 'off',
'max-lines': 'off',
'max-params': 'off', // qwq
- 'max-statements-per-line': 'off', // qwq
'max-statements': 'off', // qwq
'multiline-comment-style': ['error', 'separate-lines'],
- 'multiline-ternary': ['error', 'never'],
- 'newline-per-chained-call': 'off', // qwq
'no-await-in-loop': 'off',
'no-bitwise': 'off',
- 'no-confusing-arrow': 'off', // qwq
'no-console': 'off', // qwq
'no-continue': 'off', // qwq
'no-control-regex': 'off',
@@ -48,8 +64,6 @@ module.exports = {
'no-eq-null': 'off',
'no-inline-comments': 'off',
'no-magic-numbers': 'off', // qwq
- 'no-mixed-operators': 'off', // qwq
- 'no-multiple-empty-lines': ['error', { max: 0 }],
'no-nested-ternary': 'off',
'no-plusplus': 'off',
'no-return-assign': 'off',
@@ -60,27 +74,18 @@ module.exports = {
'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
'no-use-before-define': ['error', 'nofunc'],
'no-warning-comments': 'warn', // qwq
- 'object-curly-spacing': ['error', 'always'],
- 'object-property-newline': ['error', { allowAllPropertiesOnSameLine: true }],
'one-var': ['error', { initialized: 'never', uninitialized: 'always' }],
- 'padded-blocks': ['error', 'never'],
'prefer-const': ['error', { destructuring: 'all' }],
'prefer-destructuring': ['error', { object: true, array: false }],
'prefer-named-capture-group': 'off', // ?
- 'quote-props': ['error', 'consistent-as-needed'],
- 'quotes': ['error', 'single'],
'radix': ['error', 'as-needed'],
'require-atomic-updates': ['error', { allowProperties: true }],
'require-unicode-regexp': 'off', // ?
- 'semi': ['error', 'always', { omitLastInOneLineBlock: true }],
- 'sort-imports': ['error', { ignoreDeclarationSort: true }],
- 'sort-keys': 'off',
- 'space-before-function-paren': ['error', 'never'],
- 'spaced-comment': ['error', 'always', { block: { balanced: true } }],
'rulesdir/no-magic-words': ['error', { words: ['lchz\\x68', 'Phi\\x67ros', 'sim\\x70hi', 'f\\x75ck'] }],
'rulesdir/no-single-line-braces': 'error',
'rulesdir/single-line-spacing': 'error',
'rulesdir/space-before-inline-comments': 'error',
- 'wrap-regex': 'off' // someday will be never
+ 'sort-imports': ['error', { ignoreDeclarationSort: true }],
+ 'sort-keys': 'off' // someday will be never
}
};
diff --git a/tsconfig.json b/tsconfig.json
index 67d1c2d5..c0409911 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,7 +22,7 @@
"baseUrl": "./",
"paths": {
"/utils/*": ["public/utils/*"],
- "@/*": ["src/plugins/*"]
+ "@/*": ["src/*"]
},
"allowJs": true
// "checkJs": true
diff --git a/vite.config.ts b/vite.config.ts
index 11e1048f..7689e1a0 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -7,7 +7,7 @@ export default defineConfig({
base: './',
resolve: {
alias: {
- '@': '/src/plugins'
+ '@': '/src'
}
},
build: {