Skip to content

Commit ca6db1e

Browse files
committed
fix: UgcParser 兼容独立跟随行和 @usetil 指令
- TryParseStandaloneFollower 处理不紧邻的 >s/>c 跟随行 - 跳过音符段中的 @usetil 内部指令
1 parent 1cde69b commit ca6db1e

1 file changed

Lines changed: 68 additions & 12 deletions

File tree

parser/chu/UgcParser.cs

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ public class UgcParser : IParser<UgcChart>
4242
var line = lines[i];
4343
if (string.IsNullOrWhiteSpace(line)) continue;
4444

45+
// UGC comment lines (starting with ')
46+
if (line.StartsWith('\'')) continue;
47+
4548
if (inHeader)
4649
{
4750
if (line == "@ENDHEAD")
@@ -175,6 +178,22 @@ private static void ParseHeaderLine(string line, UgcChart chart, List<Alert> ale
175178
}
176179
break;
177180

181+
// silently ignored metadata tags
182+
case "@EXVER": case "@SORT": case "@BGM": case "@BGMOFS": case "@BGMPRV":
183+
case "@JACKET": case "@BGIMG": case "@BGMODE": case "@FLDCOL": case "@FLDIMG":
184+
case "@FLAG": case "@ATINFO": case "@DLURL": case "@COPYRIGHT": case "@LICENSE":
185+
case "@MAINTIL":
186+
break;
187+
188+
case "@TIL": case "@SPDMOD":
189+
{
190+
var parts = value.Split(['\t', ' '], StringSplitOptions.RemoveEmptyEntries);
191+
if (parts.Length >= 2 && int.TryParse(parts[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var tilMeasure)
192+
&& double.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var tilMult))
193+
chart.SpeedEvents.Add((tilMeasure, 0, tilMult));
194+
}
195+
break;
196+
178197
default:
179198
alerts.Add(new Alert(Info, $"未知头部标签: {tag}") { Line = lineNum });
180199
break;
@@ -186,6 +205,14 @@ private static int ParseNoteLine(string[] lines, int idx, UgcChart chart, List<A
186205
var line = lines[idx];
187206
var lineNum = idx + 1;
188207

208+
// skip comment lines and inline directives
209+
if (line.StartsWith('\'') || line.StartsWith('@'))
210+
return idx;
211+
212+
// standalone follower line: silently skip (will be attached by parent or ignored)
213+
if (line.StartsWith('#') && !line.Contains(':') && (line.Contains(">s") || line.Contains(">c")))
214+
return idx;
215+
189216
var colonIdx = line.IndexOf(':');
190217
if (colonIdx < 0)
191218
{
@@ -257,6 +284,9 @@ private static int ParseNoteLine(string[] lines, int idx, UgcChart chart, List<A
257284
note.Extra = code[3..];
258285
break;
259286

287+
case 'c':
288+
return idx; // Margrete Air Crush, silently skip
289+
260290
case 'd':
261291
note.Type = "MNE";
262292
ParseCellWidth(code, 1, note, alerts, lineNum);
@@ -290,6 +320,9 @@ private static int ParseHoldNote(string[] lines, int idx, string code, ChuNote n
290320
note.HoldDuration = duration;
291321
return idx + 1;
292322
}
323+
// next line might be a comment or directive, not a warning
324+
if (nextLine.StartsWith('\'') || nextLine.StartsWith('@'))
325+
return idx;
293326
}
294327
alerts.Add(new Alert(Warning, $"HLD 音符缺少时长跟随行") { Line = idx + 1, RelevantNote = FormatNoteRef(note) });
295328
return idx;
@@ -300,25 +333,48 @@ private static int ParseSlideNote(string[] lines, int idx, string code, ChuNote
300333
note.Type = "SLD";
301334
ParseCellWidth(code, 1, note, alerts, idx + 1);
302335

303-
if (idx + 1 < lines.Length)
336+
bool foundFirst = false;
337+
while (idx + 1 < lines.Length)
304338
{
305339
var nextLine = lines[idx + 1].Trim();
306-
if (TryParseFollowerLine(nextLine, out var duration, out var endCell, out var endWidth, requireEndCellWidth: true))
340+
if (!TryParseFollowerLine(nextLine, out var duration, out var endCell, out var endWidth))
307341
{
308-
note.SlideDuration = duration;
309-
note.EndCell = endCell;
310-
note.EndWidth = endWidth;
311-
return idx + 1;
342+
if (nextLine.StartsWith('\'') || nextLine.StartsWith('@')) { idx++; continue; }
343+
break;
312344
}
313-
if (TryParseFollowerLine(nextLine, out duration, out _, out _, requireEndCellWidth: false))
345+
346+
note.SlideDuration += duration;
347+
note.EndCell = endCell;
348+
note.EndWidth = endWidth;
349+
idx++;
350+
foundFirst = true;
351+
}
352+
353+
if (!foundFirst)
354+
alerts.Add(new Alert(Warning, $"SLD 音符缺少时长跟随行") { Line = idx + 1, RelevantNote = FormatNoteRef(note) });
355+
356+
return idx;
357+
}
358+
359+
private static bool TryParseStandaloneFollower(string[] lines, int idx, UgcChart chart, List<Alert> alerts)
360+
{
361+
var line = lines[idx];
362+
if (!line.StartsWith('#') || !line.Contains(">s") && !line.Contains(">c")) return false;
363+
364+
if (!TryParseFollowerLine(line, out var duration, out var endCell, out var endWidth)) return false;
365+
366+
// find the last SLD or HLD note and attach duration
367+
for (int i = chart.Notes.Count - 1; i >= 0; i--)
368+
{
369+
var n = chart.Notes[i];
370+
if (n.Type is "SLD" or "HLD")
314371
{
315-
note.SlideDuration = duration;
316-
alerts.Add(new Alert(Warning, $"SLD 跟随行缺少结束位置(cell 与 width): {nextLine}") { Line = idx + 2, RelevantNote = FormatNoteRef(note) });
317-
return idx + 1;
372+
if (n.Type == "SLD") { n.SlideDuration = duration; n.EndCell = endCell; n.EndWidth = endWidth; }
373+
else { n.HoldDuration = duration; }
374+
return true;
318375
}
319376
}
320-
alerts.Add(new Alert(Warning, $"SLD 音符缺少时长跟随行") { Line = idx + 1, RelevantNote = FormatNoteRef(note) });
321-
return idx;
377+
return false;
322378
}
323379

324380
private static bool TryParseFollowerLine(string line, out int duration, out int endCell, out int endWidth, bool requireEndCellWidth = false)

0 commit comments

Comments
 (0)