1- using MuConvert . generator ;
1+ using System . Text ;
2+ using MuConvert . generator ;
23using MuConvert . utils ;
4+ using static MuConvert . utils . Alert . LEVEL ;
35
46namespace MuConvert . ogk ;
57
68public class OgkrGenerator : IGenerator < OgkChart >
79{
10+ // 除非你知道你在做什么,不然以下两个变量请勿修改!
11+ public int RSL = 1920 ;
12+ public int RSL_X = 4096 ;
13+
14+ private List < Alert > alerts = [ ] ;
815 public ( string , List < Alert > ) Generate ( OgkChart chart )
916 {
10- throw new NotImplementedException ( ) ;
17+ chart . Sort ( ) ;
18+
19+ // 各类“需要分配ID”的对象,这里集中排序、ID分配好。
20+ var orderedLanes = SortLanesByTypeGroup ( chart . Lanes ) ;
21+ var generalLanes = AssignGroupIds ( orderedLanes , chart . EnemyMovements ) ; // EnemyMovements已经在chart.Sort()里排好序了,无需再排
22+ var paletteIds = AssignPaletteIds ( chart ) ;
23+
24+ var sb = new StringBuilder ( ) ;
25+ sb . AppendLine ( ) ;
26+
27+ EmitHeader ( sb , chart ) ;
28+ sb . AppendLine ( ) ;
29+ sb . AppendLine ( ) ;
30+
31+ EmitBPalette ( sb , paletteIds ) ;
32+ sb . AppendLine ( ) ;
33+ sb . AppendLine ( ) ;
34+
35+ EmitComposition ( sb , chart ) ;
36+ sb . AppendLine ( ) ;
37+ sb . AppendLine ( ) ;
38+
39+ EmitLane ( sb , generalLanes ) ;
40+ sb . AppendLine ( ) ;
41+
42+ EmitLaneBlock ( sb , generalLanes ) ;
43+ sb . AppendLine ( ) ;
44+ sb . AppendLine ( ) ;
45+
46+ EmitBullet ( sb , chart , paletteIds ) ;
47+ sb . AppendLine ( ) ;
48+ sb . AppendLine ( ) ;
49+
50+ EmitBeam ( sb , chart ) ;
51+ sb . AppendLine ( ) ;
52+ sb . AppendLine ( ) ;
53+
54+ EmitBell ( sb , chart , paletteIds ) ;
55+ sb . AppendLine ( ) ;
56+ sb . AppendLine ( ) ;
57+
58+ EmitFlick ( sb , chart ) ;
59+ sb . AppendLine ( ) ;
60+ sb . AppendLine ( ) ;
61+
62+ EmitNotes ( sb , chart , generalLanes ) ;
63+ sb . AppendLine ( ) ;
64+ sb . AppendLine ( ) ;
65+
66+ sb . AppendLine ( "[TOTAL]" ) ;
67+ sb . AppendLine ( ) ;
68+
69+ return ( sb . ToString ( ) , alerts ) ;
70+ }
71+
72+ // 遵循官谱的顺序:首先按照Lane的类型(左右墙、红绿蓝线、彩色线),再按时间顺序
73+ private static List < Lane > SortLanesByTypeGroup ( List < Lane > lanes )
74+ {
75+ int Order ( Lane l ) => l switch
76+ {
77+ Wall w when w . Direction == Direction . L => 0 ,
78+ Wall w when w . Direction == Direction . R => 1 ,
79+ _ => l . Type switch
80+ {
81+ LaneType . Red => 2 ,
82+ LaneType . Green => 3 ,
83+ LaneType . Blue => 4 ,
84+ LaneType . Colorful => 5 ,
85+ _ => 99 ,
86+ }
87+ } ;
88+ // 输入的lanes已经被chart.Sort()按Time升序排好序了,这里只需再做一次按类型的稳定排序即可。
89+ return lanes . OrderBy ( Order ) . ToList ( ) ;
90+ }
91+
92+ private static Dictionary < OgkBaseLane < OgkLanePoint > , int > AssignGroupIds ( List < Lane > lanes , List < EnemyMovement > enemies )
93+ {
94+ var ids = new Dictionary < OgkBaseLane < OgkLanePoint > , int > ( ) ;
95+ var c = 0 ;
96+ foreach ( var l in lanes ) ids [ l ] = c ++ ;
97+ foreach ( var e in enemies ) ids [ e ] = c ++ ;
98+ return ids ;
99+ }
100+
101+ // 给所有BulletPallete分配字符串ID。按其在Bullets/Bells中首次出现的顺序分配,同时去重(按record的结构相等性)。
102+ private static Dictionary < BulletPallete , string > AssignPaletteIds ( OgkChart chart )
103+ {
104+ var ids = new Dictionary < BulletPallete , string > ( ) ;
105+ var counter = 360 ; // 起始值为 A0 = 10 * 36
106+ void Add ( BulletPallete ? p )
107+ {
108+ if ( p == null ) return ;
109+ if ( ! ids . ContainsKey ( p ) ) ids [ p ] = Utils . IToH ( counter ++ , 36 ) ;
110+ }
111+
112+ foreach ( var bul in chart . Bullets ) Add ( bul . Detail ) ;
113+ foreach ( var note in chart . Notes )
114+ if ( note is IBullet bul ) Add ( bul . Detail ) ;
115+ return ids ;
116+ }
117+
118+ private void EmitHeader ( StringBuilder sb , OgkChart chart )
119+ {
120+ sb . AppendLine ( "[HEADER]" ) ;
121+ sb . AppendLine ( "VERSION\t 1\t 7\t 0" ) ;
122+ sb . AppendLine ( $ "CREATOR\t { chart . Designer } ") ;
123+ var ( first , common , max , min ) = chart . BpmList . BPM_DEF ( ) ;
124+ sb . AppendLine ( FormattableString . Invariant ( $ "BPM_DEF\t { first : F3} \t { common : F3} \t { max : F3} \t { min : F3} ") ) ;
125+ var firstMet = chart . MetList [ 0 ] ;
126+ sb . AppendLine ( $ "MET_DEF\t { firstMet . Numerator } \t { firstMet . Denominator } ") ;
127+ sb . AppendLine ( $ "TRESOLUTION\t { RSL } ") ;
128+ sb . AppendLine ( $ "XRESOLUTION\t { RSL_X } ") ;
129+ sb . AppendLine ( $ "CLK_DEF\t { chart . ClockCount * ( RSL / 4 ) } ") ;
130+ sb . AppendLine ( FormattableString . Invariant ( $ "PROGJUDGE_BPM\t { chart . ProgJudgeBpm : F3} ") ) ;
131+ sb . AppendLine ( "TUTORIAL\t 0" ) ;
132+ sb . AppendLine ( FormattableString . Invariant ( $ "BULLET_DAMAGE\t { chart . BulletDamages [ BulletDamage . NML ] : F3} ") ) ;
133+ sb . AppendLine ( FormattableString . Invariant ( $ "HARDBULLET_DAMAGE\t { chart . BulletDamages [ BulletDamage . STR ] : F3} ") ) ;
134+ sb . AppendLine ( FormattableString . Invariant ( $ "DANGERBULLET_DAMAGE\t { chart . BulletDamages [ BulletDamage . DNG ] : F3} ") ) ;
135+ sb . AppendLine ( FormattableString . Invariant ( $ "BEAM_DAMAGE\t { chart . BeamDamage : F3} ") ) ;
136+
137+ var counts = chart . CountNotes ( ) ;
138+ sb . AppendLine ( $ "T_TOTAL\t { counts [ "T_TOTAL" ] } ") ;
139+ sb . AppendLine ( $ "T_TAP\t { counts [ "T_TAP" ] } ") ;
140+ sb . AppendLine ( $ "T_HOLD\t { counts [ "T_HOLD" ] } ") ;
141+ sb . AppendLine ( $ "T_SIDE\t { counts [ "T_SIDE" ] } ") ;
142+ sb . AppendLine ( $ "T_SHOLD\t { counts [ "T_SHOLD" ] } ") ;
143+ sb . AppendLine ( $ "T_FLICK\t { counts [ "T_FLICK" ] } ") ;
144+ sb . AppendLine ( $ "T_BELL\t { counts [ "T_BELL" ] } ") ;
145+ }
146+
147+ private void EmitBPalette ( StringBuilder sb , Dictionary < BulletPallete , string > palettes )
148+ {
149+ sb . AppendLine ( "[B_PALETTE]" ) ;
150+ foreach ( var ( palette , id ) in palettes )
151+ {
152+ sb . AppendLine ( FormattableString . Invariant (
153+ $ "BPL\t { id } \t { palette . Shooter } \t { palette . TargetOffset } \t { ( palette . TargetToPlayer ? "PLR" : "FIX" ) } \t { palette . Speed : F6} \t { palette . Size } \t { palette . Type } \t { palette . RandomOffsetDist } ") ) ;
154+ }
155+ }
156+
157+ private void EmitComposition ( StringBuilder sb , OgkChart chart )
158+ {
159+ sb . AppendLine ( "[COMPOSITION]" ) ;
160+
161+ // BPM:跳过首项(隐含于HEADER的BPM_DEF)
162+ foreach ( var b in chart . BpmList . Skip ( 1 ) )
163+ {
164+ var ( t , g ) = Utils . BarAndTick ( b . Time , RSL ) ;
165+ sb . AppendLine ( FormattableString . Invariant ( $ "BPM\t { t } \t { g } \t { b . Bpm : F3} ") ) ;
166+ }
167+
168+ // MET:跳过首项(隐含于HEADER的MET_DEF)。
169+ // ogkr中无论MET_DEF还是MET,字段顺序都是Numerator在前、Denominator在后,与maimai刚好相反
170+ foreach ( var m in chart . MetList . Skip ( 1 ) )
171+ {
172+ var ( t , g ) = Utils . BarAndTick ( m . Time , RSL ) ;
173+ sb . AppendLine ( $ "MET\t { t } \t { g } \t { m . Numerator } \t { m . Denominator } ") ;
174+ }
175+
176+ // SFL
177+ foreach ( var ( time , duration , mult ) in chart . SflList )
178+ {
179+ var ( t , g ) = Utils . BarAndTick ( time , RSL ) ;
180+ var durTicks = Utils . Tick ( duration , RSL ) ;
181+ sb . AppendLine ( FormattableString . Invariant ( $ "SFL\t { t } \t { g } \t { durTicks } \t { mult : F6} ") ) ;
182+ }
183+
184+ // CLK(仅当显式声明时输出)
185+ if ( chart . ExplicitClocks != null )
186+ {
187+ foreach ( var time in chart . ExplicitClocks )
188+ {
189+ var ( t , g ) = Utils . BarAndTick ( time , RSL ) ;
190+ sb . AppendLine ( $ "CLK\t { t } \t { g } ") ;
191+ }
192+ }
193+
194+ // EST
195+ foreach ( var ( time , tag ) in chart . EnemyList )
196+ {
197+ var ( t , g ) = Utils . BarAndTick ( time , RSL ) ;
198+ sb . AppendLine ( $ "EST\t { t } \t { g } \t { tag } ") ;
199+ }
200+ }
201+
202+ private void EmitOneLane < T > ( StringBuilder sb , OgkBaseLane < T > lane , int id ) where T : OgkLanePoint
203+ {
204+ var cmdRoot = lane switch
205+ {
206+ Wall { Direction : Direction . L } => "WL" ,
207+ Wall { Direction : Direction . R } => "WR" ,
208+ ColorfulLane => "CL" ,
209+ EnemyMovement => "EN" ,
210+ Lane { Type : LaneType . Red } => "LL" ,
211+ Lane { Type : LaneType . Green } => "LC" ,
212+ Lane { Type : LaneType . Blue } => "LR" ,
213+ Beam { ObliqueOffset : 0 } => "BM" ,
214+ Beam b when b . ObliqueOffset != 0 => "OB" ,
215+ _ => throw new InvalidOperationException ( $ "Unsupported lane { lane } for emission") ,
216+ } ;
217+
218+ var n = lane . Points . Count ;
219+ for ( var i = 0 ; i < n ; i ++ )
220+ {
221+ var cmd = cmdRoot + ( i == 0 ? "S" : i == n - 1 ? "E" : "N" ) ;
222+ var p = lane . Points [ i ] ;
223+ var ( t , g ) = Utils . BarAndTick ( p . Time , RSL ) ;
224+ var result = $ "{ cmd } \t { id } \t { t } \t { g } \t { p . Pos } ";
225+
226+ if ( lane is ColorfulLane cl )
227+ { // 对ColorfulLane,加上colorId和brightnessId;
228+ result += $ "\t { ( int ) cl . Color } \t { cl . Brightness } ";
229+ }
230+ if ( i == 0 && lane is Lane { IsTransparent : true } )
231+ { // 最后再加上表示transparent的1
232+ result += "\t 1" ;
233+ }
234+ if ( lane is Beam beam && p is BeamPoint pp )
235+ { // 对Beam类型,添加上特有的属性
236+ result += $ "\t { pp . Width } ";
237+ if ( beam . ObliqueOffset != 0 ) result += $ "\t { beam . ObliqueOffset } "; // 对OBS/OBN/OBE,所有行都要带上shootPosXUnitOffset字段(虽然仅OBS的值有意义)。
238+ }
239+ sb . AppendLine ( result ) ;
240+ }
241+ sb . AppendLine ( ) ;
242+ }
243+
244+ private void EmitLane ( StringBuilder sb , Dictionary < OgkBaseLane < OgkLanePoint > , int > lanes )
245+ {
246+ sb . AppendLine ( "[LANE]" ) ;
247+ foreach ( var ( lane , id ) in lanes )
248+ EmitOneLane ( sb , lane , id ) ;
249+ }
250+
251+ private void EmitLaneBlock ( StringBuilder sb , Dictionary < OgkBaseLane < OgkLanePoint > , int > ids )
252+ {
253+ sb . AppendLine ( "[LANE_BLOCK]" ) ;
254+
255+ // 收集所有Wall上的Blocks,按“绝对的Fore时刻”排序后输出。
256+ // 同一Fore时刻有多个block时,右墙的block在前(与官谱观察到的惯例一致)。
257+ var entries = ids . Keys . OfType < Wall > ( )
258+ . SelectMany ( w => w . Blocks . Select ( b => ( Wall : w , Block : b , ForeTime : w . Time + b . Start ) ) )
259+ . OrderBy ( x => x . ForeTime )
260+ . ThenBy ( x => x . Wall . Direction == Direction . R ? 0 : 1 )
261+ . ToList ( ) ;
262+
263+ foreach ( var ( wall , block , foreTime ) in entries )
264+ {
265+ var rearTime = foreTime + block . Duration ;
266+ var ( fT , fG ) = Utils . BarAndTick ( foreTime , RSL ) ;
267+ var ( rT , rG ) = Utils . BarAndTick ( rearTime , RSL ) ;
268+ var ( fXU , fXG ) = Utils . BarAndTick ( block . Pos , RSL_X ) ;
269+ var ( rXU , rXG ) = Utils . BarAndTick ( block . EndPos , RSL_X ) ;
270+ sb . AppendLine ( $ "LBK\t { ids [ wall ] } \t { fT } \t { fG } \t { fXU } \t { fXG } \t { rT } \t { rG } \t { rXU } \t { rXG } ") ;
271+ }
272+ }
273+
274+ private void EmitBullet ( StringBuilder sb , OgkChart chart , Dictionary < BulletPallete , string > palettes )
275+ {
276+ sb . AppendLine ( "[BULLET]" ) ;
277+ foreach ( var bullet in chart . Bullets . OfType < Bullet > ( ) )
278+ {
279+ var ( t , g ) = Utils . BarAndTick ( bullet . Time , RSL ) ;
280+ sb . AppendLine ( $ "BLT\t { palettes [ bullet . Detail ] } \t { t } \t { g } \t { bullet . Pos } \t { bullet . Damage } ") ;
281+ }
282+ }
283+
284+ private void EmitBeam ( StringBuilder sb , OgkChart chart )
285+ {
286+ sb . AppendLine ( "[BEAM]" ) ;
287+ var recordId = 0 ;
288+ foreach ( var beam in chart . Bullets . OfType < Beam > ( ) )
289+ EmitOneLane ( sb , beam , recordId ++ ) ;
290+ }
291+
292+ private void EmitBell ( StringBuilder sb , OgkChart chart , Dictionary < BulletPallete , string > palettes )
293+ {
294+ sb . AppendLine ( "[BELL]" ) ;
295+ foreach ( var bell in chart . Notes . OfType < Bell > ( ) )
296+ {
297+ var ( t , g ) = Utils . BarAndTick ( bell . Time , RSL ) ;
298+ var palId = bell . Detail != null ? palettes [ bell . Detail ] : "--" ;
299+ sb . AppendLine ( $ "BEL\t { t } \t { g } \t { bell . Pos . Round ( ) } \t { palId } ") ;
300+ }
301+ }
302+
303+ private void EmitFlick ( StringBuilder sb , OgkChart chart )
304+ {
305+ sb . AppendLine ( "[FLICK]" ) ;
306+ foreach ( var flick in chart . Notes . OfType < Flick > ( ) )
307+ {
308+ var ( t , g ) = Utils . BarAndTick ( flick . Time , RSL ) ;
309+ var cmd = flick . IsEx ? "CFK" : "FLK" ;
310+ sb . AppendLine ( $ "{ cmd } \t { t } \t { g } \t { flick . Pos . Round ( ) } \t { flick . Direction } ") ;
311+ }
312+ }
313+
314+ private void EmitNotes ( StringBuilder sb , OgkChart chart , Dictionary < OgkBaseLane < OgkLanePoint > , int > laneIds )
315+ {
316+ sb . AppendLine ( "[NOTES]" ) ;
317+ foreach ( var tap in chart . Notes . OfType < Tap > ( ) )
318+ {
319+ if ( ! laneIds . TryGetValue ( tap . Lane , out var laneId ) )
320+ {
321+ alerts . Add ( new Alert ( Warning , "音符所引用的Lane未在LANE段中登记" , ( chart , tap . Time ) ) ) ;
322+ continue ;
323+ }
324+ var cmd = tap . IsEx ? "CTP" : "TAP" ;
325+ var ( t , g ) = Utils . BarAndTick ( tap . Time , RSL ) ;
326+ var ( xU , xG ) = Utils . BarAndTick ( tap . Pos , RSL_X ) ;
327+ var paramStr = $ "{ laneId } \t { t } \t { g } \t { xU } \t { xG } ";
328+ if ( tap is Hold hold )
329+ {
330+ cmd = hold . IsEx ? "CHD" : "HLD" ;
331+ var ( rT , rG ) = Utils . BarAndTick ( hold . EndTime , RSL ) ;
332+ var ( rXU , rXG ) = Utils . BarAndTick ( hold . EndPos , RSL_X ) ;
333+ paramStr += $ "\t { rT } \t { rG } \t { rXU } \t { rXG } ";
334+ }
335+ sb . AppendLine ( $ "{ cmd } \t { paramStr } ") ;
336+ }
11337 }
12- }
338+ }
0 commit comments