1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-22 17:12:54 +08:00

Merge pull request #30578 from smoogipoo/beatmap-legacy-object-type

Simplify convertible hitobject parsing and add `IHasLegacyHitObjectType`
This commit is contained in:
Dean Herbert 2024-11-14 16:16:44 +09:00 committed by GitHub
commit 7f8eebc510
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 171 additions and 559 deletions

View File

@ -1,12 +1,9 @@
// 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.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@ -23,7 +20,6 @@ using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko;
@ -35,15 +31,11 @@ namespace osu.Game.Tests.Visual.SongSelect
[TestFixture]
public partial class TestSceneBeatmapInfoWedge : OsuTestScene
{
private RulesetStore rulesets;
private TestBeatmapInfoWedge infoWedge;
private readonly List<IBeatmap> beatmaps = new List<IBeatmap>();
[Resolved]
private RulesetStore rulesets { get; set; } = null!;
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
}
private TestBeatmapInfoWedge infoWedge = null!;
private readonly List<IBeatmap> beatmaps = new List<IBeatmap>();
protected override void LoadComplete()
{
@ -156,7 +148,7 @@ namespace osu.Game.Tests.Visual.SongSelect
IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo);
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / bpm });
OsuModDoubleTime doubleTime = null;
OsuModDoubleTime doubleTime = null!;
selectBeatmap(beatmap);
checkDisplayedBPM($"{bpm}");
@ -173,7 +165,7 @@ namespace osu.Game.Tests.Visual.SongSelect
[TestCase(120, 120.4, null, "120")]
[TestCase(120, 120.6, "DT", "180-182 (mostly 180)")]
[TestCase(120, 120.4, "DT", "180")]
public void TestVaryingBPM(double commonBpm, double otherBpm, string mod, string expectedDisplay)
public void TestVaryingBPM(double commonBpm, double otherBpm, string? mod, string expectedDisplay)
{
IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo);
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / commonBpm });
@ -203,7 +195,7 @@ namespace osu.Game.Tests.Visual.SongSelect
double drain = beatmap.CalculateDrainLength();
beatmap.BeatmapInfo.Length = drain;
OsuModDoubleTime doubleTime = null;
OsuModDoubleTime doubleTime = null!;
selectBeatmap(beatmap);
checkDisplayedLength(drain);
@ -221,14 +213,15 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep($"check map drain ({displayedLength})", () =>
{
var label = infoWedge.DisplayedContent.ChildrenOfType<BeatmapInfoWedge.WedgeInfoText.InfoLabel>().Single(l => l.Statistic.Name == BeatmapsetsStrings.ShowStatsTotalLength(displayedLength));
var label = infoWedge.DisplayedContent.ChildrenOfType<BeatmapInfoWedge.WedgeInfoText.InfoLabel>()
.Single(l => l.Statistic.Name == BeatmapsetsStrings.ShowStatsTotalLength(displayedLength));
return label.Statistic.Content == displayedLength.ToString();
});
}
private void setRuleset(RulesetInfo rulesetInfo)
{
Container containerBefore = null;
Container? containerBefore = null;
AddStep("set ruleset", () =>
{
@ -242,9 +235,9 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore);
}
private void selectBeatmap([CanBeNull] IBeatmap b)
private void selectBeatmap(IBeatmap? b)
{
Container containerBefore = null;
Container? containerBefore = null;
AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () =>
{
@ -307,11 +300,6 @@ namespace osu.Game.Tests.Visual.SongSelect
public new WedgeInfoText Info => base.Info;
}
private class TestHitObject : ConvertHitObject, IHasPosition
{
public float X => 0;
public float Y => 0;
public Vector2 Position { get; } = Vector2.Zero;
}
private class TestHitObject : ConvertHitObject;
}
}

View File

@ -14,9 +14,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Select;
using osuTK;
namespace osu.Game.Tests.Visual.SongSelectV2
{
@ -209,11 +207,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
public new WedgeInfoText? Info => base.Info;
}
private class TestHitObject : ConvertHitObject, IHasPosition
{
public float X => 0;
public float Y => 0;
public Vector2 Position { get; } = Vector2.Zero;
}
private class TestHitObject : ConvertHitObject;
}
}

View File

@ -38,8 +38,7 @@ namespace osu.Game.Beatmaps.Formats
internal static RulesetStore? RulesetStore;
private Beatmap beatmap = null!;
private ConvertHitObjectParser? parser;
private ConvertHitObjectParser parser = null!;
private LegacySampleBank defaultSampleBank;
private int defaultSampleVolume = 100;
@ -80,6 +79,7 @@ namespace osu.Game.Beatmaps.Formats
{
this.beatmap = beatmap;
this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion;
parser = new ConvertHitObjectParser(getOffsetTime(), FormatVersion);
applyLegacyDefaults(this.beatmap.BeatmapInfo);
@ -162,7 +162,8 @@ namespace osu.Game.Beatmaps.Formats
{
if (hitObject is IHasRepeats hasRepeats)
{
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.StartTime + CONTROL_POINT_LENIENCY + 1) ?? SampleControlPoint.DEFAULT;
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.StartTime + CONTROL_POINT_LENIENCY + 1)
?? SampleControlPoint.DEFAULT;
hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList();
for (int i = 0; i < hasRepeats.NodeSamples.Count; i++)
@ -175,7 +176,8 @@ namespace osu.Game.Beatmaps.Formats
}
else
{
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + CONTROL_POINT_LENIENCY) ?? SampleControlPoint.DEFAULT;
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + CONTROL_POINT_LENIENCY)
?? SampleControlPoint.DEFAULT;
hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList();
}
}
@ -263,29 +265,7 @@ namespace osu.Game.Beatmaps.Formats
break;
case @"Mode":
int rulesetID = Parsing.ParseInt(pair.Value);
beatmap.BeatmapInfo.Ruleset = RulesetStore?.GetRuleset(rulesetID) ?? throw new ArgumentException("Ruleset is not available locally.");
switch (rulesetID)
{
case 0:
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
break;
case 1:
parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
break;
case 2:
parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
break;
case 3:
parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
break;
}
beatmap.BeatmapInfo.Ruleset = RulesetStore?.GetRuleset(Parsing.ParseInt(pair.Value)) ?? throw new ArgumentException("Ruleset is not available locally.");
break;
case @"LetterboxInBreaks":
@ -617,17 +597,10 @@ namespace osu.Game.Beatmaps.Formats
private void handleHitObject(string line)
{
// If the ruleset wasn't specified, assume the osu!standard ruleset.
parser ??= new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
var obj = parser.Parse(line);
obj.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
if (obj != null)
{
obj.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
beatmap.HitObjects.Add(obj);
}
beatmap.HitObjects.Add(obj);
}
private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0);

View File

@ -6,7 +6,7 @@ using System;
namespace osu.Game.Beatmaps.Legacy
{
[Flags]
internal enum LegacyHitObjectType
public enum LegacyHitObjectType
{
Circle = 1,
Slider = 1 << 1,

View File

@ -1,20 +0,0 @@
// 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 osu.Game.Rulesets.Objects.Types;
using osuTK;
namespace osu.Game.Rulesets.Objects.Legacy.Catch
{
/// <summary>
/// Legacy osu!catch Hit-type, used for parsing Beatmaps.
/// </summary>
internal sealed class ConvertHit : ConvertHitObject, IHasPosition
{
public float X => Position.X;
public float Y => Position.Y;
public Vector2 Position { get; set; }
}
}

View File

@ -1,63 +0,0 @@
// 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.
#nullable disable
using osuTK;
using osu.Game.Audio;
using System.Collections.Generic;
namespace osu.Game.Rulesets.Objects.Legacy.Catch
{
/// <summary>
/// A HitObjectParser to parse legacy osu!catch Beatmaps.
/// </summary>
public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
{
private ConvertHitObject lastObject;
public ConvertHitObjectParser(double offset, int formatVersion)
: base(offset, formatVersion)
{
}
protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset)
{
return lastObject = new ConvertHit
{
Position = position,
NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo,
ComboOffset = newCombo ? comboOffset : 0
};
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
IList<IList<HitSampleInfo>> nodeSamples)
{
return lastObject = new ConvertSlider
{
Position = position,
NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo,
ComboOffset = newCombo ? comboOffset : 0,
Path = new SliderPath(controlPoints, length),
NodeSamples = nodeSamples,
RepeatCount = repeatCount
};
}
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
{
return lastObject = new ConvertSpinner
{
Duration = duration,
NewCombo = newCombo
// Spinners cannot have combo offset.
};
}
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
{
return lastObject = null;
}
}
}

View File

@ -1,20 +0,0 @@
// 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 osu.Game.Rulesets.Objects.Types;
using osuTK;
namespace osu.Game.Rulesets.Objects.Legacy.Catch
{
/// <summary>
/// Legacy osu!catch Slider-type, used for parsing Beatmaps.
/// </summary>
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition
{
public float X => Position.X;
public float Y => Position.Y;
public Vector2 Position { get; set; }
}
}

View File

@ -1,19 +0,0 @@
// 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 osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Objects.Legacy.Catch
{
/// <summary>
/// Legacy osu!catch Spinner-type, used for parsing Beatmaps.
/// </summary>
internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition
{
public double EndTime => StartTime + Duration;
public double Duration { get; set; }
public float X => 256; // Required for CatchBeatmapConverter
}
}

View 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.Rulesets.Objects.Legacy
{
/// <summary>
/// Legacy "HitCircle" hit object type.
/// </summary>
/// <remarks>
/// Only used for parsing beatmaps and not gameplay.
/// </remarks>
internal sealed class ConvertHitCircle : ConvertHitObject;
}

View File

@ -1,21 +1,34 @@
// 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 osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Objects.Legacy
{
/// <summary>
/// A hit object only used for conversion, not actual gameplay.
/// Represents a legacy hit object.
/// </summary>
internal abstract class ConvertHitObject : HitObject, IHasCombo
/// <remarks>
/// Only used for parsing beatmaps and not gameplay.
/// </remarks>
internal abstract class ConvertHitObject : HitObject, IHasCombo, IHasPosition, IHasLegacyHitObjectType
{
public bool NewCombo { get; set; }
public int ComboOffset { get; set; }
public float X => Position.X;
public float Y => Position.Y;
public Vector2 Position { get; set; }
public LegacyHitObjectType LegacyType { get; set; }
public override Judgement CreateJudgement() => new IgnoreJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;

View File

@ -1,8 +1,6 @@
// 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.
#nullable disable
using osuTK;
using osu.Game.Rulesets.Objects.Types;
using System;
@ -11,7 +9,6 @@ using System.IO;
using osu.Game.Beatmaps.Formats;
using osu.Game.Audio;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Legacy;
@ -24,24 +21,32 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <summary>
/// A HitObjectParser to parse legacy Beatmaps.
/// </summary>
public abstract class ConvertHitObjectParser : HitObjectParser
public class ConvertHitObjectParser : HitObjectParser
{
/// <summary>
/// The offset to apply to all time values.
/// </summary>
protected readonly double Offset;
private readonly double offset;
/// <summary>
/// The .osu format (beatmap) version.
/// </summary>
protected readonly int FormatVersion;
private readonly int formatVersion;
protected bool FirstObject { get; private set; } = true;
/// <summary>
/// Whether the current hitobject is the first hitobject in the beatmap.
/// </summary>
private bool firstObject = true;
protected ConvertHitObjectParser(double offset, int formatVersion)
/// <summary>
/// The last parsed hitobject.
/// </summary>
private ConvertHitObject? lastObject;
internal ConvertHitObjectParser(double offset, int formatVersion)
{
Offset = offset;
FormatVersion = formatVersion;
this.offset = offset;
this.formatVersion = formatVersion;
}
public override HitObject Parse(string text)
@ -49,11 +54,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
string[] split = text.Split(',');
Vector2 pos =
FormatVersion >= LegacyBeatmapEncoder.FIRST_LAZER_VERSION
formatVersion >= LegacyBeatmapEncoder.FIRST_LAZER_VERSION
? new Vector2(Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE))
: new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE));
double startTime = Parsing.ParseDouble(split[2]) + Offset;
double startTime = Parsing.ParseDouble(split[2]) + offset;
LegacyHitObjectType type = (LegacyHitObjectType)Parsing.ParseInt(split[3]);
@ -66,11 +71,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
var soundType = (LegacyHitSoundType)Parsing.ParseInt(split[4]);
var bankInfo = new SampleBankInfo();
HitObject result = null;
ConvertHitObject? result = null;
if (type.HasFlag(LegacyHitObjectType.Circle))
{
result = CreateHit(pos, combo, comboOffset);
result = createHitCircle(pos, combo, comboOffset);
if (split.Length > 5)
readCustomSampleBanks(split[5], bankInfo);
@ -145,13 +150,13 @@ namespace osu.Game.Rulesets.Objects.Legacy
for (int i = 0; i < nodes; i++)
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
result = CreateSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples);
result = createSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples);
}
else if (type.HasFlag(LegacyHitObjectType.Spinner))
{
double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime);
double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + offset - startTime);
result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration);
result = createSpinner(new Vector2(512, 384) / 2, combo, duration);
if (split.Length > 6)
readCustomSampleBanks(split[6], bankInfo);
@ -169,18 +174,19 @@ namespace osu.Game.Rulesets.Objects.Legacy
readCustomSampleBanks(string.Join(':', ss.Skip(1)), bankInfo);
}
result = CreateHold(pos, combo, comboOffset, endTime + Offset - startTime);
result = createHold(pos, endTime + offset - startTime);
}
if (result == null)
throw new InvalidDataException($"Unknown hit object type: {split[3]}");
result.StartTime = startTime;
result.LegacyType = type;
if (result.Samples.Count == 0)
result.Samples = convertSoundType(soundType, bankInfo);
FirstObject = false;
firstObject = false;
return result;
}
@ -200,10 +206,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (!Enum.IsDefined(addBank))
addBank = LegacySampleBank.Normal;
string stringBank = bank.ToString().ToLowerInvariant();
string? stringBank = bank.ToString().ToLowerInvariant();
string? stringAddBank = addBank.ToString().ToLowerInvariant();
if (stringBank == @"none")
stringBank = null;
string stringAddBank = addBank.ToString().ToLowerInvariant();
if (stringAddBank == @"none")
{
@ -357,7 +364,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
{
int endPointLength = endPoint == null ? 0 : 1;
if (FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION)
if (formatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION)
{
if (vertices.Length + endPointLength != 3)
type = PathType.BEZIER;
@ -393,7 +400,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
// Legacy CATMULL sliders don't support multiple segments, so adjacent CATMULL segments should be treated as a single one.
// Importantly, this is not applied to the first control point, which may duplicate the slider path's position
// resulting in a duplicate (0,0) control point in the resultant list.
if (type == PathType.CATMULL && endIndex > 1 && FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION)
if (type == PathType.CATMULL && endIndex > 1 && formatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION)
continue;
// The last control point of each segment is not allowed to start a new implicit segment.
@ -442,7 +449,15 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <param name="newCombo">Whether the hit object creates a new combo.</param>
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
/// <returns>The hit object.</returns>
protected abstract HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset);
private ConvertHitObject createHitCircle(Vector2 position, bool newCombo, int comboOffset)
{
return lastObject = new ConvertHitCircle
{
Position = position,
NewCombo = firstObject || lastObject is ConvertSpinner || newCombo,
ComboOffset = newCombo ? comboOffset : 0
};
}
/// <summary>
/// Creats a legacy Slider-type hit object.
@ -455,27 +470,51 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <param name="repeatCount">The slider repeat count.</param>
/// <param name="nodeSamples">The samples to be played when the slider nodes are hit. This includes the head and tail of the slider.</param>
/// <returns>The hit object.</returns>
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
IList<IList<HitSampleInfo>> nodeSamples);
private ConvertHitObject createSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
IList<IList<HitSampleInfo>> nodeSamples)
{
return lastObject = new ConvertSlider
{
Position = position,
NewCombo = firstObject || lastObject is ConvertSpinner || newCombo,
ComboOffset = newCombo ? comboOffset : 0,
Path = new SliderPath(controlPoints, length),
NodeSamples = nodeSamples,
RepeatCount = repeatCount
};
}
/// <summary>
/// Creates a legacy Spinner-type hit object.
/// </summary>
/// <param name="position">The position of the hit object.</param>
/// <param name="newCombo">Whether the hit object creates a new combo.</param>
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
/// <param name="duration">The spinner duration.</param>
/// <returns>The hit object.</returns>
protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration);
private ConvertHitObject createSpinner(Vector2 position, bool newCombo, double duration)
{
return lastObject = new ConvertSpinner
{
Position = position,
Duration = duration,
NewCombo = newCombo
// Spinners cannot have combo offset.
};
}
/// <summary>
/// Creates a legacy Hold-type hit object.
/// </summary>
/// <param name="position">The position of the hit object.</param>
/// <param name="newCombo">Whether the hit object creates a new combo.</param>
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
/// <param name="duration">The hold duration.</param>
protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration);
private ConvertHitObject createHold(Vector2 position, double duration)
{
return lastObject = new ConvertHold
{
Position = position,
Duration = duration
};
}
private List<HitSampleInfo> convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo)
{
@ -511,21 +550,19 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <summary>
/// An optional overriding filename which causes all bank/sample specifications to be ignored.
/// </summary>
public string Filename;
public string? Filename;
/// <summary>
/// The bank identifier to use for the base ("hitnormal") sample.
/// Transferred to <see cref="HitSampleInfo.Bank"/> when appropriate.
/// </summary>
[CanBeNull]
public string BankForNormal;
public string? BankForNormal;
/// <summary>
/// The bank identifier to use for additions ("hitwhistle", "hitfinish", "hitclap").
/// Transferred to <see cref="HitSampleInfo.Bank"/> when appropriate.
/// </summary>
[CanBeNull]
public string BankForAdditions;
public string? BankForAdditions;
/// <summary>
/// Hit sample volume (0-100).
@ -548,8 +585,6 @@ namespace osu.Game.Rulesets.Objects.Legacy
public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
}
#nullable enable
public class LegacyHitSampleInfo : HitSampleInfo, IEquatable<LegacyHitSampleInfo>
{
public readonly int CustomSampleBank;
@ -577,13 +612,14 @@ namespace osu.Game.Rulesets.Objects.Legacy
IsLayered = isLayered;
}
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default)
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default,
Optional<bool> newEditorAutoBank = default)
=> With(newName, newBank, newVolume, newEditorAutoBank);
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default,
Optional<int> newCustomSampleBank = default,
Optional<bool> newIsLayered = default)
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered));
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default,
Optional<bool> newEditorAutoBank = default, Optional<int> newCustomSampleBank = default, Optional<bool> newIsLayered = default)
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank), newCustomSampleBank.GetOr(CustomSampleBank),
newIsLayered.GetOr(IsLayered));
public bool Equals(LegacyHitSampleInfo? other)
// The additions to equality checks here are *required* to ensure that pooling works correctly.
@ -615,9 +651,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
Path.ChangeExtension(Filename, null)
};
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default,
Optional<int> newCustomSampleBank = default,
Optional<bool> newIsLayered = default)
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default,
Optional<bool> newEditorAutoBank = default, Optional<int> newCustomSampleBank = default, Optional<bool> newIsLayered = default)
=> new FileHitSampleInfo(Filename, newVolume.GetOr(Volume));
public bool Equals(FileHitSampleInfo? other)

View File

@ -3,17 +3,18 @@
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Objects.Legacy.Mania
namespace osu.Game.Rulesets.Objects.Legacy
{
/// <summary>
/// Legacy osu!mania Spinner-type, used for parsing Beatmaps.
/// Legacy "Hold" hit object type. Generally only valid in the mania ruleset.
/// </summary>
internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition
/// <remarks>
/// Only used for parsing beatmaps and not gameplay.
/// </remarks>
internal sealed class ConvertHold : ConvertHitObject, IHasDuration
{
public double Duration { get; set; }
public double EndTime => StartTime + Duration;
public float X { get; set; }
}
}

View File

@ -1,8 +1,6 @@
// 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.
#nullable disable
using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic;
using Newtonsoft.Json;
@ -13,7 +11,13 @@ using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Objects.Legacy
{
internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasSliderVelocity
/// <summary>
/// Legacy "Slider" hit object type.
/// </summary>
/// <remarks>
/// Only used for parsing beatmaps and not gameplay.
/// </remarks>
internal class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasSliderVelocity, IHasGenerateTicks
{
/// <summary>
/// Scoring distance with a speed-adjusted beat length of 1 second.
@ -50,6 +54,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
set => SliderVelocityMultiplierBindable.Value = value;
}
public bool GenerateTicks { get; set; } = true;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);

View File

@ -3,11 +3,14 @@
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Objects.Legacy.Taiko
namespace osu.Game.Rulesets.Objects.Legacy
{
/// <summary>
/// Legacy osu!taiko Spinner-type, used for parsing Beatmaps.
/// Legacy "Spinner" hit object type.
/// </summary>
/// <remarks>
/// Only used for parsing beatmaps and not gameplay.
/// </remarks>
internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration
{
public double Duration { get; set; }

View File

@ -0,0 +1,18 @@
// 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 osu.Game.Beatmaps.Legacy;
namespace osu.Game.Rulesets.Objects.Legacy
{
/// <summary>
/// A hit object from a legacy beatmap representation.
/// </summary>
public interface IHasLegacyHitObjectType
{
/// <summary>
/// The hit object type.
/// </summary>
LegacyHitObjectType LegacyType { get; }
}
}

View File

@ -1,15 +0,0 @@
// 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 osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Objects.Legacy.Mania
{
/// <summary>
/// Legacy osu!mania Hit-type, used for parsing Beatmaps.
/// </summary>
internal sealed class ConvertHit : ConvertHitObject, IHasXPosition
{
public float X { get; set; }
}
}

View File

@ -1,58 +0,0 @@
// 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 osuTK;
using osu.Game.Audio;
using System.Collections.Generic;
namespace osu.Game.Rulesets.Objects.Legacy.Mania
{
/// <summary>
/// A HitObjectParser to parse legacy osu!mania Beatmaps.
/// </summary>
public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
{
public ConvertHitObjectParser(double offset, int formatVersion)
: base(offset, formatVersion)
{
}
protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset)
{
return new ConvertHit
{
X = position.X
};
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
IList<IList<HitSampleInfo>> nodeSamples)
{
return new ConvertSlider
{
X = position.X,
Path = new SliderPath(controlPoints, length),
NodeSamples = nodeSamples,
RepeatCount = repeatCount
};
}
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
{
return new ConvertSpinner
{
X = position.X,
Duration = duration
};
}
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
{
return new ConvertHold
{
X = position.X,
Duration = duration
};
}
}
}

View File

@ -1,16 +0,0 @@
// 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 osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Objects.Legacy.Mania
{
internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasDuration
{
public float X { get; set; }
public double Duration { get; set; }
public double EndTime => StartTime + Duration;
}
}

View File

@ -1,15 +0,0 @@
// 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 osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Objects.Legacy.Mania
{
/// <summary>
/// Legacy osu!mania Slider-type, used for parsing Beatmaps.
/// </summary>
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition
{
public float X { get; set; }
}
}

View File

@ -1,20 +0,0 @@
// 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 osu.Game.Rulesets.Objects.Types;
using osuTK;
namespace osu.Game.Rulesets.Objects.Legacy.Osu
{
/// <summary>
/// Legacy osu! Hit-type, used for parsing Beatmaps.
/// </summary>
internal sealed class ConvertHit : ConvertHitObject, IHasPosition
{
public Vector2 Position { get; set; }
public float X => Position.X;
public float Y => Position.Y;
}
}

View File

@ -1,64 +0,0 @@
// 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.
#nullable disable
using osuTK;
using System.Collections.Generic;
using osu.Game.Audio;
namespace osu.Game.Rulesets.Objects.Legacy.Osu
{
/// <summary>
/// A HitObjectParser to parse legacy osu! Beatmaps.
/// </summary>
public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
{
private ConvertHitObject lastObject;
public ConvertHitObjectParser(double offset, int formatVersion)
: base(offset, formatVersion)
{
}
protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset)
{
return lastObject = new ConvertHit
{
Position = position,
NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo,
ComboOffset = newCombo ? comboOffset : 0
};
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
IList<IList<HitSampleInfo>> nodeSamples)
{
return lastObject = new ConvertSlider
{
Position = position,
NewCombo = FirstObject || lastObject is ConvertSpinner || newCombo,
ComboOffset = newCombo ? comboOffset : 0,
Path = new SliderPath(controlPoints, length),
NodeSamples = nodeSamples,
RepeatCount = repeatCount
};
}
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
{
return lastObject = new ConvertSpinner
{
Position = position,
Duration = duration,
NewCombo = newCombo
// Spinners cannot have combo offset.
};
}
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
{
return lastObject = null;
}
}
}

View File

@ -1,22 +0,0 @@
// 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 osu.Game.Rulesets.Objects.Types;
using osuTK;
namespace osu.Game.Rulesets.Objects.Legacy.Osu
{
/// <summary>
/// Legacy osu! Slider-type, used for parsing Beatmaps.
/// </summary>
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasGenerateTicks
{
public Vector2 Position { get; set; }
public float X => Position.X;
public float Y => Position.Y;
public bool GenerateTicks { get; set; } = true;
}
}

View File

@ -1,24 +0,0 @@
// 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 osu.Game.Rulesets.Objects.Types;
using osuTK;
namespace osu.Game.Rulesets.Objects.Legacy.Osu
{
/// <summary>
/// Legacy osu! Spinner-type, used for parsing Beatmaps.
/// </summary>
internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasPosition
{
public double Duration { get; set; }
public double EndTime => StartTime + Duration;
public Vector2 Position { get; set; }
public float X => Position.X;
public float Y => Position.Y;
}
}

View File

@ -1,12 +0,0 @@
// 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.Rulesets.Objects.Legacy.Taiko
{
/// <summary>
/// Legacy osu!taiko Hit-type, used for parsing Beatmaps.
/// </summary>
internal sealed class ConvertHit : ConvertHitObject
{
}
}

View File

@ -1,51 +0,0 @@
// 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.
#nullable disable
using osuTK;
using System.Collections.Generic;
using osu.Game.Audio;
namespace osu.Game.Rulesets.Objects.Legacy.Taiko
{
/// <summary>
/// A HitObjectParser to parse legacy osu!taiko Beatmaps.
/// </summary>
public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
{
public ConvertHitObjectParser(double offset, int formatVersion)
: base(offset, formatVersion)
{
}
protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset)
{
return new ConvertHit();
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
IList<IList<HitSampleInfo>> nodeSamples)
{
return new ConvertSlider
{
Path = new SliderPath(controlPoints, length),
NodeSamples = nodeSamples,
RepeatCount = repeatCount
};
}
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
{
return new ConvertSpinner
{
Duration = duration
};
}
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
{
return null;
}
}
}

View File

@ -1,12 +0,0 @@
// 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.Rulesets.Objects.Legacy.Taiko
{
/// <summary>
/// Legacy osu!taiko Slider-type, used for parsing Beatmaps.
/// </summary>
internal sealed class ConvertSlider : Legacy.ConvertSlider
{
}
}