Skip to content

Commit e2bc23a

Browse files
committed
refactor: refactor: 根据ChuNote的重构,把UgcGenerator改好
1 parent 5aad72e commit e2bc23a

1 file changed

Lines changed: 35 additions & 109 deletions

File tree

generator/chu/UgcGenerator.cs

Lines changed: 35 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)