@@ -39,7 +39,7 @@ private List<ChuNote> SortedNotesForConnectingPrevious(ChuChart chart)
3939 var nextDict = new Dictionary < ChuNote , List < ChuNote > > ( ) ;
4040 foreach ( var n in chart . Notes )
4141 {
42- if ( n . Previous != null ) nextDict . Add ( n . Previous , n ) ;
42+ if ( n . TargetNote != null ) nextDict . Add ( n . TargetNote , n ) ;
4343 }
4444
4545 // 2. 遍历 chart.Notes,对每个 ChuNote 以 DFS 方式把它本身以及它所有 Next 子孙依次加入结果。
@@ -176,21 +176,8 @@ private string Serialize(ChuChart ugc)
176176 sb . AppendLine ( ) ;
177177
178178 var notes = SortedNotesForConnectingPrevious ( ugc ) ;
179-
180- // UGC Slide / AIR-SLIDE / AIR-HOLD / AIR-CRUSH (v8):
181- // - Chains (ChuNote.Previous) serialize as ONE parent line + follower lines (#OffsetTick from parent time).
182- // - Ground slide: parent `s`, followers `>s` / `>c` + end cell/width.
183- // - Air slide: parent `S` + cell/width + hh + N/I; followers `>s`/`>c` + xw + hh.
184- // - Air hold: parent `H` + cell/width + color; followers `>s` / `>c` only.
185- // - Air crush: parent `C` + cell/width + hh + color,interval; followers `>c` + xw + hh.
186- // - First segment may attach to TAP/HLD via Previous; only skip emit when Previous is another segment of the same chain.
187- var slideChains = BuildSlideChains ( notes ) ;
188-
189179 foreach ( var n in notes )
190180 {
191- if ( IsSlideChainNote ( n . Type ) && IsChainContinueSegments ( n ) )
192- continue ; // 是链式音符且不是第一段,则应当已经被处理过了,直接跳过
193-
194181 if ( n . SpeedGroup != useTil )
195182 {
196183 useTil = n . SpeedGroup ;
@@ -207,120 +194,59 @@ private string Serialize(ChuChart ugc)
207194 sb . Append ( $ "#{ m } '{ o } :{ ucode } ") ;
208195 sb . AppendLine ( ) ;
209196
210- if ( IsSlideChainNote ( n . Type ) )
211- {
212- if ( slideChains . TryGetValue ( n , out var segments ) )
213- {
214- foreach ( var seg in segments )
215- {
216- var endTicks = Utils . Tick ( seg . EndTime - n . Time , RSL ) ;
217- if ( endTicks <= 0 ) continue ;
218- if ( IsAirSlide ( n . Type ) || IsAirCrush ( n . Type ) )
219- sb . AppendLine ( $ "#{ endTicks } >{ SlideFollowerMarker ( seg . Type ) } { IToH36 ( seg . EndCell ) } { IToH36 ( seg . EndWidth ) } { EncodeAirHeight ( seg . EndHeight ) } ") ;
220- else if ( IsSlide ( n . Type ) )
221- sb . AppendLine ( $ "#{ endTicks } >{ SlideFollowerMarker ( seg . Type ) } { IToH36 ( seg . EndCell ) } { IToH36 ( seg . EndWidth ) } ") ;
222- else
223- sb . AppendLine ( $ "#{ endTicks } >{ SlideFollowerMarker ( seg . Type ) } ") ;
224- }
225- }
226- }
227- else
228- {
229- var durTicks = Utils . Tick ( n . Duration , RSL ) ;
230- if ( n . Type is "HLD" or "HXD" && durTicks > 0 )
231- sb . AppendLine ( $ "#{ durTicks } >s") ;
232- }
197+ AppendFollowerLines ( sb , n ) ;
233198 }
234199 return sb . ToString ( ) ;
235200 }
236201
237- private static Dictionary < ChuNote , List < ChuNote > > BuildSlideChains ( List < ChuNote > notes )
202+ private void AppendFollowerLines ( StringBuilder sb , ChuNote n )
238203 {
239- var chains = new Dictionary < ChuNote , List < ChuNote > > ( ) ;
240- foreach ( var n in notes )
241- {
242- if ( ! IsSlideChainNote ( n . Type ) ) continue ;
243- var head = GetSlideHead ( n ) ;
244- if ( ! chains . TryGetValue ( head , out var list ) )
245- chains [ head ] = list = [ ] ;
246- list . Add ( n ) ;
247- }
204+ if ( n . Segments . Count == 0 ) return ;
248205
249- // Order segments by their end time so follower ticks are increasing.
250- foreach ( var ( _ , segs ) in chains )
206+ Rational time = 0 ;
207+ foreach ( var seg in n . Segments )
251208 {
252- segs . Sort ( ( a , b ) =>
253- {
254- var t = a . EndTime . CompareTo ( b . EndTime ) ;
255- if ( t != 0 ) return t ;
256- // stable-ish tie-breakers
257- t = a . Time . CompareTo ( b . Time ) ;
258- if ( t != 0 ) return t ;
259- t = string . CompareOrdinal ( a . Type , b . Type ) ;
260- if ( t != 0 ) return t ;
261- return 0 ;
262- } ) ;
209+ time += seg . Length ;
210+ var endTick = Utils . Tick ( time , RSL ) ;
211+
212+ var marker = seg . C ? 'c' : 's' ;
213+ if ( IsAirSlide ( n ) || n . Type == ChuNoteType . Crush )
214+ sb . AppendLine ( $ "#{ endTick } >{ marker } { IToH36 ( seg . EndCell ) } { IToH36 ( seg . EndWidth ) } { EncodeAirHeight ( seg . EndHeight ) } ") ;
215+ else if ( n . Type == ChuNoteType . Slide )
216+ sb . AppendLine ( $ "#{ endTick } >{ marker } { IToH36 ( seg . EndCell ) } { IToH36 ( seg . EndWidth ) } ") ;
217+ else
218+ sb . AppendLine ( $ "#{ endTick } >{ marker } ") ;
263219 }
264-
265- // For a valid chain, follower ticks should be strictly increasing; if the chart has
266- // degenerate segments, later code simply skips non-positive offsets.
267- return chains ;
268220 }
269221
270- private static ChuNote GetSlideHead ( ChuNote n )
271- {
272- var cur = n ;
273- while ( IsChainContinueSegments ( cur ) ) cur = cur . Previous ! ;
274- return cur ;
275- }
276-
277- private static char SlideFollowerMarker ( string t ) => t is "SLC" or "SXC" or "ASC" or "ALD" ? 'c' : 's' ;
278-
279- private static string EncodeAirHeight ( decimal value ) => IToH36 ( Math . Clamp ( ( int ) Math . Round ( C2U_Height ( value ) * 10 ) , 0 , 1295 ) ) . PadLeft ( 2 , '0' ) ;
222+ private static string EncodeAirHeight ( decimal value ) => IToH36 ( Math . Clamp ( ( int ) Math . Round ( Height_ToUgc ( value ) * 10 ) , 0 , 1295 ) ) . PadLeft ( 2 , '0' ) ;
280223
281224 private string AirColor ( ChuNote n )
282225 {
283- if ( Try_C2U_AirColor ( n , out var color ) ) return color ;
284- else
285- {
286- if ( n . Tag != "" ) alerts . Add ( new Alert ( Alert . LEVEL . Warning , string . Format ( Locale . C2SUnsupportedAirColor , "UGC Generator" , n . Type , n . Tag ) , n . Time ) ) ;
287- return "N" ;
288- }
289- }
290- private string CrushColor ( ChuNote n )
291- {
292- if ( C2U_AirCrushColor . TryGetValue ( n . Tag , out var color ) ) return color ;
293- else if ( n . Tag . Length == 1 ) return n . Tag ;
294- else
295- {
296- if ( n . Tag != "" ) alerts . Add ( new Alert ( Alert . LEVEL . Warning , string . Format ( Locale . C2SUnsupportedAirColor , "UGC Generator" , n . Type , n . Tag ) , n . Time ) ) ;
297- return "0" ;
298- }
299- }
300-
301- private string CrushInterval ( Rational crushInterval )
302- {
303- return crushInterval > 25 ? "$" : Utils . Tick ( crushInterval , RSL ) . ToString ( ) ;
226+ var color = AirColor_ToUgc ( n ) ;
227+ if ( color == "N" && n . Color is not ( NoteColor . DEF or NoteColor . GRN or NoteColor . PPL ) )
228+ alerts . Add ( new Alert ( Alert . LEVEL . Warning , string . Format ( Locale . C2SUnsupportedAirColor , "UGC Generator" , n . Type , n . Color ) , n . Time ) ) ;
229+ return color ;
304230 }
231+ private string CrushColor ( ChuNote n ) => AirCrush_Color_ToUgc [ n . Color ] ;
232+ private string CrushInterval ( Rational ? crushInterval ) =>
233+ crushInterval != null ? Utils . Tick ( crushInterval . Value , RSL ) . ToString ( ) : "$" ;
305234
306235 private string UCode ( ChuNote n )
307236 {
308237 string c = IToH36 ( n . Cell ) , w = IToH36 ( n . Width ) ;
309- return n . Type switch
238+ return ( n . Type , n . IsAir ) switch
310239 {
311- "TAP" => $ "t{ c } { w } ",
312- "CHR" => $ "x{ c } { w } { C2U_ChrExtras . GetValueOrDefault ( n . Tag , "C" ) } ",
313- "HLD" or "HXD" => $ "h{ c } { w } ",
314- "SLD" or "SXD" => $ "s{ c } { w } ",
315- "SLC" or "SXC" => $ "s{ c } { w } ",
316- "FLK" => $ "f{ c } { w } { n . Tag } ",
317- "MNE" => $ "d{ c } { w } ",
318- // AIR-SLIDE (v8): #BarTick:S x w hh c
319- "ASD" or "ASC" => $ "S{ c } { w } { EncodeAirHeight ( n . Height ) } { AirColor ( n ) } ",
320- "AIR" or "AUR" or "AUL" or "ADW" or "ADR" or "ADL" => $ "a{ c } { w } { C2U_AirDirections [ n . Type ] } { AirColor ( n ) } ",
321- // AIR-HOLD (v8): #BarTick:H x w c + 子行 #OffsetTick:s / :c(见 Umiguri Chart v8 doc)
322- "AHD" or "AHX" => $ "H{ c } { w } { AirColor ( n ) } ",
323- "ALD" => $ "C{ c } { w } { EncodeAirHeight ( n . Height ) } { CrushColor ( n ) } ,{ CrushInterval ( n . CrushInterval ) } ",
240+ ( ChuNoteType . Tap , false ) when ! n . IsEx => $ "t{ c } { w } ",
241+ ( ChuNoteType . Tap , false ) => $ "x{ c } { w } { ExDirections_ToUgc [ n . Ex ! . Value ] } ",
242+ ( ChuNoteType . Tap , true ) => $ "a{ c } { w } { AirDirections_ToUgc [ n . AirDirection ] } { AirColor ( n ) } ",
243+ ( ChuNoteType . Flick , _ ) => $ "f{ c } { w } { n . Ex switch { ExDirection . RS => "R" , _ => "L" } } ",
244+ ( ChuNoteType . Hold , false ) => $ "h{ c } { w } ",
245+ ( ChuNoteType . Hold , true ) => $ "H{ c } { w } { AirColor ( n ) } ",
246+ ( ChuNoteType . Slide , false ) => $ "s{ c } { w } ",
247+ ( ChuNoteType . Slide , true ) => $ "S{ c } { w } { EncodeAirHeight ( n . Height ) } { AirColor ( n ) } ",
248+ ( ChuNoteType . Crush , _ ) => $ "C{ c } { w } { EncodeAirHeight ( n . Height ) } { CrushColor ( n ) } ,{ CrushInterval ( n . CrushInterval ) } ",
249+ ( ChuNoteType . Mine , _ ) => $ "d{ c } { w } ",
324250 _ => ""
325251 } ;
326252 }
0 commit comments