@@ -34,7 +34,7 @@ public override (ChuChart, List<Alert>) Parse(string text)
3434
3535 if ( inHeader )
3636 {
37- if ( line == "@ENDHEAD" )
37+ if ( line . StartsWith ( "@ENDHEAD" ) )
3838 {
3939 inHeader = false ;
4040 continue ;
@@ -183,7 +183,9 @@ private void ParseHeaderLine(string line, ChuChart chart, List<Alert> alerts, in
183183 case "@EXVER" : case "@SORT" : case "@BGM" : case "@BGMOFS" : case "@BGMPRV" :
184184 case "@JACKET" : case "@BGIMG" : case "@BGMODE" : case "@FLDCOL" : case "@FLDIMG" :
185185 case "@FLAG" : case "@ATINFO" : case "@DLURL" : case "@COPYRIGHT" : case "@LICENSE" :
186- case "@MAINTIL" : case "@TIL" :
186+ case "@MAINTIL" : case "@TIL" : case "@USETIL" :
187+ case "@MAINBPM" :
188+ case "@BGSCENE" : case "@FLDSCENE" : case "@RLDATE" : case "@CMT" :
187189 break ;
188190
189191 case "@SPDMOD" :
@@ -313,7 +315,7 @@ private int ParseNoteLine(string[] lines, int idx, ChuChart chart, List<Alert> a
313315 break ;
314316
315317 default :
316- alerts . Add ( new Alert ( Warning , $ "未知的音符类型前缀 '{ typeChar } ': { line } ", note . Time , ( double ) chart . ToSecond ( note . Time ) , lineNum , line ) ) ;
318+ alerts . Add ( new Alert ( Warning , $ "未知的音符类型前缀 '{ typeChar } ': { line } ", ( chart , note . Time ) , lineNum , line ) ) ;
317319 // 如果后面跟的是跟随行(子ノーツ)而非主行(親ノーツ)的话,把它们全部消耗掉
318320 while ( idx + 1 < lines . Length )
319321 {
@@ -406,6 +408,23 @@ private int ParseHoldNote(bool isAirHold, string[] lines, int idx, string code,
406408 alerts . Add ( new Alert ( Warning , $ "HLD 音符缺少时长跟随行") { Line = idx + 1 , RelevantNote = lines [ idx ] } ) ;
407409 return idx ;
408410 }
411+
412+ // UGC中约定Air系列音符都一定紧跟在其Previous的后面。
413+ // 所以我们直接用上一个解析出的note就可以立即确定前驱了,无需再等到最后集中FillPrevious,而且等到最后集中FillPrevious时的结果也可能是错的。
414+ // 本函数作为一个工具函数干的就是这个事情。
415+ private bool AddAirPreviousFromLastNote ( ChuNote note , ChuChart chart )
416+ {
417+ if ( chart . Notes . Count > 0 )
418+ {
419+ var filtered = FilterPreviousCandidates ( note , [ chart . Notes . Last ( ) ] ) ; // 仅传入一个元素到FilterPreviousCandidates,因此返回结果最多一个元素
420+ if ( filtered . Count > 0 )
421+ {
422+ note . Previous = filtered [ 0 ] ;
423+ return true ;
424+ }
425+ }
426+ return false ;
427+ }
409428
410429 private int ParseSlideNote ( bool isAirSlide , string [ ] lines , int idx , string code , ChuNote previousNote , List < Alert > alerts , ChuChart chart )
411430 {
@@ -439,6 +458,12 @@ private int ParseSlideNote(bool isAirSlide, string[] lines, int idx, string code
439458 EndHeight = endHeight != null ? U2C_Height ( endHeight . Value ) : previousNote . EndHeight ,
440459 Previous = foundFirst ? previousNote : null ,
441460 } ;
461+
462+ if ( isAirSlide && ! foundFirst )
463+ {
464+ if ( ! AddAirPreviousFromLastNote ( note , chart ) ) // 尝试直接从上一个note添加前驱。如果失败了报警告。
465+ alerts . Add ( new Alert ( Warning , $ "无法找到 Air Slide 的前驱音符", ( chart , note . Time ) , idx + 1 , lines [ idx ] ) ) ;
466+ }
442467
443468 chart . Notes . Add ( note ) ;
444469 previousNote = note ;
@@ -496,51 +521,42 @@ private void ParseCellWidth(string code, int startIdx, ChuNote note, List<Alert>
496521 if ( code . Length > startIdx + 1 )
497522 note . Width = HToI ( code [ startIdx + 1 ] ) ;
498523 else
499- alerts . Add ( new Alert ( Warning , $ "音符缺少 width: { code } ", note . Time , ( double ) chart . ToSecond ( note . Time ) , lineNum , FormatNoteRef ( note , code ) ) ) ;
524+ alerts . Add ( new Alert ( Warning , $ "音符缺少 width: { code } ", ( chart , note . Time ) , lineNum , FormatNoteRef ( note , code ) ) ) ;
500525 }
501526 else
502527 {
503- alerts . Add ( new Alert ( Warning , $ "音符缺少 cell 和 width: { code } ", note . Time , ( double ) chart . ToSecond ( note . Time ) , lineNum , FormatNoteRef ( note , code ) ) ) ;
528+ alerts . Add ( new Alert ( Warning , $ "音符缺少 cell 和 width: { code } ", ( chart , note . Time ) , lineNum , FormatNoteRef ( note , code ) ) ) ;
504529 }
505530 }
506531
507532 private void ParseAirNote ( string code , ChuNote note , List < Alert > alerts , int lineNum , ChuChart chart )
508533 {
534+ note . Type = "AIR" ; // 出错情况下的缺省值
509535 if ( code . Length < 5 )
510536 {
511537 alerts . Add ( new Alert ( Warning , $ "AIR 音符代码过短: { code } ") { Line = lineNum } ) ;
512- note . Type = "AIR" ;
513538 return ;
514539 }
515540
516541 ParseCellWidth ( code , 1 , note , alerts , lineNum , chart ) ;
517542 var mainPart = code [ 3 ..] ;
518543
519- if ( mainPart . Length < 2 )
520- {
521- alerts . Add ( new Alert ( Warning , $ "AIR 音符方向代码过短: { code } ") { Line = lineNum } ) ;
522- note . Type = "AIR" ;
523- return ;
524- }
525-
526- var dir = mainPart [ ..2 ] ;
527- if ( U2C_AirDirections . TryGetValue ( dir , out var airType ) )
528- {
529- note . Type = airType ;
530- }
531- else
532- {
533- note . Type = "AIR" ;
534- alerts . Add ( new Alert ( Warning , $ "未知的 AIR 方向: { dir } ") { Line = lineNum , RelevantNote = FormatNoteRef ( note , code ) } ) ;
535- }
544+ // 解析方向
545+ var direction = mainPart [ ..2 ] ;
546+ if ( U2C_AirDirections . TryGetValue ( direction , out var airType ) ) note . Type = airType ;
547+ else alerts . Add ( new Alert ( Warning , $ "未知的 AIR 方向: { direction } ") { Line = lineNum , RelevantNote = FormatNoteRef ( note , code ) } ) ;
548+ // 解析颜色
536549 ParseHeightAndColor ( note , mainPart [ 2 ..] , alerts , lineNum , "a" ) ;
550+
551+ if ( ! AddAirPreviousFromLastNote ( note , chart ) ) // 尝试直接从上一个note添加前驱。如果失败了报警告。
552+ alerts . Add ( new Alert ( Warning , $ "无法找到 Air 的前驱音符", ( chart , note . Time ) , lineNum + 1 , code ) ) ;
537553 }
538554
539555 private int ParseAirCrushNote ( string [ ] lines , int idx , string code , ChuNote note , List < Alert > alerts , ChuChart chart )
540556 {
541557 note . Type = "ALD" ;
542558 ParseCellWidth ( code , 1 , note , alerts , idx + 1 , chart ) ;
543- if ( code . Length <= 3 ) alerts . Add ( new Alert ( Warning , "AirCrush缺少参数!" , note . Time , ( double ) chart . ToSecond ( note . Time ) , idx + 1 , lines [ idx ] ) ) ;
559+ if ( code . Length <= 3 ) alerts . Add ( new Alert ( Warning , "AirCrush缺少参数!" , ( chart , note . Time ) , idx + 1 , lines [ idx ] ) ) ;
544560 else ParseHeightAndColor ( note , code [ 3 ..] , alerts , idx + 1 , "C" ) ;
545561
546562 bool foundFirst = false ;
@@ -555,7 +571,7 @@ private int ParseAirCrushNote(string[] lines, int idx, string code, ChuNote note
555571 }
556572
557573 if ( Version >= 8 && marker != "c" )
558- alerts . Add ( new Alert ( Warning , $ "Air-Crush(v8)子行标记应为 'c',实际为 '{ marker } '", note . Time , ( double ) chart . ToSecond ( note . Time ) , idx + 1 , nextLine ) ) ;
574+ alerts . Add ( new Alert ( Warning , $ "Air-Crush(v8)子行标记应为 'c',实际为 '{ marker } '", ( chart , note . Time ) , idx + 1 , nextLine ) ) ;
559575
560576 if ( Version <= 6 && ! intervalSet && marker == "s" )
561577 {
0 commit comments