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 @@

-
-
- +
+
-
+
+
@@ -187,7 +189,7 @@

更多设置 日志
-
+
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: {