1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-16 01:33:21 +08:00

Merge pull request #788 from smoogipooo/mania-beatmap-conversion

Mania beatmap conversion
This commit is contained in:
Dan Balasescu 2017-05-19 21:18:21 +09:00 committed by GitHub
commit 9e9df4ada5
42 changed files with 693 additions and 146 deletions

View File

@ -85,25 +85,25 @@ namespace osu.Desktop.VisualTests.Tests
Clock = new FramedClock(),
Children = new Drawable[]
{
new OsuHitRenderer(beatmap)
new OsuHitRenderer(beatmap, false)
{
Scale = new Vector2(0.5f),
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft
},
new TaikoHitRenderer(beatmap)
new TaikoHitRenderer(beatmap, false)
{
Scale = new Vector2(0.5f),
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight
},
new CatchHitRenderer(beatmap)
new CatchHitRenderer(beatmap, false)
{
Scale = new Vector2(0.5f),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft
},
new ManiaHitRenderer(beatmap)
new ManiaHitRenderer(beatmap, false)
{
Scale = new Vector2(0.5f),
Anchor = Anchor.BottomRight,

View File

@ -43,13 +43,8 @@ namespace osu.Desktop.VisualTests.Tests
RelativeCoordinateSpace = new Vector2(1, 10000),
Children = new[]
{
new DrawableNote(new Note
{
StartTime = 5000
})
{
AccentColour = Color4.Red
}
new DrawableNote(new Note { StartTime = 5000 }) { AccentColour = Color4.Red },
new DrawableNote(new Note { StartTime = 6000 }) { AccentColour = Color4.Red }
}
}
}
@ -74,10 +69,7 @@ namespace osu.Desktop.VisualTests.Tests
{
StartTime = 5000,
Duration = 1000
})
{
AccentColour = Color4.Red
}
}) { AccentColour = Color4.Red }
}
}
}

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch
{
public class CatchRuleset : Ruleset
{
public override HitRenderer CreateHitRendererWith(WorkingBeatmap beatmap) => new CatchHitRenderer(beatmap);
public override HitRenderer CreateHitRendererWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new CatchHitRenderer(beatmap, isForCurrentRuleset);
public override IEnumerable<Mod> GetModsFor(ModType type)
{

View File

@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class CatchHitRenderer : HitRenderer<CatchBaseHit, CatchJudgement>
{
public CatchHitRenderer(WorkingBeatmap beatmap)
: base(beatmap)
public CatchHitRenderer(WorkingBeatmap beatmap, bool isForCurrentRuleset)
: base(beatmap, isForCurrentRuleset)
{
}

View File

@ -1,35 +1,153 @@
// 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 osu.Game.Rulesets.Mania.Objects;
using System.Collections.Generic;
using System;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using System;
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using OpenTK;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Database;
namespace osu.Game.Rulesets.Mania.Beatmaps
{
internal class ManiaBeatmapConverter : BeatmapConverter<ManiaHitObject>
public class ManiaBeatmapConverter : BeatmapConverter<ManiaHitObject>
{
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
private Pattern lastPattern = new Pattern();
private FastRandom random;
private Beatmap beatmap;
private bool isForCurrentRuleset;
protected override Beatmap<ManiaHitObject> ConvertBeatmap(Beatmap original, bool isForCurrentRuleset)
{
this.isForCurrentRuleset = isForCurrentRuleset;
beatmap = original;
BeatmapDifficulty difficulty = original.BeatmapInfo.Difficulty;
int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
random = new FastRandom(seed);
return base.ConvertBeatmap(original, isForCurrentRuleset);
}
protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, Beatmap beatmap)
{
int availableColumns = (int)Math.Round(beatmap.BeatmapInfo.Difficulty.CircleSize);
var positionData = original as IHasXPosition;
float localWDivisor = 512.0f / availableColumns;
int column = MathHelper.Clamp((int)Math.Floor((positionData?.X ?? 1) / localWDivisor), 0, availableColumns - 1);
yield return new Note
var maniaOriginal = original as ManiaHitObject;
if (maniaOriginal != null)
{
StartTime = original.StartTime,
Column = column,
};
yield return maniaOriginal;
yield break;
}
var objects = isForCurrentRuleset ? generateSpecific(original) : generateConverted(original);
if (objects == null)
yield break;
foreach (ManiaHitObject obj in objects)
yield return obj;
}
/// <summary>
/// Method that generates hit objects for osu!mania specific beatmaps.
/// </summary>
/// <param name="original">The original hit object.</param>
/// <returns>The hit objects generated.</returns>
private IEnumerable<ManiaHitObject> generateSpecific(HitObject original)
{
var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern);
Pattern newPattern = generator.Generate();
lastPattern = newPattern;
return newPattern.HitObjects;
}
/// <summary>
/// Method that generates hit objects for non-osu!mania beatmaps.
/// </summary>
/// <param name="original">The original hit object.</param>
/// <returns>The hit objects generated.</returns>
private IEnumerable<ManiaHitObject> generateConverted(HitObject original)
{
var endTimeData = original as IHasEndTime;
var distanceData = original as IHasDistance;
var positionData = original as IHasPosition;
// Following lines currently commented out to appease resharper
//Patterns.PatternGenerator conversion = null;
if (distanceData != null)
{
// Slider
}
else if (endTimeData != null)
{
// Spinner
}
else if (positionData != null)
{
// Circle
}
//if (conversion == null)
return null;
//Pattern newPattern = conversion.Generate();
//lastPattern = newPattern;
//return newPattern.HitObjects;
}
/// <summary>
/// A pattern generator for osu!mania-specific beatmaps.
/// </summary>
private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator
{
public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern)
: base(random, hitObject, beatmap, previousPattern)
{
}
public override Pattern Generate()
{
var endTimeData = HitObject as IHasEndTime;
var positionData = HitObject as IHasXPosition;
int column = GetColumn(positionData?.X ?? 0);
var pattern = new Pattern();
if (endTimeData != null)
{
pattern.Add(new HoldNote
{
StartTime = HitObject.StartTime,
Samples = HitObject.Samples,
Duration = endTimeData.Duration,
Column = column,
});
}
else if (positionData != null)
{
pattern.Add(new Note
{
StartTime = HitObject.StartTime,
Samples = HitObject.Samples,
Column = column
});
}
return pattern;
}
}
}
}

View File

@ -0,0 +1,106 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects;
using OpenTK;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
/// <summary>
/// A pattern generator for legacy hit objects.
/// </summary>
internal abstract class PatternGenerator : Patterns.PatternGenerator
{
/// <summary>
/// The column index at which to start generating random notes.
/// </summary>
protected readonly int RandomStart;
/// <summary>
/// The random number generator to use.
/// </summary>
protected readonly FastRandom Random;
protected PatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern)
: base(hitObject, beatmap, previousPattern)
{
Random = random;
RandomStart = AvailableColumns == 8 ? 1 : 0;
}
/// <summary>
/// Converts an x-position into a column.
/// </summary>
/// <param name="position">The x-position.</param>
/// <param name="allowSpecial">Whether to treat as 7K + 1.</param>
/// <returns>The column.</returns>
protected int GetColumn(float position, bool allowSpecial = false)
{
if (allowSpecial && AvailableColumns == 8)
{
const float local_x_divisor = 512f / 7;
return MathHelper.Clamp((int)Math.Floor(position / local_x_divisor), 0, 6) + 1;
}
float localXDivisor = 512f / AvailableColumns;
return MathHelper.Clamp((int)Math.Floor(position / localXDivisor), 0, AvailableColumns - 1);
}
/// <summary>
/// Generates a count of notes to be generated from probabilities.
/// </summary>
/// <param name="p2">Probability for 2 notes to be generated.</param>
/// <param name="p3">Probability for 3 notes to be generated.</param>
/// <param name="p4">Probability for 4 notes to be generated.</param>
/// <param name="p5">Probability for 5 notes to be generated.</param>
/// <param name="p6">Probability for 6 notes to be generated.</param>
/// <returns>The amount of notes to be generated.</returns>
protected int GetRandomNoteCount(double p2, double p3, double p4 = 0, double p5 = 0, double p6 = 0)
{
double val = Random.NextDouble();
if (val >= 1 - p6)
return 6;
if (val >= 1 - p5)
return 5;
if (val >= 1 - p4)
return 4;
if (val >= 1 - p3)
return 3;
return val >= 1 - p2 ? 2 : 1;
}
private double? conversionDifficulty;
/// <summary>
/// A difficulty factor used for various conversion methods from osu!stable.
/// </summary>
protected double ConversionDifficulty
{
get
{
if (conversionDifficulty != null)
return conversionDifficulty.Value;
HitObject lastObject = Beatmap.HitObjects.LastOrDefault();
HitObject firstObject = Beatmap.HitObjects.FirstOrDefault();
double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0);
drainTime -= Beatmap.EventInfo.TotalBreakTime;
if (drainTime == 0)
drainTime = 10000;
BeatmapDifficulty difficulty = Beatmap.BeatmapInfo.Difficulty;
conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + Beatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
return conversionDifficulty.Value;
}
}
}
}

View File

@ -0,0 +1,65 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
/// <summary>
/// The type of pattern to generate. Used for legacy patterns.
/// </summary>
[Flags]
internal enum PatternType
{
None = 0,
/// <summary>
/// Keep the same as last row.
/// </summary>
ForceStack = 1,
/// <summary>
/// Keep different from last row.
/// </summary>
ForceNotStack = 2,
/// <summary>
/// Keep as single note at its original position.
/// </summary>
KeepSingle = 4,
/// <summary>
/// Use a lower random value.
/// </summary>
LowProbability = 8,
/// <summary>
/// Reserved.
/// </summary>
Alternate = 16,
/// <summary>
/// Ignore the repeat count.
/// </summary>
ForceSigSlider = 32,
/// <summary>
/// Convert slider to circle.
/// </summary>
ForceNotSlider = 64,
/// <summary>
/// Notes gathered together.
/// </summary>
Gathered = 128,
Mirror = 256,
/// <summary>
/// Change 0 -> 6.
/// </summary>
Reverse = 512,
/// <summary>
/// 1 -> 5 -> 1 -> 5 like reverse.
/// </summary>
Cycle = 1024,
/// <summary>
/// Next note will be at column + 1.
/// </summary>
Stair = 2048,
/// <summary>
/// Next note will be at column - 1.
/// </summary>
ReverseStair = 4096
}
}

View File

@ -0,0 +1,61 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Mania.Objects;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
{
/// <summary>
/// Creates a pattern containing hit objects.
/// </summary>
internal class Pattern
{
private readonly List<ManiaHitObject> hitObjects = new List<ManiaHitObject>();
/// <summary>
/// All the hit objects contained in this pattern.
/// </summary>
public IEnumerable<ManiaHitObject> HitObjects => hitObjects;
/// <summary>
/// Whether this pattern already contains a hit object in a code.
/// </summary>
/// <param name="column">The column index.</param>
/// <returns>Whether this pattern already contains a hit object in <paramref name="column"/></returns>
public bool IsFilled(int column) => hitObjects.Exists(h => h.Column == column);
/// <summary>
/// Amount of columns taken up by hit objects in this pattern.
/// </summary>
public int ColumnsFilled => HitObjects.GroupBy(h => h.Column).Count();
/// <summary>
/// Adds a hit object to this pattern.
/// </summary>
/// <param name="hitObject">The hit object to add.</param>
public void Add(ManiaHitObject hitObject) => hitObjects.Add(hitObject);
/// <summary>
/// Copies hit object from another pattern to this one.
/// </summary>
/// <param name="other">The other pattern.</param>
public void Add(Pattern other)
{
other.HitObjects.ForEach(Add);
}
/// <summary>
/// Clears this pattern, removing all hit objects.
/// </summary>
public void Clear() => hitObjects.Clear();
/// <summary>
/// Removes a hit object from this pattern.
/// </summary>
/// <param name="hitObject">The hit object to remove.</param>
public bool Remove(ManiaHitObject hitObject) => hitObjects.Remove(hitObject);
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
{
/// <summary>
/// Generator to create a pattern <see cref="Pattern"/> from a hit object.
/// </summary>
internal abstract class PatternGenerator
{
/// <summary>
/// The number of columns available to create the pattern.
/// </summary>
protected readonly int AvailableColumns;
/// <summary>
/// The last pattern.
/// </summary>
protected readonly Pattern PreviousPattern;
/// <summary>
/// The hit object to create the pattern for.
/// </summary>
protected readonly HitObject HitObject;
/// <summary>
/// The beatmap which <see cref="HitObject"/> is a part of.
/// </summary>
protected readonly Beatmap Beatmap;
protected PatternGenerator(HitObject hitObject, Beatmap beatmap, Pattern previousPattern)
{
PreviousPattern = previousPattern;
HitObject = hitObject;
Beatmap = beatmap;
AvailableColumns = (int)Math.Round(beatmap.BeatmapInfo.Difficulty.CircleSize);
}
/// <summary>
/// Generates the pattern for <see cref="HitObject"/>, filled with hit objects.
/// </summary>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
public abstract Pattern Generate();
}
}

View File

@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania
{
public class ManiaRuleset : Ruleset
{
public override HitRenderer CreateHitRendererWith(WorkingBeatmap beatmap) => new ManiaHitRenderer(beatmap);
public override HitRenderer CreateHitRendererWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new ManiaHitRenderer(beatmap, isForCurrentRuleset);
public override IEnumerable<Mod> GetModsFor(ModType type)
{

View File

@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils
/// </summary>
internal class FastRandom
{
private const double uint_to_real = 1.0 / (int.MaxValue + 1.0);
private const double uint_to_real = 1.0 / (uint.MaxValue + 1.0);
private const uint int_mask = 0x7FFFFFFF;
private const uint y = 842502087;
private const uint z = 3579807591;

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
tailPiece = new NotePiece
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre
Origin = Anchor.TopCentre
}
});
}
@ -61,5 +61,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void UpdateState(ArmedState state)
{
}
protected override void Update()
{
if (Time.Current > HitObject.StartTime)
headPiece.Colour = Color4.Green;
if (Time.Current > HitObject.EndTime)
{
bodyPiece.Colour = Color4.Green;
tailPiece.Colour = Color4.Green;
}
}
}
}

View File

@ -3,8 +3,6 @@
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
@ -15,8 +13,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
public new TObject HitObject;
private readonly Container glowContainer;
protected DrawableManiaHitObject(TObject hitObject)
: base(hitObject)
{
@ -24,21 +20,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
RelativePositionAxes = Axes.Y;
Y = (float)HitObject.StartTime;
Add(glowContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
});
}
public override Color4 AccentColour
@ -49,13 +30,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (base.AccentColour == value)
return;
base.AccentColour = value;
glowContainer.EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = value
};
}
}

View File

@ -19,7 +19,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public BodyPiece()
{
RelativeSizeAxes = Axes.Both;
Masking = true;
Children = new[]
{

View File

@ -9,5 +9,11 @@ namespace osu.Game.Rulesets.Mania.Objects
public abstract class ManiaHitObject : HitObject, IHasColumn
{
public int Column { get; set; }
/// <summary>
/// The number of other <see cref="ManiaHitObject"/> that start at
/// the same time as this hit object.
/// </summary>
public int Siblings { get; set; }
}
}

View File

@ -24,8 +24,8 @@ namespace osu.Game.Rulesets.Mania.UI
{
public int? Columns;
public ManiaHitRenderer(WorkingBeatmap beatmap)
: base(beatmap)
public ManiaHitRenderer(WorkingBeatmap beatmap, bool isForCurrentRuleset)
: base(beatmap, isForCurrentRuleset)
{
}
@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.UI
return t;
});
double lastObjectTime = (Objects.Last() as IHasEndTime)?.EndTime ?? Objects.Last().StartTime;
double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
// Perform some post processing of the timing changes
timingChanges = timingChanges
@ -76,6 +76,10 @@ namespace osu.Game.Rulesets.Mania.UI
protected override DrawableHitObject<ManiaHitObject, ManiaJudgement> GetVisualRepresentation(ManiaHitObject h)
{
var holdNote = h as HoldNote;
if (holdNote != null)
return new DrawableHoldNote(holdNote);
var note = h as Note;
if (note != null)
return new DrawableNote(note);

View File

@ -47,7 +47,11 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Beatmaps\Patterns\Legacy\PatternGenerator.cs" />
<Compile Include="Beatmaps\Patterns\PatternGenerator.cs" />
<Compile Include="Beatmaps\Patterns\Legacy\PatternType.cs" />
<Compile Include="Beatmaps\ManiaBeatmapConverter.cs" />
<Compile Include="Beatmaps\Patterns\Pattern.cs" />
<Compile Include="MathUtils\FastRandom.cs" />
<Compile Include="Judgements\HitWindows.cs" />
<Compile Include="Judgements\ManiaJudgement.cs" />

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu
{
public class OsuRuleset : Ruleset
{
public override HitRenderer CreateHitRendererWith(WorkingBeatmap beatmap) => new OsuHitRenderer(beatmap);
public override HitRenderer CreateHitRendererWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new OsuHitRenderer(beatmap, isForCurrentRuleset);
public override IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap) => new[]
{

View File

@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Osu.UI
{
public class OsuHitRenderer : HitRenderer<OsuHitObject, OsuJudgement>
{
public OsuHitRenderer(WorkingBeatmap beatmap)
: base(beatmap)
public OsuHitRenderer(WorkingBeatmap beatmap, bool isForCurrentRuleset)
: base(beatmap, isForCurrentRuleset)
{
}

View File

@ -41,13 +41,13 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(HitObject) };
protected override Beatmap<TaikoHitObject> ConvertBeatmap(Beatmap original)
protected override Beatmap<TaikoHitObject> ConvertBeatmap(Beatmap original, bool isForCurrentRuleset)
{
// Rewrite the beatmap info to add the slider velocity multiplier
BeatmapInfo info = original.BeatmapInfo.DeepClone();
info.Difficulty.SliderMultiplier *= legacy_velocity_multiplier;
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original);
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original, isForCurrentRuleset);
// Post processing step to transform hit objects with the same start time into strong hits
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko
{
public class TaikoRuleset : Ruleset
{
public override HitRenderer CreateHitRendererWith(WorkingBeatmap beatmap) => new TaikoHitRenderer(beatmap);
public override HitRenderer CreateHitRendererWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new TaikoHitRenderer(beatmap, isForCurrentRuleset);
public override IEnumerable<Mod> GetModsFor(ModType type)
{

View File

@ -22,8 +22,8 @@ namespace osu.Game.Rulesets.Taiko.UI
{
public class TaikoHitRenderer : HitRenderer<TaikoHitObject, TaikoJudgement>
{
public TaikoHitRenderer(WorkingBeatmap beatmap)
: base(beatmap)
public TaikoHitRenderer(WorkingBeatmap beatmap, bool isForCurrentRuleset)
: base(beatmap, isForCurrentRuleset)
{
}

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
using osu.Game.Beatmaps.Events;
using osu.Game.Beatmaps.Timing;
using osu.Game.Database;
using osu.Game.Rulesets.Objects;
@ -17,6 +18,7 @@ namespace osu.Game.Beatmaps
{
public BeatmapInfo BeatmapInfo;
public TimingInfo TimingInfo = new TimingInfo();
public EventInfo EventInfo = new EventInfo();
public readonly List<Color4> ComboColors = new List<Color4>
{
new Color4(17, 136, 170, 255),
@ -40,6 +42,7 @@ namespace osu.Game.Beatmaps
{
BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo;
TimingInfo = original?.TimingInfo ?? TimingInfo;
EventInfo = original?.EventInfo ?? EventInfo;
ComboColors = original?.ComboColors ?? ComboColors;
}
}

View File

@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps
protected DifficultyCalculator(Beatmap beatmap)
{
Objects = CreateBeatmapConverter().Convert(beatmap).HitObjects;
Objects = CreateBeatmapConverter().Convert(beatmap, true).HitObjects;
foreach (var h in Objects)
h.ApplyDefaults(beatmap.TimingInfo, beatmap.BeatmapInfo.Difficulty);

View File

@ -3,14 +3,11 @@
namespace osu.Game.Beatmaps.Events
{
public enum EventType
public class BackgroundEvent : Event
{
Background = 0,
Video = 1,
Break = 2,
Colour = 3,
Sprite = 4,
Sample = 5,
Animation = 6
/// <summary>
/// The file name.
/// </summary>
public string Filename;
}
}
}

View File

@ -0,0 +1,28 @@
// 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.Beatmaps.Events
{
public class BreakEvent : Event
{
/// <summary>
/// The minimum duration required for a break to have any effect.
/// </summary>
private const double min_break_duration = 650;
/// <summary>
/// The break end time.
/// </summary>
public double EndTime;
/// <summary>
/// The duration of the break.
/// </summary>
public double Duration => EndTime - StartTime;
/// <summary>
/// Whether the break has any effect. Breaks that are too short are culled before they reach the EventInfo.
/// </summary>
public bool HasEffect => Duration >= min_break_duration;
}
}

View File

@ -0,0 +1,13 @@
// 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.Beatmaps.Events
{
public abstract class Event
{
/// <summary>
/// The event start time.
/// </summary>
public double StartTime;
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
namespace osu.Game.Beatmaps.Events
{
public class EventInfo
{
/// <summary>
/// All the background events.
/// </summary>
public readonly List<BackgroundEvent> Backgrounds = new List<BackgroundEvent>();
/// <summary>
/// All the break events.
/// </summary>
public readonly List<BreakEvent> Breaks = new List<BreakEvent>();
/// <summary>
/// Total duration of all breaks.
/// </summary>
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
/// <summary>
/// Retrieves the active background at a time.
/// </summary>
/// <param name="time">The time to retrieve the background at.</param>
/// <returns>The background.</returns>
public BackgroundEvent BackgroundAt(double time) => Backgrounds.FirstOrDefault(b => b.StartTime <= time);
}
}

View File

@ -204,23 +204,42 @@ namespace osu.Game.Beatmaps.Formats
private void handleEvents(Beatmap beatmap, string val)
{
if (val.StartsWith(@"//"))
return;
if (val.StartsWith(@" "))
return; // TODO
string[] split = val.Split(',');
EventType type;
int intType;
if (!int.TryParse(split[0], out intType))
if (!Enum.TryParse(split[0], out type))
throw new InvalidDataException($@"Unknown event type {split[0]}");
// Todo: Implement the rest
switch (type)
{
if (!Enum.TryParse(split[0], out type))
throw new InvalidDataException($@"Unknown event type {split[0]}");
case EventType.Video:
case EventType.Background:
string filename = split[2].Trim('"');
beatmap.EventInfo.Backgrounds.Add(new BackgroundEvent
{
StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
Filename = filename
});
if (type == EventType.Background)
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
break;
case EventType.Break:
var breakEvent = new BreakEvent
{
StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
};
if (!breakEvent.HasEffect)
return;
beatmap.EventInfo.Breaks.Add(breakEvent);
break;
}
else
type = (EventType)intType;
// TODO: Parse and store the rest of the event
if (type == EventType.Background)
beatmap.BeatmapInfo.Metadata.BackgroundFile = split[2].Trim('"');
}
private void handleTimingPoints(Beatmap beatmap, string val)
@ -330,6 +349,9 @@ namespace osu.Game.Beatmaps.Formats
if (string.IsNullOrEmpty(line))
continue;
if (line.StartsWith(" ") || line.StartsWith("_") || line.StartsWith("//"))
continue;
if (line.StartsWith(@"osu file format v"))
{
beatmap.BeatmapInfo.BeatmapVersion = int.Parse(line.Substring(17));
@ -390,5 +412,16 @@ namespace osu.Game.Beatmaps.Formats
Soft = 2,
Drum = 3
}
internal enum EventType
{
Background = 0,
Video = 1,
Break = 2,
Colour = 3,
Sprite = 4,
Sample = 5,
Animation = 6
}
}
}

View File

@ -26,19 +26,21 @@ namespace osu.Game.Rulesets.Beatmaps
/// Converts a Beatmap using this Beatmap Converter.
/// </summary>
/// <param name="original">The un-converted Beatmap.</param>
/// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param>
/// <returns>The converted Beatmap.</returns>
public Beatmap<T> Convert(Beatmap original)
public Beatmap<T> Convert(Beatmap original, bool isForCurrentRuleset)
{
// We always operate on a clone of the original beatmap, to not modify it game-wide
return ConvertBeatmap(new Beatmap(original));
return ConvertBeatmap(new Beatmap(original), isForCurrentRuleset);
}
/// <summary>
/// Performs the conversion of a Beatmap using this Beatmap Converter.
/// </summary>
/// <param name="original">The un-converted Beatmap.</param>
/// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param>
/// <returns>The converted Beatmap.</returns>
protected virtual Beatmap<T> ConvertBeatmap(Beatmap original)
protected virtual Beatmap<T> ConvertBeatmap(Beatmap original, bool isForCurrentRuleset)
{
return new Beatmap<T>
{

View File

@ -43,5 +43,10 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
EndTime = endTime
};
}
protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime)
{
return null;
}
}
}

View File

@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Globalization;
using osu.Game.Beatmaps.Formats;
using osu.Game.Audio;
using System.Linq;
namespace osu.Game.Rulesets.Objects.Legacy
{
@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
var soundType = (LegacySoundType)int.Parse(split[4]);
var bankInfo = new SampleBankInfo();
HitObject result;
HitObject result = null;
if ((type & ConvertHitObjectType.Circle) > 0)
{
@ -140,17 +141,20 @@ namespace osu.Game.Rulesets.Objects.Legacy
{
// Note: Hold is generated by BMS converts
// Todo: Apparently end time is determined by samples??
// Shouldn't need implementation until mania
double endTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture);
result = new ConvertHold
if (split.Length > 5 && !string.IsNullOrEmpty(split[5]))
{
Position = new Vector2(int.Parse(split[0]), int.Parse(split[1])),
NewCombo = combo
};
string[] ss = split[5].Split(':');
endTime = Convert.ToDouble(ss[0], CultureInfo.InvariantCulture);
readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo);
}
result = CreateHold(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, endTime);
}
else
throw new InvalidOperationException($@"Unknown hit object type {type}");
if (result == null)
throw new InvalidOperationException($@"Unknown hit object type {type}.");
result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture);
result.Samples = convertSoundType(soundType, bankInfo);
@ -214,6 +218,14 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <returns>The hit object.</returns>
protected abstract HitObject CreateSpinner(Vector2 position, double endTime);
/// <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="endTime">The hold end time.</param>
protected abstract HitObject CreateHold(Vector2 position, bool newCombo, double endTime);
private SampleInfoList convertSoundType(LegacySoundType type, SampleBankInfo bankInfo)
{
var soundTypes = new SampleInfoList

View File

@ -1,22 +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 OpenTK;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Objects.Legacy
{
/// <summary>
/// Legacy Hold-type, used for parsing "specials" in beatmaps.
/// </summary>
internal sealed class ConvertHold : HitObject, IHasPosition, IHasCombo, IHasHold
{
public Vector2 Position { get; set; }
public float X => Position.X;
public float Y => Position.Y;
public bool NewCombo { get; set; }
}
}

View File

@ -44,5 +44,14 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
EndTime = endTime
};
}
protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime)
{
return new ConvertHold
{
X = position.X,
EndTime = endTime
};
}
}
}

View File

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

View File

@ -44,5 +44,10 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
EndTime = endTime
};
}
protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime)
{
return null;
}
}
}

View File

@ -41,5 +41,10 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
EndTime = endTime
};
}
protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime)
{
return null;
}
}
}

View File

@ -8,5 +8,9 @@ namespace osu.Game.Rulesets.Objects.Types
/// </summary>
public interface IHasHold
{
/// <summary>
/// The time at which the hold ends.
/// </summary>
double EndTime { get; }
}
}

View File

@ -18,12 +18,13 @@ namespace osu.Game.Rulesets
public abstract IEnumerable<Mod> GetModsFor(ModType type);
/// <summary>
/// Attempt to create a HitRenderer for the provided beatmap.
/// Attempt to create a hit renderer for a beatmap
/// </summary>
/// <param name="beatmap"></param>
/// <param name="beatmap">The beatmap to create the hit renderer for.</param>
/// <param name="isForCurrentRuleset">Whether the hit renderer should assume the beatmap is for the current ruleset.</param>
/// <exception cref="BeatmapInvalidForRulesetException">Unable to successfully load the beatmap to be usable with this ruleset.</exception>
/// <returns></returns>
public abstract HitRenderer CreateHitRendererWith(WorkingBeatmap beatmap);
public abstract HitRenderer CreateHitRendererWith(WorkingBeatmap beatmap, bool isForCurrentRuleset);
public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap);

View File

@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.UI
/// </summary>
protected abstract bool AllObjectsJudged { get; }
protected HitRenderer()
internal HitRenderer()
{
KeyConversionInputManager = CreateKeyConversionInputManager();
KeyConversionInputManager.RelativeSizeAxes = Axes.Both;
@ -120,7 +120,12 @@ namespace osu.Game.Rulesets.UI
/// </summary>
public Beatmap<TObject> Beatmap;
protected HitRenderer(WorkingBeatmap beatmap)
/// <summary>
/// Creates a hit renderer for a beatmap.
/// </summary>
/// <param name="beatmap">The beatmap to create the hit renderer for.</param>
/// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param>
internal HitRenderer(WorkingBeatmap beatmap, bool isForCurrentRuleset)
{
Debug.Assert(beatmap != null, "HitRenderer initialized with a null beatmap.");
@ -134,7 +139,7 @@ namespace osu.Game.Rulesets.UI
throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can't be converted for the current ruleset.");
// Convert the beatmap
Beatmap = converter.Convert(beatmap.Beatmap);
Beatmap = converter.Convert(beatmap.Beatmap, isForCurrentRuleset);
// Apply defaults
foreach (var h in Beatmap.HitObjects)
@ -201,8 +206,13 @@ namespace osu.Game.Rulesets.UI
private readonly List<DrawableHitObject<TObject, TJudgement>> drawableObjects = new List<DrawableHitObject<TObject, TJudgement>>();
protected HitRenderer(WorkingBeatmap beatmap)
: base(beatmap)
/// <summary>
/// Creates a hit renderer for a beatmap.
/// </summary>
/// <param name="beatmap">The beatmap to create the hit renderer for.</param>
/// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param>
protected HitRenderer(WorkingBeatmap beatmap, bool isForCurrentRuleset)
: base(beatmap, isForCurrentRuleset)
{
InputManager.Add(content = new Container
{

View File

@ -89,7 +89,7 @@ namespace osu.Game.Screens.Play
try
{
HitRenderer = rulesetInstance.CreateHitRendererWith(Beatmap);
HitRenderer = rulesetInstance.CreateHitRendererWith(Beatmap, ruleset.ID == Beatmap.BeatmapInfo.Ruleset.ID);
}
catch (BeatmapInvalidForRulesetException)
{
@ -97,7 +97,7 @@ namespace osu.Game.Screens.Play
// let's try again forcing the beatmap's ruleset.
ruleset = Beatmap.BeatmapInfo.Ruleset;
rulesetInstance = ruleset.CreateInstance();
HitRenderer = rulesetInstance.CreateHitRendererWith(Beatmap);
HitRenderer = rulesetInstance.CreateHitRendererWith(Beatmap, true);
}
if (!HitRenderer.Objects.Any())

View File

@ -74,6 +74,10 @@
<Compile Include="Audio\SampleInfoList.cs" />
<Compile Include="Beatmaps\Drawables\BeatmapBackgroundSprite.cs" />
<Compile Include="Beatmaps\DifficultyCalculator.cs" />
<Compile Include="Beatmaps\Events\BackgroundEvent.cs" />
<Compile Include="Beatmaps\Events\BreakEvent.cs" />
<Compile Include="Beatmaps\Events\Event.cs" />
<Compile Include="Beatmaps\Events\EventInfo.cs" />
<Compile Include="Online\API\Requests\PostMessageRequest.cs" />
<Compile Include="Online\Chat\ErrorMessage.cs" />
<Compile Include="Overlays\Chat\ChatTabControl.cs" />
@ -125,6 +129,7 @@
<Compile Include="Rulesets\Objects\Legacy\ConvertSlider.cs" />
<Compile Include="Rulesets\Objects\Legacy\Mania\ConvertHit.cs" />
<Compile Include="Rulesets\Objects\Legacy\Mania\ConvertHitObjectParser.cs" />
<Compile Include="Rulesets\Objects\Legacy\Mania\ConvertHold.cs" />
<Compile Include="Rulesets\Objects\Legacy\Mania\ConvertSlider.cs" />
<Compile Include="Rulesets\Objects\Legacy\Mania\ConvertSpinner.cs" />
<Compile Include="Rulesets\Objects\Legacy\Osu\ConvertHitObjectParser.cs" />
@ -161,7 +166,6 @@
<Compile Include="Rulesets\Objects\CircularArcApproximator.cs" />
<Compile Include="Rulesets\Objects\Legacy\Osu\ConvertHit.cs" />
<Compile Include="Rulesets\Objects\Legacy\ConvertHitObjectParser.cs" />
<Compile Include="Rulesets\Objects\Legacy\ConvertHold.cs" />
<Compile Include="Rulesets\Objects\Legacy\Osu\ConvertSlider.cs" />
<Compile Include="Rulesets\Objects\Legacy\Osu\ConvertSpinner.cs" />
<Compile Include="Rulesets\Objects\SliderCurve.cs" />
@ -341,7 +345,6 @@
<Compile Include="Beatmaps\Formats\BeatmapDecoder.cs" />
<Compile Include="Beatmaps\Formats\OsuLegacyDecoder.cs" />
<Compile Include="Beatmaps\IO\OszArchiveReader.cs" />
<Compile Include="Beatmaps\Events\EventType.cs" />
<Compile Include="Graphics\UserInterface\Volume\VolumeMeter.cs" />
<Compile Include="Database\BeatmapSetInfo.cs" />
<Compile Include="Database\BeatmapMetadata.cs" />