mirror of
https://github.com/ppy/osu.git
synced 2025-02-13 15:53:51 +08:00
Merge pull request #7183 from smoogipoo/legacy-beatmap-saving
Add initial implementation of a legacy beatmap encoder
This commit is contained in:
commit
5cdc7d3b18
54
osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
Normal file
54
osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.IO.Serialization;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
[TestFixture]
|
||||
public class LegacyBeatmapEncoderTest
|
||||
{
|
||||
private const string normal = "Soleily - Renatus (Gamu) [Insane].osu";
|
||||
|
||||
private static IEnumerable<string> allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu"));
|
||||
|
||||
[TestCaseSource(nameof(allBeatmaps))]
|
||||
public void TestDecodeEncodedBeatmap(string name)
|
||||
{
|
||||
var decoded = decode(normal, out var encoded);
|
||||
|
||||
Assert.That(decoded.HitObjects.Count, Is.EqualTo(encoded.HitObjects.Count));
|
||||
Assert.That(encoded.Serialize(), Is.EqualTo(decoded.Serialize()));
|
||||
}
|
||||
|
||||
private Beatmap decode(string filename, out Beatmap encoded)
|
||||
{
|
||||
using (var stream = TestResources.OpenResource(filename))
|
||||
using (var sr = new LineBufferedReader(stream))
|
||||
{
|
||||
var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr);
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
using (var sw = new StreamWriter(ms))
|
||||
using (var sr2 = new LineBufferedReader(ms))
|
||||
{
|
||||
new LegacyBeatmapEncoder(legacyDecoded).Encode(sw);
|
||||
sw.Flush();
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
encoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr2);
|
||||
return legacyDecoded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,9 @@ namespace osu.Game.Tests.Resources
|
||||
{
|
||||
public static class TestResources
|
||||
{
|
||||
public static Stream OpenResource(string name) => new DllResourceStore("osu.Game.Tests.dll").GetStream($"Resources/{name}");
|
||||
public static DllResourceStore GetStore() => new DllResourceStore("osu.Game.Tests.dll");
|
||||
|
||||
public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}");
|
||||
|
||||
public static Stream GetTestBeatmapStream(bool virtualTrack = false) => new DllResourceStore("osu.Game.Resources.dll").GetStream($"Beatmaps/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz");
|
||||
|
||||
|
@ -10,6 +10,7 @@ using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
|
||||
namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
@ -293,22 +294,22 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
string[] split = line.Split(',');
|
||||
|
||||
if (!Enum.TryParse(split[0], out EventType type))
|
||||
if (!Enum.TryParse(split[0], out LegacyEventType type))
|
||||
throw new InvalidDataException($@"Unknown event type: {split[0]}");
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case EventType.Background:
|
||||
case LegacyEventType.Background:
|
||||
string bgFilename = split[2].Trim('"');
|
||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = bgFilename.ToStandardisedPath();
|
||||
break;
|
||||
|
||||
case EventType.Video:
|
||||
case LegacyEventType.Video:
|
||||
string videoFilename = split[2].Trim('"');
|
||||
beatmap.BeatmapInfo.Metadata.VideoFile = videoFilename.ToStandardisedPath();
|
||||
break;
|
||||
|
||||
case EventType.Break:
|
||||
case LegacyEventType.Break:
|
||||
double start = getOffsetTime(Parsing.ParseDouble(split[1]));
|
||||
|
||||
var breakEvent = new BreakPeriod
|
||||
@ -358,9 +359,9 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
if (split.Length >= 8)
|
||||
{
|
||||
EffectFlags effectFlags = (EffectFlags)Parsing.ParseInt(split[7]);
|
||||
kiaiMode = effectFlags.HasFlag(EffectFlags.Kiai);
|
||||
omitFirstBarSignature = effectFlags.HasFlag(EffectFlags.OmitFirstBarLine);
|
||||
LegacyEffectFlags effectFlags = (LegacyEffectFlags)Parsing.ParseInt(split[7]);
|
||||
kiaiMode = effectFlags.HasFlag(LegacyEffectFlags.Kiai);
|
||||
omitFirstBarSignature = effectFlags.HasFlag(LegacyEffectFlags.OmitFirstBarLine);
|
||||
}
|
||||
|
||||
string stringSampleSet = sampleSet.ToString().ToLowerInvariant();
|
||||
@ -448,13 +449,5 @@ namespace osu.Game.Beatmaps.Formats
|
||||
private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0);
|
||||
|
||||
protected virtual TimingControlPoint CreateTimingControlPoint() => new TimingControlPoint();
|
||||
|
||||
[Flags]
|
||||
internal enum EffectFlags
|
||||
{
|
||||
None = 0,
|
||||
Kiai = 1,
|
||||
OmitFirstBarLine = 8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
410
osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
Normal file
410
osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
Normal file
@ -0,0 +1,410 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
public class LegacyBeatmapEncoder
|
||||
{
|
||||
public const int LATEST_VERSION = 128;
|
||||
|
||||
private readonly IBeatmap beatmap;
|
||||
|
||||
public LegacyBeatmapEncoder(IBeatmap beatmap)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
|
||||
if (beatmap.BeatmapInfo.RulesetID < 0 || beatmap.BeatmapInfo.RulesetID > 3)
|
||||
throw new ArgumentException("Only beatmaps in the osu, taiko, catch, or mania rulesets can be encoded to the legacy beatmap format.", nameof(beatmap));
|
||||
}
|
||||
|
||||
public void Encode(TextWriter writer)
|
||||
{
|
||||
writer.WriteLine($"osu file format v{LATEST_VERSION}");
|
||||
|
||||
writer.WriteLine();
|
||||
handleGeneral(writer);
|
||||
|
||||
writer.WriteLine();
|
||||
handleEditor(writer);
|
||||
|
||||
writer.WriteLine();
|
||||
handleMetadata(writer);
|
||||
|
||||
writer.WriteLine();
|
||||
handleDifficulty(writer);
|
||||
|
||||
writer.WriteLine();
|
||||
handleEvents(writer);
|
||||
|
||||
writer.WriteLine();
|
||||
handleTimingPoints(writer);
|
||||
|
||||
writer.WriteLine();
|
||||
handleHitObjects(writer);
|
||||
}
|
||||
|
||||
private void handleGeneral(TextWriter writer)
|
||||
{
|
||||
writer.WriteLine("[General]");
|
||||
|
||||
writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}"));
|
||||
// Todo: Not all countdown types are supported by lazer yet
|
||||
writer.WriteLine(FormattableString.Invariant($"Countdown: {(beatmap.BeatmapInfo.Countdown ? '1' : '0')}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank(beatmap.ControlPointInfo.SamplePoints[0].SampleBank)}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Mode: {beatmap.BeatmapInfo.RulesetID}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}"));
|
||||
// if (beatmap.BeatmapInfo.UseSkinSprites)
|
||||
// writer.WriteLine(@"UseSkinSprites: 1");
|
||||
// if (b.AlwaysShowPlayfield)
|
||||
// writer.WriteLine(@"AlwaysShowPlayfield: 1");
|
||||
// if (b.OverlayPosition != OverlayPosition.NoChange)
|
||||
// writer.WriteLine(@"OverlayPosition: " + b.OverlayPosition);
|
||||
// if (!string.IsNullOrEmpty(b.SkinPreference))
|
||||
// writer.WriteLine(@"SkinPreference:" + b.SkinPreference);
|
||||
// if (b.EpilepsyWarning)
|
||||
// writer.WriteLine(@"EpilepsyWarning: 1");
|
||||
// if (b.CountdownOffset > 0)
|
||||
// writer.WriteLine(@"CountdownOffset: " + b.CountdownOffset.ToString());
|
||||
if (beatmap.BeatmapInfo.RulesetID == 3)
|
||||
writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}"));
|
||||
// if (b.SamplesMatchPlaybackRate)
|
||||
// writer.WriteLine(@"SamplesMatchPlaybackRate: 1");
|
||||
}
|
||||
|
||||
private void handleEditor(TextWriter writer)
|
||||
{
|
||||
writer.WriteLine("[Editor]");
|
||||
|
||||
if (beatmap.BeatmapInfo.Bookmarks.Length > 0)
|
||||
writer.WriteLine(FormattableString.Invariant($"Bookmarks: {string.Join(',', beatmap.BeatmapInfo.Bookmarks)}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.BeatmapInfo.DistanceSpacing}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"BeatDivisor: {beatmap.BeatmapInfo.BeatDivisor}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.BeatmapInfo.GridSize}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"TimelineZoom: {beatmap.BeatmapInfo.TimelineZoom}"));
|
||||
}
|
||||
|
||||
private void handleMetadata(TextWriter writer)
|
||||
{
|
||||
writer.WriteLine("[Metadata]");
|
||||
|
||||
writer.WriteLine(FormattableString.Invariant($"Title: {beatmap.Metadata.Title}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Artist: {beatmap.Metadata.Artist}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Creator: {beatmap.Metadata.AuthorString}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.Version}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID ?? 0}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID ?? -1}"));
|
||||
}
|
||||
|
||||
private void handleDifficulty(TextWriter writer)
|
||||
{
|
||||
writer.WriteLine("[Difficulty]");
|
||||
|
||||
writer.WriteLine(FormattableString.Invariant($"HPDrainRate: {beatmap.BeatmapInfo.BaseDifficulty.DrainRate}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"CircleSize: {beatmap.BeatmapInfo.BaseDifficulty.CircleSize}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"OverallDifficulty: {beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.BeatmapInfo.BaseDifficulty.ApproachRate}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"SliderTickRate: {beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate}"));
|
||||
}
|
||||
|
||||
private void handleEvents(TextWriter writer)
|
||||
{
|
||||
writer.WriteLine("[Events]");
|
||||
|
||||
if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile))
|
||||
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Background},0,\"{beatmap.BeatmapInfo.Metadata.BackgroundFile}\",0,0"));
|
||||
|
||||
if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.VideoFile))
|
||||
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Video},0,\"{beatmap.BeatmapInfo.Metadata.VideoFile}\",0,0"));
|
||||
|
||||
foreach (var b in beatmap.Breaks)
|
||||
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}"));
|
||||
}
|
||||
|
||||
private void handleTimingPoints(TextWriter writer)
|
||||
{
|
||||
if (beatmap.ControlPointInfo.Groups.Count == 0)
|
||||
return;
|
||||
|
||||
writer.WriteLine("[TimingPoints]");
|
||||
|
||||
foreach (var group in beatmap.ControlPointInfo.Groups)
|
||||
{
|
||||
var timingPoint = group.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault();
|
||||
var difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(group.Time);
|
||||
var samplePoint = beatmap.ControlPointInfo.SamplePointAt(group.Time);
|
||||
var effectPoint = beatmap.ControlPointInfo.EffectPointAt(group.Time);
|
||||
|
||||
// Convert beat length the legacy format
|
||||
double beatLength;
|
||||
if (timingPoint != null)
|
||||
beatLength = timingPoint.BeatLength;
|
||||
else
|
||||
beatLength = -100 / difficultyPoint.SpeedMultiplier;
|
||||
|
||||
// Apply the control point to a hit sample to uncover legacy properties (e.g. suffix)
|
||||
HitSampleInfo tempHitSample = samplePoint.ApplyTo(new HitSampleInfo());
|
||||
|
||||
// Convert effect flags to the legacy format
|
||||
LegacyEffectFlags effectFlags = LegacyEffectFlags.None;
|
||||
if (effectPoint.KiaiMode)
|
||||
effectFlags |= LegacyEffectFlags.Kiai;
|
||||
if (effectPoint.OmitFirstBarLine)
|
||||
effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
|
||||
|
||||
writer.Write(FormattableString.Invariant($"{group.Time},"));
|
||||
writer.Write(FormattableString.Invariant($"{beatLength},"));
|
||||
writer.Write(FormattableString.Invariant($"{(int)beatmap.ControlPointInfo.TimingPointAt(group.Time).TimeSignature},"));
|
||||
writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},"));
|
||||
writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample.Suffix)},"));
|
||||
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));
|
||||
writer.Write(FormattableString.Invariant($"{(timingPoint != null ? '1' : '0')},"));
|
||||
writer.Write(FormattableString.Invariant($"{(int)effectFlags}"));
|
||||
writer.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHitObjects(TextWriter writer)
|
||||
{
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return;
|
||||
|
||||
writer.WriteLine("[HitObjects]");
|
||||
|
||||
switch (beatmap.BeatmapInfo.RulesetID)
|
||||
{
|
||||
case 0:
|
||||
foreach (var h in beatmap.HitObjects)
|
||||
handleOsuHitObject(writer, h);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
foreach (var h in beatmap.HitObjects)
|
||||
handleTaikoHitObject(writer, h);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
foreach (var h in beatmap.HitObjects)
|
||||
handleCatchHitObject(writer, h);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
foreach (var h in beatmap.HitObjects)
|
||||
handleManiaHitObject(writer, h);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleOsuHitObject(TextWriter writer, HitObject hitObject)
|
||||
{
|
||||
var positionData = (IHasPosition)hitObject;
|
||||
|
||||
writer.Write(FormattableString.Invariant($"{positionData.X},"));
|
||||
writer.Write(FormattableString.Invariant($"{positionData.Y},"));
|
||||
writer.Write(FormattableString.Invariant($"{hitObject.StartTime},"));
|
||||
writer.Write(FormattableString.Invariant($"{(int)getObjectType(hitObject)},"));
|
||||
|
||||
writer.Write(hitObject is IHasCurve
|
||||
? FormattableString.Invariant($"0,")
|
||||
: FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},"));
|
||||
|
||||
if (hitObject is IHasCurve curveData)
|
||||
{
|
||||
addCurveData(writer, curveData, positionData);
|
||||
writer.Write(getSampleBank(hitObject.Samples, zeroBanks: true));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hitObject is IHasEndTime endTimeData)
|
||||
writer.Write(FormattableString.Invariant($"{endTimeData.EndTime},"));
|
||||
writer.Write(getSampleBank(hitObject.Samples));
|
||||
}
|
||||
|
||||
writer.WriteLine();
|
||||
}
|
||||
|
||||
private static LegacyHitObjectType getObjectType(HitObject hitObject)
|
||||
{
|
||||
var comboData = (IHasCombo)hitObject;
|
||||
|
||||
var type = (LegacyHitObjectType)(comboData.ComboOffset << 4);
|
||||
|
||||
if (comboData.NewCombo) type |= LegacyHitObjectType.NewCombo;
|
||||
|
||||
switch (hitObject)
|
||||
{
|
||||
case IHasCurve _:
|
||||
type |= LegacyHitObjectType.Slider;
|
||||
break;
|
||||
|
||||
case IHasEndTime _:
|
||||
type |= LegacyHitObjectType.Spinner | LegacyHitObjectType.NewCombo;
|
||||
break;
|
||||
|
||||
default:
|
||||
type |= LegacyHitObjectType.Circle;
|
||||
break;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private void addCurveData(TextWriter writer, IHasCurve curveData, IHasPosition positionData)
|
||||
{
|
||||
PathType? lastType = null;
|
||||
|
||||
for (int i = 0; i < curveData.Path.ControlPoints.Count; i++)
|
||||
{
|
||||
PathControlPoint point = curveData.Path.ControlPoints[i];
|
||||
|
||||
if (point.Type.Value != null)
|
||||
{
|
||||
if (point.Type.Value != lastType)
|
||||
{
|
||||
switch (point.Type.Value)
|
||||
{
|
||||
case PathType.Bezier:
|
||||
writer.Write("B|");
|
||||
break;
|
||||
|
||||
case PathType.Catmull:
|
||||
writer.Write("C|");
|
||||
break;
|
||||
|
||||
case PathType.PerfectCurve:
|
||||
writer.Write("P|");
|
||||
break;
|
||||
|
||||
case PathType.Linear:
|
||||
writer.Write("L|");
|
||||
break;
|
||||
}
|
||||
|
||||
lastType = point.Type.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// New segment with the same type - duplicate the control point
|
||||
writer.Write(FormattableString.Invariant($"{positionData.X + point.Position.Value.X}:{positionData.Y + point.Position.Value.Y}|"));
|
||||
}
|
||||
}
|
||||
|
||||
if (i != 0)
|
||||
{
|
||||
writer.Write(FormattableString.Invariant($"{positionData.X + point.Position.Value.X}:{positionData.Y + point.Position.Value.Y}"));
|
||||
writer.Write(i != curveData.Path.ControlPoints.Count - 1 ? "|" : ",");
|
||||
}
|
||||
}
|
||||
|
||||
writer.Write(FormattableString.Invariant($"{curveData.RepeatCount + 1},"));
|
||||
writer.Write(FormattableString.Invariant($"{curveData.Path.Distance},"));
|
||||
|
||||
for (int i = 0; i < curveData.NodeSamples.Count; i++)
|
||||
{
|
||||
writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}"));
|
||||
writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ",");
|
||||
}
|
||||
|
||||
for (int i = 0; i < curveData.NodeSamples.Count; i++)
|
||||
{
|
||||
writer.Write(getSampleBank(curveData.NodeSamples[i], true));
|
||||
writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ",");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTaikoHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
|
||||
|
||||
private void handleCatchHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
|
||||
|
||||
private void handleManiaHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
|
||||
|
||||
private string getSampleBank(IList<HitSampleInfo> samples, bool banksOnly = false, bool zeroBanks = false)
|
||||
{
|
||||
LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank);
|
||||
LegacySampleBank addBank = toLegacySampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name) && s.Name != HitSampleInfo.HIT_NORMAL)?.Bank);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.Append(FormattableString.Invariant($"{(zeroBanks ? 0 : (int)normalBank)}:"));
|
||||
sb.Append(FormattableString.Invariant($"{(zeroBanks ? 0 : (int)addBank)}"));
|
||||
|
||||
if (!banksOnly)
|
||||
{
|
||||
string customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name))?.Suffix);
|
||||
string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty;
|
||||
int volume = samples.FirstOrDefault()?.Volume ?? 100;
|
||||
|
||||
sb.Append(":");
|
||||
sb.Append(FormattableString.Invariant($"{customSampleBank}:"));
|
||||
sb.Append(FormattableString.Invariant($"{volume}:"));
|
||||
sb.Append(FormattableString.Invariant($"{sampleFilename}"));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private LegacyHitSoundType toLegacyHitSoundType(IList<HitSampleInfo> samples)
|
||||
{
|
||||
LegacyHitSoundType type = LegacyHitSoundType.None;
|
||||
|
||||
foreach (var sample in samples)
|
||||
{
|
||||
switch (sample.Name)
|
||||
{
|
||||
case HitSampleInfo.HIT_WHISTLE:
|
||||
type |= LegacyHitSoundType.Whistle;
|
||||
break;
|
||||
|
||||
case HitSampleInfo.HIT_FINISH:
|
||||
type |= LegacyHitSoundType.Finish;
|
||||
break;
|
||||
|
||||
case HitSampleInfo.HIT_CLAP:
|
||||
type |= LegacyHitSoundType.Clap;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private LegacySampleBank toLegacySampleBank(string sampleBank)
|
||||
{
|
||||
switch (sampleBank?.ToLowerInvariant())
|
||||
{
|
||||
case "normal":
|
||||
return LegacySampleBank.Normal;
|
||||
|
||||
case "soft":
|
||||
return LegacySampleBank.Soft;
|
||||
|
||||
case "drum":
|
||||
return LegacySampleBank.Drum;
|
||||
|
||||
default:
|
||||
return LegacySampleBank.None;
|
||||
}
|
||||
}
|
||||
|
||||
private string toLegacyCustomSampleBank(string sampleSuffix) => string.IsNullOrEmpty(sampleSuffix) ? "0" : sampleSuffix;
|
||||
}
|
||||
}
|
@ -148,47 +148,6 @@ namespace osu.Game.Beatmaps.Formats
|
||||
Fonts
|
||||
}
|
||||
|
||||
internal enum LegacySampleBank
|
||||
{
|
||||
None = 0,
|
||||
Normal = 1,
|
||||
Soft = 2,
|
||||
Drum = 3
|
||||
}
|
||||
|
||||
internal enum EventType
|
||||
{
|
||||
Background = 0,
|
||||
Video = 1,
|
||||
Break = 2,
|
||||
Colour = 3,
|
||||
Sprite = 4,
|
||||
Sample = 5,
|
||||
Animation = 6
|
||||
}
|
||||
|
||||
internal enum LegacyOrigins
|
||||
{
|
||||
TopLeft,
|
||||
Centre,
|
||||
CentreLeft,
|
||||
TopRight,
|
||||
BottomCentre,
|
||||
TopCentre,
|
||||
Custom,
|
||||
CentreRight,
|
||||
BottomLeft,
|
||||
BottomRight
|
||||
}
|
||||
|
||||
internal enum StoryLayer
|
||||
{
|
||||
Background = 0,
|
||||
Fail = 1,
|
||||
Pass = 2,
|
||||
Foreground = 3
|
||||
}
|
||||
|
||||
internal class LegacyDifficultyControlPoint : DifficultyControlPoint
|
||||
{
|
||||
public LegacyDifficultyControlPoint()
|
||||
|
@ -12,6 +12,7 @@ using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
|
||||
namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
@ -83,12 +84,12 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
storyboardSprite = null;
|
||||
|
||||
if (!Enum.TryParse(split[0], out EventType type))
|
||||
if (!Enum.TryParse(split[0], out LegacyEventType type))
|
||||
throw new InvalidDataException($@"Unknown event type: {split[0]}");
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case EventType.Sprite:
|
||||
case LegacyEventType.Sprite:
|
||||
{
|
||||
var layer = parseLayer(split[1]);
|
||||
var origin = parseOrigin(split[2]);
|
||||
@ -100,7 +101,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
break;
|
||||
}
|
||||
|
||||
case EventType.Animation:
|
||||
case LegacyEventType.Animation:
|
||||
{
|
||||
var layer = parseLayer(split[1]);
|
||||
var origin = parseOrigin(split[2]);
|
||||
@ -115,7 +116,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
break;
|
||||
}
|
||||
|
||||
case EventType.Sample:
|
||||
case LegacyEventType.Sample:
|
||||
{
|
||||
var time = double.Parse(split[1], CultureInfo.InvariantCulture);
|
||||
var layer = parseLayer(split[2]);
|
||||
@ -271,7 +272,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
private string parseLayer(string value) => Enum.Parse(typeof(StoryLayer), value).ToString();
|
||||
private string parseLayer(string value) => Enum.Parse(typeof(LegacyStoryLayer), value).ToString();
|
||||
|
||||
private Anchor parseOrigin(string value)
|
||||
{
|
||||
|
15
osu.Game/Beatmaps/Legacy/LegacyEffectFlags.cs
Normal file
15
osu.Game/Beatmaps/Legacy/LegacyEffectFlags.cs
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Beatmaps.Legacy
|
||||
{
|
||||
[Flags]
|
||||
internal enum LegacyEffectFlags
|
||||
{
|
||||
None = 0,
|
||||
Kiai = 1,
|
||||
OmitFirstBarLine = 8
|
||||
}
|
||||
}
|
16
osu.Game/Beatmaps/Legacy/LegacyEventType.cs
Normal file
16
osu.Game/Beatmaps/Legacy/LegacyEventType.cs
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Beatmaps.Legacy
|
||||
{
|
||||
internal enum LegacyEventType
|
||||
{
|
||||
Background = 0,
|
||||
Video = 1,
|
||||
Break = 2,
|
||||
Colour = 3,
|
||||
Sprite = 4,
|
||||
Sample = 5,
|
||||
Animation = 6
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
namespace osu.Game.Beatmaps.Legacy
|
||||
{
|
||||
[Flags]
|
||||
internal enum ConvertHitObjectType
|
||||
internal enum LegacyHitObjectType
|
||||
{
|
||||
Circle = 1,
|
||||
Slider = 1 << 1,
|
17
osu.Game/Beatmaps/Legacy/LegacyHitSoundType.cs
Normal file
17
osu.Game/Beatmaps/Legacy/LegacyHitSoundType.cs
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Beatmaps.Legacy
|
||||
{
|
||||
[Flags]
|
||||
internal enum LegacyHitSoundType
|
||||
{
|
||||
None = 0,
|
||||
Normal = 1,
|
||||
Whistle = 2,
|
||||
Finish = 4,
|
||||
Clap = 8
|
||||
}
|
||||
}
|
19
osu.Game/Beatmaps/Legacy/LegacyOrigins.cs
Normal file
19
osu.Game/Beatmaps/Legacy/LegacyOrigins.cs
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Beatmaps.Legacy
|
||||
{
|
||||
internal enum LegacyOrigins
|
||||
{
|
||||
TopLeft,
|
||||
Centre,
|
||||
CentreLeft,
|
||||
TopRight,
|
||||
BottomCentre,
|
||||
TopCentre,
|
||||
Custom,
|
||||
CentreRight,
|
||||
BottomLeft,
|
||||
BottomRight
|
||||
}
|
||||
}
|
13
osu.Game/Beatmaps/Legacy/LegacySampleBank.cs
Normal file
13
osu.Game/Beatmaps/Legacy/LegacySampleBank.cs
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Beatmaps.Legacy
|
||||
{
|
||||
internal enum LegacySampleBank
|
||||
{
|
||||
None = 0,
|
||||
Normal = 1,
|
||||
Soft = 2,
|
||||
Drum = 3
|
||||
}
|
||||
}
|
13
osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs
Normal file
13
osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Beatmaps.Legacy
|
||||
{
|
||||
internal enum LegacyStoryLayer
|
||||
{
|
||||
Background = 0,
|
||||
Fail = 1,
|
||||
Pass = 2,
|
||||
Foreground = 3
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ using osu.Game.Audio;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
{
|
||||
@ -46,27 +47,27 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
|
||||
double startTime = Parsing.ParseDouble(split[2]) + Offset;
|
||||
|
||||
ConvertHitObjectType type = (ConvertHitObjectType)Parsing.ParseInt(split[3]);
|
||||
LegacyHitObjectType type = (LegacyHitObjectType)Parsing.ParseInt(split[3]);
|
||||
|
||||
int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4;
|
||||
type &= ~ConvertHitObjectType.ComboOffset;
|
||||
int comboOffset = (int)(type & LegacyHitObjectType.ComboOffset) >> 4;
|
||||
type &= ~LegacyHitObjectType.ComboOffset;
|
||||
|
||||
bool combo = type.HasFlag(ConvertHitObjectType.NewCombo);
|
||||
type &= ~ConvertHitObjectType.NewCombo;
|
||||
bool combo = type.HasFlag(LegacyHitObjectType.NewCombo);
|
||||
type &= ~LegacyHitObjectType.NewCombo;
|
||||
|
||||
var soundType = (LegacySoundType)Parsing.ParseInt(split[4]);
|
||||
var soundType = (LegacyHitSoundType)Parsing.ParseInt(split[4]);
|
||||
var bankInfo = new SampleBankInfo();
|
||||
|
||||
HitObject result = null;
|
||||
|
||||
if (type.HasFlag(ConvertHitObjectType.Circle))
|
||||
if (type.HasFlag(LegacyHitObjectType.Circle))
|
||||
{
|
||||
result = CreateHit(pos, combo, comboOffset);
|
||||
|
||||
if (split.Length > 5)
|
||||
readCustomSampleBanks(split[5], bankInfo);
|
||||
}
|
||||
else if (type.HasFlag(ConvertHitObjectType.Slider))
|
||||
else if (type.HasFlag(LegacyHitObjectType.Slider))
|
||||
{
|
||||
PathType pathType = PathType.Catmull;
|
||||
double? length = null;
|
||||
@ -157,7 +158,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
}
|
||||
|
||||
// Populate node sound types with the default hit object sound type
|
||||
var nodeSoundTypes = new List<LegacySoundType>();
|
||||
var nodeSoundTypes = new List<LegacyHitSoundType>();
|
||||
for (int i = 0; i < nodes; i++)
|
||||
nodeSoundTypes.Add(soundType);
|
||||
|
||||
@ -172,7 +173,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
break;
|
||||
|
||||
int.TryParse(adds[i], out var sound);
|
||||
nodeSoundTypes[i] = (LegacySoundType)sound;
|
||||
nodeSoundTypes[i] = (LegacyHitSoundType)sound;
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,7 +187,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
// The samples are played when the slider ends, which is the last node
|
||||
result.Samples = nodeSamples[^1];
|
||||
}
|
||||
else if (type.HasFlag(ConvertHitObjectType.Spinner))
|
||||
else if (type.HasFlag(LegacyHitObjectType.Spinner))
|
||||
{
|
||||
double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset);
|
||||
|
||||
@ -195,7 +196,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
if (split.Length > 6)
|
||||
readCustomSampleBanks(split[6], bankInfo);
|
||||
}
|
||||
else if (type.HasFlag(ConvertHitObjectType.Hold))
|
||||
else if (type.HasFlag(LegacyHitObjectType.Hold))
|
||||
{
|
||||
// Note: Hold is generated by BMS converts
|
||||
|
||||
@ -231,8 +232,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
|
||||
string[] split = str.Split(':');
|
||||
|
||||
var bank = (LegacyBeatmapDecoder.LegacySampleBank)Parsing.ParseInt(split[0]);
|
||||
var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Parsing.ParseInt(split[1]);
|
||||
var bank = (LegacySampleBank)Parsing.ParseInt(split[0]);
|
||||
var addbank = (LegacySampleBank)Parsing.ParseInt(split[1]);
|
||||
|
||||
string stringBank = bank.ToString().ToLowerInvariant();
|
||||
if (stringBank == @"none")
|
||||
@ -333,7 +334,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
/// <param name="endTime">The hold end time.</param>
|
||||
protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime);
|
||||
|
||||
private List<HitSampleInfo> convertSoundType(LegacySoundType type, SampleBankInfo bankInfo)
|
||||
private List<HitSampleInfo> convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo)
|
||||
{
|
||||
// Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario
|
||||
if (!string.IsNullOrEmpty(bankInfo.Filename))
|
||||
@ -359,7 +360,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
}
|
||||
};
|
||||
|
||||
if (type.HasFlag(LegacySoundType.Finish))
|
||||
if (type.HasFlag(LegacyHitSoundType.Finish))
|
||||
{
|
||||
soundTypes.Add(new LegacyHitSampleInfo
|
||||
{
|
||||
@ -370,7 +371,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
});
|
||||
}
|
||||
|
||||
if (type.HasFlag(LegacySoundType.Whistle))
|
||||
if (type.HasFlag(LegacyHitSoundType.Whistle))
|
||||
{
|
||||
soundTypes.Add(new LegacyHitSampleInfo
|
||||
{
|
||||
@ -381,7 +382,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
});
|
||||
}
|
||||
|
||||
if (type.HasFlag(LegacySoundType.Clap))
|
||||
if (type.HasFlag(LegacyHitSoundType.Clap))
|
||||
{
|
||||
soundTypes.Add(new LegacyHitSampleInfo
|
||||
{
|
||||
@ -430,15 +431,5 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
Path.ChangeExtension(Filename, null)
|
||||
};
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum LegacySoundType
|
||||
{
|
||||
None = 0,
|
||||
Normal = 1,
|
||||
Whistle = 2,
|
||||
Finish = 4,
|
||||
Clap = 8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user