Rework/rewrite beatmap parsing to parse to base hit objects, which mode-specific beatmap converters can then use.

This commit is contained in:
smoogipooo
2017-03-13 19:15:25 +09:00
parent c76a495d3d
commit f50e0bbf3c
58 changed files with 470 additions and 322 deletions

View File

@ -11,6 +11,7 @@ using osu.Framework.Audio.Sample;
using osu.Game.Beatmaps.Samples;
using OpenTK;
using Container = osu.Framework.Graphics.Containers.Container;
using osu.Game.Modes.Objects.Types;
namespace osu.Game.Modes.Objects.Drawables
{
@ -67,14 +68,14 @@ namespace osu.Game.Modes.Objects.Drawables
}
}
public abstract class DrawableHitObject<HitObjectType> : DrawableHitObject
where HitObjectType : HitObject
public abstract class DrawableHitObject<TObject> : DrawableHitObject
where TObject : HitObject
{
public event Action<DrawableHitObject<HitObjectType>, JudgementInfo> OnJudgement;
public event Action<DrawableHitObject<TObject>, JudgementInfo> OnJudgement;
public HitObjectType HitObject;
public TObject HitObject;
protected DrawableHitObject(HitObjectType hitObject)
protected DrawableHitObject(TObject hitObject)
{
HitObject = hitObject;
}
@ -88,7 +89,9 @@ namespace osu.Game.Modes.Objects.Drawables
if (Judgement.Result != null)
return false;
Judgement.TimeOffset = Time.Current - HitObject.EndTime;
double endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
Judgement.TimeOffset = Time.Current - endTime;
CheckJudgement(userTriggered);
@ -138,14 +141,14 @@ namespace osu.Game.Modes.Objects.Drawables
Sample = audio.Sample.Get($@"Gameplay/{sampleSet.ToString().ToLower()}-hit{type.ToString().ToLower()}");
}
private List<DrawableHitObject<HitObjectType>> nestedHitObjects;
private List<DrawableHitObject<TObject>> nestedHitObjects;
protected IEnumerable<DrawableHitObject<HitObjectType>> NestedHitObjects => nestedHitObjects;
protected IEnumerable<DrawableHitObject<TObject>> NestedHitObjects => nestedHitObjects;
protected void AddNested(DrawableHitObject<HitObjectType> h)
protected void AddNested(DrawableHitObject<TObject> h)
{
if (nestedHitObjects == null)
nestedHitObjects = new List<DrawableHitObject<HitObjectType>>();
nestedHitObjects = new List<DrawableHitObject<TObject>>();
h.OnJudgement += (d, j) => { OnJudgement?.Invoke(d, j); } ;
nestedHitObjects.Add(h);

View File

@ -0,0 +1,15 @@
using osu.Game.Modes.Objects.Types;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Modes.Objects
{
internal class Hit : HitObject, IHasPosition, IHasCombo
{
public Vector2 Position { get; set; }
public Color4 ComboColour { get; set; }
public bool NewCombo { get; set; }
public int ComboIndex { get; set; }
}
}

View File

@ -3,28 +3,31 @@
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Samples;
using OpenTK.Graphics;
namespace osu.Game.Modes.Objects
{
/// <summary>
/// A hitobject describes a point in a beatmap
/// A HitObject describes an object in a Beatmap.
/// <para>
/// HitObjects may contain more properties for which you should be checking through the IHas* types.
/// </para>
/// </summary>
public abstract class HitObject
public class HitObject
{
public double StartTime;
public virtual double EndTime => StartTime;
/// <summary>
/// The time at which the HitObject starts.
/// </summary>
public double StartTime { get; set; }
public bool NewCombo { get; set; }
public Color4 Colour = new Color4(17, 136, 170, 255);
public double Duration => EndTime - StartTime;
public HitSampleInfo Sample;
public int ComboIndex;
/// <summary>
/// The sample to be played when this HitObject is hit.
/// </summary>
public HitSampleInfo Sample { get; set; }
/// <summary>
/// Sets default parameters from a beatmap.
/// </summary>
/// <param name="beatmap">The beatmap to set from.</param>
public virtual void SetDefaultsFromBeatmap(Beatmap beatmap) { }
}
}

View File

@ -1,25 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using System;
using System.Collections.Generic;
namespace osu.Game.Modes.Objects
{
public abstract class HitObjectConverter<T>
where T : HitObject
{
public abstract List<T> Convert(Beatmap beatmap);
}
public class HitObjectConvertException : Exception
{
public HitObject Input { get; }
public HitObjectConvertException(string modeName, HitObject input)
: base($@"Can't convert from {input.GetType().Name} to {modeName} HitObject!")
{
Input = input;
}
}
}

View File

@ -0,0 +1,17 @@
namespace osu.Game.Modes.Objects
{
/// <summary>
/// Converts HitObjects to another mode.
/// </summary>
/// <typeparam name="T">The type of HitObject to be converted to.</typeparam>
public interface IHitObjectConverter<T>
where T : HitObject
{
/// <summary>
/// Converts a <see cref="HitObject"/> to another mode.
/// </summary>
/// <param name="hitObject">The base HitObject to convert.</param>
/// <returns>The converted HitObject.</returns>
T Convert(HitObject hitObject);
}
}

View File

@ -0,0 +1,105 @@
using OpenTK;
using osu.Game.Beatmaps.Samples;
using osu.Game.Modes.Objects.Types;
using System;
using System.Collections.Generic;
using System.Globalization;
namespace osu.Game.Modes.Objects
{
internal class LegacyHitObjectParser : HitObjectParser
{
public override HitObject Parse(string text)
{
string[] split = text.Split(',');
var type = (HitObjectType)int.Parse(split[3]);
bool combo = type.HasFlag(HitObjectType.NewCombo);
type &= (HitObjectType)0xF;
type &= ~HitObjectType.NewCombo;
HitObject result;
switch (type)
{
case HitObjectType.Circle:
result = new Hit
{
Position = new Vector2(int.Parse(split[0]), int.Parse(split[1])),
NewCombo = combo
};
break;
case HitObjectType.Slider:
CurveType curveType = CurveType.Catmull;
double length = 0;
List<Vector2> points = new List<Vector2> { new Vector2(int.Parse(split[0]), int.Parse(split[1])) };
string[] pointsplit = split[5].Split('|');
foreach (string t in pointsplit)
{
if (t.Length == 1)
{
switch (t)
{
case @"C":
curveType = CurveType.Catmull;
break;
case @"B":
curveType = CurveType.Bezier;
break;
case @"L":
curveType = CurveType.Linear;
break;
case @"P":
curveType = CurveType.PerfectCurve;
break;
}
continue;
}
string[] temp = t.Split(':');
Vector2 v = new Vector2(
(int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture),
(int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture)
);
points.Add(v);
}
int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture);
if (repeatCount > 9000)
throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high");
if (split.Length > 7)
length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture);
result = new Slider
{
ControlPoints = points,
Distance = length,
CurveType = curveType,
RepeatCount = repeatCount,
Position = new Vector2(int.Parse(split[0]), int.Parse(split[1])),
NewCombo = combo
};
break;
case HitObjectType.Spinner:
result = new Spinner
{
EndTime = Convert.ToDouble(split[5], CultureInfo.InvariantCulture)
};
break;
default:
throw new InvalidOperationException($@"Unknown hit object type {type}");
}
result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture);
result.Sample = new HitSampleInfo
{
Type = (SampleType)int.Parse(split[4]),
Set = SampleSet.Soft,
};
// TODO: "addition" field
return result;
}
}
}

View File

@ -1,13 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Modes.Objects
{
/// <summary>
/// Returns null HitObjects but at least allows us to run.
/// </summary>
public class NullHitObjectParser : HitObjectParser
{
public override HitObject Parse(string text) => null;
}
}

View File

@ -0,0 +1,23 @@
using osu.Game.Modes.Objects.Types;
using System.Collections.Generic;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Modes.Objects
{
internal class Slider : HitObject, IHasCurve, IHasDistance, IHasPosition, IHasCombo, IHasRepeats
{
public List<Vector2> ControlPoints { get; set; }
public CurveType CurveType { get; set; }
public double Distance { get; set; }
public Vector2 Position { get; set; }
public Color4 ComboColour { get; set; }
public bool NewCombo { get; set; }
public int ComboIndex { get; set; }
public int RepeatCount { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using osu.Game.Modes.Objects.Types;
namespace osu.Game.Modes.Objects
{
internal class Spinner : HitObject, IHasEndTime
{
public double EndTime { get; set; }
public double Duration => EndTime - StartTime;
}
}

View File

@ -0,0 +1,10 @@
namespace osu.Game.Modes.Objects.Types
{
public enum CurveType
{
Catmull,
Bezier,
Linear,
PerfectCurve
}
}

View File

@ -0,0 +1,25 @@
using OpenTK.Graphics;
namespace osu.Game.Modes.Objects.Types
{
/// <summary>
/// A HitObject that is part of a combo.
/// </summary>
public interface IHasCombo
{
/// <summary>
/// The colour of this HitObject in the combo.
/// </summary>
Color4 ComboColour { get; set; }
/// <summary>
/// Whether the HitObject starts a new combo.
/// </summary>
bool NewCombo { get; }
/// <summary>
/// The combo index.
/// </summary>
int ComboIndex { get; }
}
}

View File

@ -0,0 +1,21 @@
using System.Collections.Generic;
using OpenTK;
namespace osu.Game.Modes.Objects.Types
{
/// <summary>
/// A HitObject that has a curve.
/// </summary>
public interface IHasCurve
{
/// <summary>
/// The control points that shape the curve.
/// </summary>
List<Vector2> ControlPoints { get; }
/// <summary>
/// The type of curve.
/// </summary>
CurveType CurveType { get; }
}
}

View File

@ -0,0 +1,13 @@
namespace osu.Game.Modes.Objects.Types
{
/// <summary>
/// A HitObject that has a distance.
/// </summary>
public interface IHasDistance
{
/// <summary>
/// The distance of the HitObject.
/// </summary>
double Distance { get; }
}
}

View File

@ -0,0 +1,18 @@
namespace osu.Game.Modes.Objects.Types
{
/// <summary>
/// A HitObject that ends at a different time than its start time.
/// </summary>
public interface IHasEndTime
{
/// <summary>
/// The time at which the HitObject ends.
/// </summary>
double EndTime { get; }
/// <summary>
/// The duration of the HitObject.
/// </summary>
double Duration { get; }
}
}

View File

@ -0,0 +1,15 @@
using OpenTK;
namespace osu.Game.Modes.Objects.Types
{
/// <summary>
/// A HitObject that has a starting position.
/// </summary>
public interface IHasPosition
{
/// <summary>
/// The starting position of the HitObject.
/// </summary>
Vector2 Position { get; }
}
}

View File

@ -0,0 +1,13 @@
namespace osu.Game.Modes.Objects.Types
{
/// <summary>
/// A HitObject that spans some length.
/// </summary>
public interface IHasRepeats
{
/// <summary>
/// The amount of times the HitObject repeats.
/// </summary>
int RepeatCount { get; }
}
}

View File

@ -0,0 +1,16 @@
using System;
namespace osu.Game.Modes.Objects.Types
{
[Flags]
public enum HitObjectType
{
Circle = 1 << 0,
Slider = 1 << 1,
NewCombo = 1 << 2,
Spinner = 1 << 3,
ColourHax = 122,
Hold = 1 << 7,
SliderTick = 1 << 8,
}
}