Initial rework of beatmap conversion process

This commit is contained in:
smoogipoo
2018-04-19 22:04:12 +09:00
parent 66b3b295e7
commit 03a5df84c6
56 changed files with 230 additions and 234 deletions

View File

@ -22,25 +22,27 @@ namespace osu.Game.Beatmaps
remove => ObjectConverted -= value;
}
/// <summary>
/// Checks if a Beatmap can be converted using this Beatmap Converter.
/// </summary>
/// <param name="beatmap">The Beatmap to check.</param>
/// <returns>Whether the Beatmap can be converted using this Beatmap Converter.</returns>
public bool CanConvert(IBeatmap beatmap) => ValidConversionTypes.All(t => beatmap.HitObjects.Any(t.IsInstanceOfType));
public IBeatmap Beatmap { get; }
/// <summary>
/// Converts a Beatmap using this Beatmap Converter.
/// </summary>
/// <param name="original">The un-converted Beatmap.</param>
/// <returns>The converted Beatmap.</returns>
public Beatmap<T> Convert(IBeatmap original)
protected BeatmapConverter(IBeatmap beatmap)
{
// We always operate on a clone of the original beatmap, to not modify it game-wide
return ConvertBeatmap(original.Clone());
Beatmap = beatmap;
}
void IBeatmapConverter.Convert(IBeatmap original) => Convert(original);
/// <summary>
/// Whether <see cref="Beatmap"/> can be converted by this <see cref="BeatmapConverter{T}"/>.
/// </summary>
public bool CanConvert => ValidConversionTypes.All(t => Beatmap.HitObjects.Any(t.IsInstanceOfType));
/// <summary>
/// Converts <see cref="Beatmap"/>.
/// </summary>
/// <returns>The converted Beatmap.</returns>
public IBeatmap Convert()
{
// We always operate on a clone of the original beatmap, to not modify it game-wide
return ConvertBeatmap(Beatmap.Clone());
}
/// <summary>
/// Performs the conversion of a Beatmap using this Beatmap Converter.

View File

@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps
this.audioManager = audioManager;
}
protected override IBeatmap GetBeatmap()
protected override IBeatmap GetOriginalBeatmap()
{
try
{

View File

@ -2,30 +2,47 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Beatmaps
{
public interface IBeatmapProcessor
{
IBeatmap Beatmap { get; }
/// <summary>
/// Post-processes <see cref="Beatmap"/> to add mode-specific components that aren't added during conversion.
/// <para>
/// An example of such a usage is for combo colours.
/// </para>
/// </summary>
void PostProcess();
}
/// <summary>
/// Processes a post-converted Beatmap.
/// </summary>
/// <typeparam name="TObject">The type of HitObject contained in the Beatmap.</typeparam>
public class BeatmapProcessor<TObject>
where TObject : HitObject
public class BeatmapProcessor : IBeatmapProcessor
{
public IBeatmap Beatmap { get; }
public BeatmapProcessor(IBeatmap beatmap)
{
Beatmap = beatmap;
}
/// <summary>
/// Post-processes a Beatmap to add mode-specific components that aren't added during conversion.
/// <para>
/// An example of such a usage is for combo colours.
/// </para>
/// </summary>
/// <param name="beatmap">The Beatmap to process.</param>
public virtual void PostProcess(Beatmap<TObject> beatmap)
public virtual void PostProcess()
{
IHasComboInformation lastObj = null;
foreach (var obj in beatmap.HitObjects.OfType<IHasComboInformation>())
foreach (var obj in Beatmap.HitObjects.OfType<IHasComboInformation>())
{
if (obj.NewCombo)
{

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Objects;
using System.Collections.Generic;
using osu.Game.Rulesets.Mods;
using osu.Framework.Timing;
@ -12,30 +11,17 @@ namespace osu.Game.Beatmaps
{
public abstract class DifficultyCalculator
{
protected double TimeRate = 1;
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
}
public abstract class DifficultyCalculator<T> : DifficultyCalculator where T : HitObject
{
protected readonly Beatmap<T> Beatmap;
protected readonly IBeatmap Beatmap;
protected readonly Mod[] Mods;
protected double TimeRate = 1;
protected DifficultyCalculator(IBeatmap beatmap, Mod[] mods = null)
{
Beatmap = beatmap;
Mods = mods ?? new Mod[0];
var converter = CreateBeatmapConverter(beatmap);
foreach (var mod in Mods.OfType<IApplicableToBeatmapConverter<T>>())
mod.ApplyToBeatmapConverter(converter);
Beatmap = converter.Convert(beatmap);
ApplyMods(Mods);
PreprocessHitObjects();
}
protected virtual void ApplyMods(Mod[] mods)
@ -43,22 +29,12 @@ namespace osu.Game.Beatmaps
var clock = new StopwatchClock();
mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock));
TimeRate = clock.Rate;
foreach (var mod in Mods.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty);
foreach (var h in Beatmap.HitObjects)
h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
foreach (var mod in mods.OfType<IApplicableToHitObject<T>>())
foreach (var obj in Beatmap.HitObjects)
mod.ApplyToHitObject(obj);
}
protected virtual void PreprocessHitObjects()
{
}
protected abstract BeatmapConverter<T> CreateBeatmapConverter(IBeatmap beatmap);
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
}
}

View File

@ -7,6 +7,7 @@ using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
namespace osu.Game.Beatmaps
@ -39,7 +40,7 @@ namespace osu.Game.Beatmaps
this.game = game;
}
protected override IBeatmap GetBeatmap() => new Beatmap();
protected override IBeatmap GetOriginalBeatmap() => new Beatmap();
protected override Texture GetBackground() => game.Textures.Get(@"Backgrounds/bg4");
@ -58,6 +59,8 @@ namespace osu.Game.Beatmaps
throw new NotImplementedException();
}
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => null;
public override string Description => "dummy";
@ -68,6 +71,14 @@ namespace osu.Game.Beatmaps
: base(rulesetInfo)
{
}
private class DummyBeatmapConverter : IBeatmapConverter
{
public event Action<HitObject, IEnumerable<HitObject>> ObjectConverted;
public IBeatmap Beatmap { get; set; }
public bool CanConvert => true;
public IBeatmap Convert() => Beatmap;
}
}
}
}

View File

@ -16,10 +16,16 @@ namespace osu.Game.Beatmaps
/// </summary>
event Action<HitObject, IEnumerable<HitObject>> ObjectConverted;
IBeatmap Beatmap { get; }
/// <summary>
/// Converts a Beatmap using this Beatmap Converter.
/// Whether <see cref="Beatmap"/> can be converted by this <see cref="IBeatmapConverter"/>.
/// </summary>
/// <param name="beatmap">The un-converted Beatmap.</param>
void Convert(IBeatmap beatmap);
bool CanConvert { get; }
/// <summary>
/// Converts <see cref="Beatmap"/>.
/// </summary>
IBeatmap Convert();
}
}

View File

@ -14,6 +14,8 @@ using osu.Framework.IO.File;
using System.IO;
using osu.Game.IO.Serialization;
using System.Diagnostics;
using osu.Game.Rulesets;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
namespace osu.Game.Beatmaps
@ -36,7 +38,7 @@ namespace osu.Game.Beatmaps
Mods.ValueChanged += mods => applyRateAdjustments();
beatmap = new AsyncLazy<IBeatmap>(populateBeatmap);
originalBeatmap = new AsyncLazy<IBeatmap>(populateOriginalBeatmap);
background = new AsyncLazy<Texture>(populateBackground, b => b == null || !b.IsDisposed);
track = new AsyncLazy<Track>(populateTrack);
waveform = new AsyncLazy<Waveform>(populateWaveform);
@ -51,26 +53,25 @@ namespace osu.Game.Beatmaps
{
var path = FileSafety.GetTempPath(Guid.NewGuid().ToString().Replace("-", string.Empty) + ".json");
using (var sw = new StreamWriter(path))
sw.WriteLine(Beatmap.Serialize());
sw.WriteLine(OriginalBeatmap.Serialize());
Process.Start(path);
}
protected abstract IBeatmap GetBeatmap();
protected abstract IBeatmap GetOriginalBeatmap();
protected abstract Texture GetBackground();
protected abstract Track GetTrack();
protected virtual Skin GetSkin() => new DefaultSkin();
protected virtual Waveform GetWaveform() => new Waveform();
protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
public bool BeatmapLoaded => beatmap.IsResultAvailable;
public IBeatmap Beatmap => beatmap.Value.Result;
public async Task<IBeatmap> GetBeatmapAsync() => await beatmap.Value;
public bool BeatmapLoaded => originalBeatmap.IsResultAvailable;
public IBeatmap OriginalBeatmap => originalBeatmap.Value.Result;
public async Task<IBeatmap> GetOriginalBeatmapAsync() => await originalBeatmap.Value;
private readonly AsyncLazy<IBeatmap> originalBeatmap;
private readonly AsyncLazy<IBeatmap> beatmap;
private IBeatmap populateBeatmap()
private IBeatmap populateOriginalBeatmap()
{
var b = GetBeatmap() ?? new Beatmap();
var b = GetOriginalBeatmap() ?? new Beatmap();
// use the database-backed info.
b.BeatmapInfo = BeatmapInfo;
@ -78,6 +79,41 @@ namespace osu.Game.Beatmaps
return b;
}
public IBeatmap GetBeatmap(RulesetInfo ruleset)
{
var rulesetInstance = ruleset.CreateInstance();
IBeatmapConverter converter = rulesetInstance.CreateBeatmapConverter(OriginalBeatmap);
// Check if the beatmap can be converted
if (!converter.CanConvert)
throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the current ruleset (converter: {converter}).");
// Apply conversion mods
foreach (var mod in Mods.Value.OfType<IApplicableToBeatmapConverter>())
mod.ApplyToBeatmapConverter(converter);
// Convert
IBeatmap converted = converter.Convert();
// Apply difficulty mods
foreach (var mod in Mods.Value.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty);
// Post-process
rulesetInstance.CreateBeatmapProcessor(converted)?.PostProcess();
// Compute default values for hitobjects, including creating nested hitobjects in-case they're needed
foreach (var obj in converted.HitObjects)
obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty);
foreach (var mod in Mods.Value.OfType<IApplicableToHitObject>())
foreach (var obj in converted.HitObjects)
mod.ApplyToHitObject(obj);
return converted;
}
public bool BackgroundLoaded => background.IsResultAvailable;
public Texture Background => background.Value.Result;
public async Task<Texture> GetBackgroundAsync() => await background.Value;