Merge remote-tracking branch 'upstream/master' into smoogipoo-disable-mouse-buttons

This commit is contained in:
Dean Herbert
2018-05-15 19:20:43 +09:00
136 changed files with 1536 additions and 846 deletions

View File

@ -15,21 +15,27 @@ namespace osu.Game.Beatmaps
/// <summary>
/// A Beatmap containing converted HitObjects.
/// </summary>
public class Beatmap<T> : IJsonSerializable
public class Beatmap<T> : IBeatmap
where T : HitObject
{
public BeatmapInfo BeatmapInfo = new BeatmapInfo();
public ControlPointInfo ControlPointInfo = new ControlPointInfo();
public List<BreakPeriod> Breaks = new List<BreakPeriod>();
public BeatmapInfo BeatmapInfo { get; set; } = new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
Artist = @"Unknown",
Title = @"Unknown",
AuthorString = @"Unknown Creator",
},
Version = @"Normal",
BaseDifficulty = new BeatmapDifficulty()
};
[JsonIgnore]
public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata;
/// <summary>
/// The HitObjects this Beatmap contains.
/// </summary>
[JsonConverter(typeof(TypedListConverter<HitObject>))]
public List<T> HitObjects = new List<T>();
public ControlPointInfo ControlPointInfo { get; set; } = new ControlPointInfo();
public List<BreakPeriod> Breaks { get; set; } = new List<BreakPeriod>();
/// <summary>
/// Total amount of break time in the beatmap.
@ -38,51 +44,28 @@ namespace osu.Game.Beatmaps
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
/// <summary>
/// Constructs a new beatmap.
/// The HitObjects this Beatmap contains.
/// </summary>
/// <param name="original">The original beatmap to use the parameters of.</param>
public Beatmap(Beatmap<T> original = null)
{
BeatmapInfo = original?.BeatmapInfo.DeepClone() ?? BeatmapInfo;
ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo;
Breaks = original?.Breaks ?? Breaks;
HitObjects = original?.HitObjects ?? HitObjects;
[JsonConverter(typeof(TypedListConverter<HitObject>))]
public List<T> HitObjects = new List<T>();
if (original == null && Metadata == null)
{
// we may have no metadata in cases we weren't sourced from the database.
// let's fill it (and other related fields) so we don't need to null-check it in future usages.
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
Artist = @"Unknown",
Title = @"Unknown",
AuthorString = @"Unknown Creator",
},
Version = @"Normal",
BaseDifficulty = new BeatmapDifficulty()
};
}
IEnumerable<HitObject> IBeatmap.HitObjects => HitObjects;
public virtual IEnumerable<BeatmapStatistic> GetStatistics() => Enumerable.Empty<BeatmapStatistic>();
IBeatmap IBeatmap.Clone() => Clone();
public Beatmap<T> Clone()
{
var newInstance = (Beatmap<T>)MemberwiseClone();
newInstance.BeatmapInfo = BeatmapInfo.DeepClone();
return newInstance;
}
}
/// <summary>
/// A Beatmap containing un-converted HitObjects.
/// </summary>
public class Beatmap : Beatmap<HitObject>
{
/// <summary>
/// Constructs a new beatmap.
/// </summary>
/// <param name="original">The original beatmap to use the parameters of.</param>
public Beatmap(Beatmap original)
: base(original)
{
}
public Beatmap()
{
}
public Beatmap Clone() => (Beatmap)base.Clone();
}
}

View File

@ -22,32 +22,34 @@ 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(Beatmap 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(Beatmap original)
protected BeatmapConverter(IBeatmap beatmap)
{
// We always operate on a clone of the original beatmap, to not modify it game-wide
return ConvertBeatmap(new Beatmap(original));
Beatmap = beatmap;
}
void IBeatmapConverter.Convert(Beatmap original) => Convert(original);
/// <summary>
/// Whether <see cref="Beatmap"/> can be converted by this <see cref="BeatmapConverter{T}"/>.
/// </summary>
public bool CanConvert => !Beatmap.HitObjects.Any() || 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.
/// </summary>
/// <param name="original">The un-converted Beatmap.</param>
/// <returns>The converted Beatmap.</returns>
protected virtual Beatmap<T> ConvertBeatmap(Beatmap original)
protected virtual Beatmap<T> ConvertBeatmap(IBeatmap original)
{
var beatmap = CreateBeatmap();
@ -67,7 +69,7 @@ namespace osu.Game.Beatmaps
/// <param name="original">The hit object to convert.</param>
/// <param name="beatmap">The un-converted Beatmap.</param>
/// <returns>The converted hit object.</returns>
private IEnumerable<T> convert(HitObject original, Beatmap beatmap)
private IEnumerable<T> convert(HitObject original, IBeatmap beatmap)
{
// Check if the hitobject is already the converted type
T tObject = original as T;
@ -107,6 +109,6 @@ namespace osu.Game.Beatmaps
/// <param name="original">The hit object to convert.</param>
/// <param name="beatmap">The un-converted Beatmap.</param>
/// <returns>The converted hit object.</returns>
protected abstract IEnumerable<T> ConvertHitObject(HitObject original, Beatmap beatmap);
protected abstract IEnumerable<T> ConvertHitObject(HitObject original, IBeatmap beatmap);
}
}

View File

@ -9,7 +9,9 @@ using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps.Formats;
@ -333,7 +335,7 @@ namespace osu.Game.Beatmaps
ms.Position = 0;
var decoder = Decoder.GetDecoder<Beatmap>(sr);
Beatmap beatmap = decoder.Decode(sr);
IBeatmap beatmap = decoder.Decode(sr);
beatmap.BeatmapInfo.Path = name;
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
@ -341,9 +343,16 @@ namespace osu.Game.Beatmaps
RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
// TODO: this should be done in a better place once we actually need to dynamically update it.
beatmap.BeatmapInfo.Ruleset = ruleset;
beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance()?.CreateDifficultyCalculator(beatmap).Calculate() ?? 0;
if (ruleset != null)
{
// TODO: this should be done in a better place once we actually need to dynamically update it.
var converted = new DummyConversionBeatmap(beatmap).GetPlayableBeatmap(ruleset);
beatmap.BeatmapInfo.StarDifficulty = ruleset.CreateInstance().CreateDifficultyCalculator(converted).Calculate();
}
else
beatmap.BeatmapInfo.StarDifficulty = 0;
beatmapInfos.Add(beatmap.BeatmapInfo);
}
@ -351,5 +360,23 @@ namespace osu.Game.Beatmaps
return beatmapInfos;
}
/// <summary>
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
/// </summary>
private class DummyConversionBeatmap : WorkingBeatmap
{
private readonly IBeatmap beatmap;
public DummyConversionBeatmap(IBeatmap beatmap)
: base(beatmap.BeatmapInfo)
{
this.beatmap = beatmap;
}
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null;
protected override Track GetTrack() => null;
}
}
}

View File

@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps
this.audioManager = audioManager;
}
protected override Beatmap GetBeatmap()
protected override IBeatmap GetBeatmap()
{
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,64 +0,0 @@
// 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;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
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 Mod[] Mods;
protected DifficultyCalculator(Beatmap beatmap, Mod[] mods = null)
{
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)
{
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(Beatmap beatmap);
}
}

View File

@ -6,7 +6,9 @@ using System.Collections.Generic;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
namespace osu.Game.Beatmaps
@ -39,7 +41,7 @@ namespace osu.Game.Beatmaps
this.game = game;
}
protected override Beatmap GetBeatmap() => new Beatmap();
protected override IBeatmap GetBeatmap() => new Beatmap();
protected override Texture GetBackground() => game.Textures.Get(@"Backgrounds/bg4");
@ -53,12 +55,14 @@ namespace osu.Game.Beatmaps
{
public override IEnumerable<Mod> GetModsFor(ModType type) => new Mod[] { };
public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset)
public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap)
{
throw new NotImplementedException();
}
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => null;
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 +72,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

@ -0,0 +1,56 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Beatmaps
{
public interface IBeatmap : IJsonSerializable
{
/// <summary>
/// This beatmap's info.
/// </summary>
BeatmapInfo BeatmapInfo { get; set; }
/// <summary>
/// This beatmap's metadata.
/// </summary>
BeatmapMetadata Metadata { get; }
/// <summary>
/// The control points in this beatmap.
/// </summary>
ControlPointInfo ControlPointInfo { get; }
/// <summary>
/// The breaks in this beatmap.
/// </summary>
List<BreakPeriod> Breaks { get; }
/// <summary>
/// Total amount of break time in the beatmap.
/// </summary>
double TotalBreakTime { get; }
/// <summary>
/// The hitobjects contained by this beatmap.
/// </summary>
IEnumerable<HitObject> HitObjects { get; }
/// <summary>
/// Returns statistics for the <see cref="HitObjects"/> contained in this beatmap.
/// </summary>
/// <returns></returns>
IEnumerable<BeatmapStatistic> GetStatistics();
/// <summary>
/// Creates a shallow-clone of this beatmap and returns it.
/// </summary>
/// <returns>The shallow-cloned beatmap.</returns>
IBeatmap Clone();
}
}

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(Beatmap beatmap);
bool CanConvert { get; }
/// <summary>
/// Converts <see cref="Beatmap"/>.
/// </summary>
IBeatmap Convert();
}
}

View File

@ -1,21 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Beatmaps.Legacy
{
/// <summary>
/// A type of Beatmap loaded from a legacy .osu beatmap file (version &lt;=15).
/// </summary>
public class LegacyBeatmap : Beatmap
{
/// <summary>
/// Constructs a new beatmap.
/// </summary>
/// <param name="original">The original beatmap to use the parameters of.</param>
internal LegacyBeatmap(Beatmap original = null)
: base(original)
{
HitObjects = original?.HitObjects;
}
}
}

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<Beatmap>(populateBeatmap);
beatmap = new AsyncLazy<IBeatmap>(populateBeatmap);
background = new AsyncLazy<Texture>(populateBackground, b => b == null || !b.IsDisposed);
track = new AsyncLazy<Track>(populateTrack);
waveform = new AsyncLazy<Waveform>(populateWaveform);
@ -45,7 +47,7 @@ namespace osu.Game.Beatmaps
}
/// <summary>
/// Saves the <see cref="Beatmap"/>.
/// Saves the <see cref="Beatmaps.Beatmap"/>.
/// </summary>
public void Save()
{
@ -55,7 +57,7 @@ namespace osu.Game.Beatmaps
Process.Start(path);
}
protected abstract Beatmap GetBeatmap();
protected abstract IBeatmap GetBeatmap();
protected abstract Texture GetBackground();
protected abstract Track GetTrack();
protected virtual Skin GetSkin() => new DefaultSkin();
@ -63,12 +65,11 @@ namespace osu.Game.Beatmaps
protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
public bool BeatmapLoaded => beatmap.IsResultAvailable;
public Beatmap Beatmap => beatmap.Value.Result;
public async Task<Beatmap> GetBeatmapAsync() => await beatmap.Value;
public IBeatmap Beatmap => beatmap.Value.Result;
public async Task<IBeatmap> GetBeatmapAsync() => await beatmap.Value;
private readonly AsyncLazy<IBeatmap> beatmap;
private readonly AsyncLazy<Beatmap> beatmap;
private Beatmap populateBeatmap()
private IBeatmap populateBeatmap()
{
var b = GetBeatmap() ?? new Beatmap();
@ -78,6 +79,51 @@ namespace osu.Game.Beatmaps
return b;
}
/// <summary>
/// Constructs a playable <see cref="IBeatmap"/> from <see cref="Beatmap"/> using the applicable converters for a specific <see cref="RulesetInfo"/>.
/// <para>
/// The returned <see cref="IBeatmap"/> is in a playable state - all <see cref="HitObject"/> and <see cref="BeatmapDifficulty"/> <see cref="Mod"/>s
/// have been applied, and <see cref="HitObject"/>s have been fully constructed.
/// </para>
/// </summary>
/// <param name="ruleset">The <see cref="RulesetInfo"/> to create a playable <see cref="IBeatmap"/> for.</param>
/// <returns>The converted <see cref="IBeatmap"/>.</returns>
/// <exception cref="BeatmapInvalidForRulesetException">If <see cref="Beatmap"/> could not be converted to <paramref name="ruleset"/>.</exception>
public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset)
{
var rulesetInstance = ruleset.CreateInstance();
IBeatmapConverter converter = rulesetInstance.CreateBeatmapConverter(Beatmap);
// Check if the beatmap can be converted
if (!converter.CanConvert)
throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, 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;

View File

@ -44,19 +44,6 @@ namespace osu.Game.Graphics.Containers
return base.OnClick(state);
}
protected override bool OnDragStart(InputState state)
{
if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position))
{
State = Visibility.Hidden;
return true;
}
return base.OnDragStart(state);
}
protected override bool OnDrag(InputState state) => State == Visibility.Hidden;
private void onStateChanged(Visibility visibility)
{
switch (visibility)

View File

@ -14,14 +14,18 @@ namespace osu.Game.Graphics.UserInterface
public class BreadcrumbControl<T> : OsuTabControl<T>
{
private const float padding = 10;
private const float item_chevron_size = 10;
protected override TabItem<T> CreateTabItem(T value) => new BreadcrumbTabItem(value);
protected override TabItem<T> CreateTabItem(T value) => new BreadcrumbTabItem(value)
{
AccentColour = AccentColour,
};
protected override float StripWidth() => base.StripWidth() - (padding + 8);
protected override float StripWidth() => base.StripWidth() - (padding + item_chevron_size);
public BreadcrumbControl()
{
Height = 26;
Height = 32;
TabContainer.Spacing = new Vector2(padding, 0f);
Current.ValueChanged += tab =>
{
@ -47,6 +51,7 @@ namespace osu.Game.Graphics.UserInterface
public override bool HandleKeyboardInput => State == Visibility.Visible;
public override bool HandleMouseInput => State == Visibility.Visible;
public override bool IsRemovable => true;
private Visibility state;
@ -77,13 +82,14 @@ namespace osu.Game.Graphics.UserInterface
public BreadcrumbTabItem(T value) : base(value)
{
Text.TextSize = 16;
Padding = new MarginPadding { Right = padding + 8 }; //padding + chevron width
Text.TextSize = 18;
Text.Margin = new MarginPadding { Vertical = 8 };
Padding = new MarginPadding { Right = padding + item_chevron_size };
Add(Chevron = new SpriteIcon
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreLeft,
Size = new Vector2(12),
Size = new Vector2(item_chevron_size),
Icon = FontAwesome.fa_chevron_right,
Margin = new MarginPadding { Left = padding },
Alpha = 0f,

View File

@ -0,0 +1,66 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using OpenTK.Graphics;
namespace osu.Game.Overlays
{
/// <summary>
/// An overlay which will display a black screen that dims over a period before confirming an exit action.
/// Action is BYO (derived class will need to call <see cref="BeginConfirm"/> and <see cref="AbortConfirm"/> from a user event).
/// </summary>
public abstract class HoldToConfirmOverlay : Container
{
public Action Action;
private Box overlay;
private const int activate_delay = 400;
private const int fadeout_delay = 200;
private bool fired;
/// <summary>
/// Whether the overlay should be allowed to return from a fired state.
/// </summary>
protected virtual bool AllowMultipleFires => false;
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.Both;
AlwaysPresent = true;
Children = new Drawable[]
{
overlay = new Box
{
Alpha = 0,
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
}
};
}
protected void BeginConfirm()
{
if (!AllowMultipleFires && fired) return;
overlay.FadeIn(activate_delay * (1 - overlay.Alpha), Easing.Out).OnComplete(_ =>
{
Action?.Invoke();
fired = true;
});
}
protected void AbortConfirm()
{
if (!AllowMultipleFires && fired) return;
overlay.FadeOut(fadeout_delay, Easing.Out);
}
}
}

View File

@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Input;
using OpenTK.Graphics;
@ -43,7 +44,7 @@ namespace osu.Game.Overlays.KeyBinding
}
private OsuSpriteText text;
private OsuSpriteText pressAKey;
private OsuTextFlowContainer pressAKey;
private FillFlowContainer<KeyButton> buttons;
@ -95,10 +96,11 @@ namespace osu.Game.Overlays.KeyBinding
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight
},
pressAKey = new OsuSpriteText
pressAKey = new OsuTextFlowContainer
{
Text = "Press a key to change binding, DEL to delete, ESC to cancel.",
Y = height,
Text = "Press a key to change binding, Shift+Delete to delete, Escape to cancel.",
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding(padding),
Alpha = 0,
Colour = colours.YellowDark
@ -204,9 +206,16 @@ namespace osu.Game.Overlays.KeyBinding
finalise();
return true;
case Key.Delete:
bindTarget.UpdateKeyCombination(InputKey.None);
finalise();
return true;
{
if (state.Keyboard.ShiftPressed)
{
bindTarget.UpdateKeyCombination(InputKey.None);
finalise();
return true;
}
break;
}
}
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state));
@ -223,6 +232,26 @@ namespace osu.Game.Overlays.KeyBinding
return true;
}
protected override bool OnJoystickPress(InputState state, Framework.Input.JoystickEventArgs args)
{
if (!HasFocus)
return false;
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state));
finalise();
return true;
}
protected override bool OnJoystickRelease(InputState state, Framework.Input.JoystickEventArgs args)
{
if (!HasFocus)
return base.OnJoystickRelease(state, args);
finalise();
return true;
}
private void finalise()
{
if (bindTarget != null)
@ -241,7 +270,7 @@ namespace osu.Game.Overlays.KeyBinding
GetContainingInputManager().ChangeFocus(null);
pressAKey.FadeOut(300, Easing.OutQuint);
pressAKey.Padding = new MarginPadding { Bottom = -pressAKey.DrawHeight };
pressAKey.Padding = new MarginPadding { Top = height, Bottom = -pressAKey.DrawHeight };
}
protected override void OnFocus(InputState state)
@ -250,7 +279,7 @@ namespace osu.Game.Overlays.KeyBinding
AutoSizeEasing = Easing.OutQuint;
pressAKey.FadeIn(300, Easing.OutQuint);
pressAKey.Padding = new MarginPadding();
pressAKey.Padding = new MarginPadding { Top = height };
updateBindTarget();
base.OnFocus(state);

View File

@ -116,6 +116,7 @@ namespace osu.Game.Overlays.Mods
}
private Mod mod;
private readonly Container scaleContainer;
public Mod Mod
{
@ -149,14 +150,26 @@ namespace osu.Game.Overlays.Mods
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
switch (args.Button)
scaleContainer.ScaleTo(0.9f, 800, Easing.Out);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
scaleContainer.ScaleTo(1, 500, Easing.OutElastic);
// only trigger the event if we are inside the area of the button
if (Contains(ToScreenSpace(state.Mouse.Position - Position)))
{
case MouseButton.Left:
SelectNext(1);
break;
case MouseButton.Right:
SelectNext(-1);
break;
switch (args.Button)
{
case MouseButton.Left:
SelectNext(1);
break;
case MouseButton.Right:
SelectNext(-1);
break;
}
}
return true;
@ -176,7 +189,8 @@ namespace osu.Game.Overlays.Mods
start = Mods.Length - 1;
for (int i = start; i < Mods.Length && i >= 0; i += direction)
if (SelectAt(i)) return;
if (SelectAt(i))
return;
Deselect();
}
@ -242,8 +256,14 @@ namespace osu.Game.Overlays.Mods
Anchor = Anchor.TopCentre,
Children = new Drawable[]
{
iconsContainer = new Container<ModIcon>
scaleContainer = new Container
{
Child = iconsContainer = new Container<ModIcon>
{
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
},
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,

View File

@ -4,7 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
@ -16,7 +17,8 @@ namespace osu.Game.Overlays.Music
{
public class PlaylistList : CompositeDrawable
{
public Action<BeatmapSetInfo> OnSelect;
public Action<BeatmapSetInfo> Selected;
public Action<BeatmapSetInfo, int> OrderChanged;
private readonly ItemsScrollContainer items;
@ -25,7 +27,8 @@ namespace osu.Game.Overlays.Music
InternalChild = items = new ItemsScrollContainer
{
RelativeSizeAxes = Axes.Both,
OnSelect = set => OnSelect?.Invoke(set)
Selected = set => Selected?.Invoke(set),
OrderChanged = (s, i) => OrderChanged?.Invoke(s, i)
};
}
@ -35,34 +38,20 @@ namespace osu.Game.Overlays.Music
set { base.Padding = value; }
}
public IEnumerable<BeatmapSetInfo> BeatmapSets
{
get { return items.Sets; }
set { items.Sets = value; }
}
public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet;
public BeatmapSetInfo NextSet => items.NextSet;
public BeatmapSetInfo PreviousSet => items.PreviousSet;
public BeatmapSetInfo SelectedSet
{
get { return items.SelectedSet; }
set { items.SelectedSet = value; }
}
public void AddBeatmapSet(BeatmapSetInfo beatmapSet) => items.AddBeatmapSet(beatmapSet);
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => items.RemoveBeatmapSet(beatmapSet);
public void Filter(string searchTerm) => items.SearchTerm = searchTerm;
private class ItemsScrollContainer : OsuScrollContainer
{
public Action<BeatmapSetInfo> OnSelect;
public Action<BeatmapSetInfo> Selected;
public Action<BeatmapSetInfo, int> OrderChanged;
private readonly SearchContainer search;
private readonly FillFlowContainer<PlaylistItem> items;
private readonly IBindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
public ItemsScrollContainer()
{
Children = new Drawable[]
@ -83,14 +72,36 @@ namespace osu.Game.Overlays.Music
};
}
public IEnumerable<BeatmapSetInfo> Sets
[BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps, OsuGameBase osuGame)
{
get { return items.Select(x => x.BeatmapSetInfo).ToList(); }
set
{
items.Clear();
value.ForEach(AddBeatmapSet);
}
beatmaps.GetAllUsableBeatmapSets().ForEach(addBeatmapSet);
beatmaps.ItemAdded += addBeatmapSet;
beatmaps.ItemRemoved += removeBeatmapSet;
beatmapBacking.BindTo(osuGame.Beatmap);
beatmapBacking.ValueChanged += _ => updateSelectedSet();
}
private void addBeatmapSet(BeatmapSetInfo obj)
{
var newItem = new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) };
items.Add(newItem);
items.SetLayoutPosition(newItem, items.Count - 1);
}
private void removeBeatmapSet(BeatmapSetInfo obj)
{
var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID);
if (itemToRemove != null)
items.Remove(itemToRemove);
}
private void updateSelectedSet()
{
foreach (PlaylistItem s in items.Children)
s.Selected = s.BeatmapSetInfo.ID == beatmapBacking.Value.BeatmapSetInfo.ID;
}
public string SearchTerm
@ -99,34 +110,7 @@ namespace osu.Game.Overlays.Music
set { search.SearchTerm = value; }
}
public void AddBeatmapSet(BeatmapSetInfo beatmapSet)
{
var newItem = new PlaylistItem(beatmapSet) { OnSelect = set => OnSelect?.Invoke(set) };
items.Add(newItem);
items.SetLayoutPosition(newItem, items.Count);
}
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet)
{
var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == beatmapSet.ID);
if (itemToRemove != null)
items.Remove(itemToRemove);
}
public BeatmapSetInfo SelectedSet
{
get { return items.FirstOrDefault(i => i.Selected)?.BeatmapSetInfo; }
set
{
foreach (PlaylistItem s in items.Children)
s.Selected = s.BeatmapSetInfo.ID == value?.ID;
}
}
public BeatmapSetInfo FirstVisibleSet => items.FirstOrDefault(i => i.MatchingFilter)?.BeatmapSetInfo;
public BeatmapSetInfo NextSet => (items.SkipWhile(i => !i.Selected).Skip(1).FirstOrDefault() ?? items.FirstOrDefault())?.BeatmapSetInfo;
public BeatmapSetInfo PreviousSet => (items.TakeWhile(i => !i.Selected).LastOrDefault() ?? items.LastOrDefault())?.BeatmapSetInfo;
private Vector2 nativeDragPosition;
private PlaylistItem draggedItem;
@ -227,6 +211,7 @@ namespace osu.Game.Overlays.Music
}
items.SetLayoutPosition(draggedItem, dstIndex);
OrderChanged?.Invoke(draggedItem.BeatmapSetInfo, dstIndex);
}
private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2018 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;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
@ -19,18 +19,16 @@ namespace osu.Game.Overlays.Music
public class PlaylistOverlay : OverlayContainer
{
private const float transition_duration = 600;
private const float playlist_height = 510;
public Action<BeatmapSetInfo, int> OrderChanged;
private BeatmapManager beatmaps;
private FilterControl filter;
private PlaylistList list;
private BeatmapManager beatmaps;
private readonly Bindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
public IEnumerable<BeatmapSetInfo> BeatmapSets => list.BeatmapSets;
[BackgroundDependencyLoader]
private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours)
{
@ -60,7 +58,8 @@ namespace osu.Game.Overlays.Music
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 },
OnSelect = itemSelected,
Selected = itemSelected,
OrderChanged = (s, i) => OrderChanged?.Invoke(s, i)
},
filter = new FilterControl
{
@ -74,30 +73,16 @@ namespace osu.Game.Overlays.Music
},
};
beatmaps.ItemAdded += handleBeatmapAdded;
beatmaps.ItemRemoved += handleBeatmapRemoved;
list.BeatmapSets = beatmaps.GetAllUsableBeatmapSets();
beatmapBacking.BindTo(game.Beatmap);
filter.Search.OnCommit = (sender, newText) =>
{
var beatmap = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault();
if (beatmap != null) playSpecified(beatmap);
BeatmapInfo beatmap = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault();
if (beatmap != null)
beatmapBacking.Value = beatmaps.GetWorkingBeatmap(beatmap);
};
}
protected override void LoadComplete()
{
base.LoadComplete();
beatmapBacking.ValueChanged += b => list.SelectedSet = b?.BeatmapSetInfo;
beatmapBacking.TriggerChange();
}
private void handleBeatmapAdded(BeatmapSetInfo setInfo) => Schedule(() => list.AddBeatmapSet(setInfo));
private void handleBeatmapRemoved(BeatmapSetInfo setInfo) => Schedule(() => list.RemoveBeatmapSet(setInfo));
protected override void PopIn()
{
filter.Search.HoldFocus = true;
@ -123,49 +108,7 @@ namespace osu.Game.Overlays.Music
return;
}
playSpecified(set.Beatmaps.First());
}
public void PlayPrevious()
{
var playable = list.PreviousSet;
if (playable != null)
{
playSpecified(playable.Beatmaps.First());
list.SelectedSet = playable;
}
}
public void PlayNext()
{
var playable = list.NextSet;
if (playable != null)
{
playSpecified(playable.Beatmaps.First());
list.SelectedSet = playable;
}
}
private void playSpecified(BeatmapInfo info)
{
beatmapBacking.Value = beatmaps.GetWorkingBeatmap(info, beatmapBacking);
var track = beatmapBacking.Value.Track;
track.Restart();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (beatmaps != null)
{
beatmaps.ItemAdded -= handleBeatmapAdded;
beatmaps.ItemRemoved -= handleBeatmapRemoved;
}
beatmapBacking.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First());
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
@ -50,7 +51,10 @@ namespace osu.Game.Overlays
private LocalisationEngine localisation;
private BeatmapManager beatmaps;
private readonly Bindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
private List<BeatmapSetInfo> beatmapSets;
private BeatmapSetInfo currentSet;
private Container dragContainer;
private Container playerContainer;
@ -93,8 +97,9 @@ namespace osu.Game.Overlays
}
[BackgroundDependencyLoader]
private void load(OsuGameBase game, OsuColour colours, LocalisationEngine localisation)
private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours, LocalisationEngine localisation)
{
this.beatmaps = beatmaps;
this.localisation = localisation;
Children = new Drawable[]
@ -111,6 +116,7 @@ namespace osu.Game.Overlays
{
RelativeSizeAxes = Axes.X,
Y = player_height + 10,
OrderChanged = playlistOrderChanged
},
playerContainer = new Container
{
@ -185,7 +191,7 @@ namespace osu.Game.Overlays
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Action = next,
Action = () => next(),
Icon = FontAwesome.fa_step_forward,
},
}
@ -214,11 +220,24 @@ namespace osu.Game.Overlays
}
};
beatmapSets = beatmaps.GetAllUsableBeatmapSets();
beatmaps.ItemAdded += handleBeatmapAdded;
beatmaps.ItemRemoved += handleBeatmapRemoved;
beatmapBacking.BindTo(game.Beatmap);
playlist.StateChanged += s => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint);
}
private void playlistOrderChanged(BeatmapSetInfo beatmapSetInfo, int index)
{
beatmapSets.Remove(beatmapSetInfo);
beatmapSets.Insert(index, beatmapSetInfo);
}
private void handleBeatmapAdded(BeatmapSetInfo obj) => beatmapSets.Add(obj);
private void handleBeatmapRemoved(BeatmapSetInfo obj) => beatmapSets.RemoveAll(s => s.ID == obj.ID);
protected override void LoadComplete()
{
beatmapBacking.ValueChanged += beatmapChanged;
@ -257,7 +276,7 @@ namespace osu.Game.Overlays
playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && playlist.BeatmapSets.Any())
if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && beatmapSets.Any())
next();
}
else
@ -271,7 +290,7 @@ namespace osu.Game.Overlays
if (track == null)
{
if (!beatmapBacking.Disabled)
playlist.PlayNext();
next(true);
return;
}
@ -284,13 +303,26 @@ namespace osu.Game.Overlays
private void prev()
{
queuedDirection = TransformDirection.Prev;
playlist.PlayPrevious();
var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault();
if (playable != null)
{
beatmapBacking.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmapBacking);
beatmapBacking.Value.Track.Restart();
}
}
private void next()
private void next(bool instant = false)
{
queuedDirection = TransformDirection.Next;
playlist.PlayNext();
if (!instant)
queuedDirection = TransformDirection.Next;
var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault();
if (playable != null)
{
beatmapBacking.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmapBacking);
beatmapBacking.Value.Track.Restart();
}
}
private WorkingBeatmap current;
@ -314,8 +346,8 @@ namespace osu.Game.Overlays
else
{
//figure out the best direction based on order in playlist.
var last = playlist.BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
var next = beatmap == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.ID != beatmap.BeatmapSetInfo?.ID).Count();
var last = beatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
var next = beatmap == null ? -1 : beatmapSets.TakeWhile(b => b.ID != beatmap.BeatmapSetInfo?.ID).Count();
direction = last > next ? TransformDirection.Prev : TransformDirection.Next;
}

View File

@ -100,6 +100,8 @@ namespace osu.Game.Overlays
public bool Adjust(GlobalAction action)
{
if (!IsLoaded) return false;
switch (action)
{
case GlobalAction.DecreaseVolume:

View File

@ -0,0 +1,41 @@
// Copyright (c) 2007-2018 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.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Difficulty
{
public abstract class DifficultyCalculator
{
protected readonly IBeatmap Beatmap;
protected readonly Mod[] Mods;
protected double TimeRate { get; private set; } = 1;
protected DifficultyCalculator(IBeatmap beatmap, Mod[] mods = null)
{
Beatmap = beatmap;
Mods = mods ?? new Mod[0];
ApplyMods(Mods);
}
protected virtual void ApplyMods(Mod[] mods)
{
var clock = new StopwatchClock();
mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock));
TimeRate = clock.Rate;
}
protected virtual void PreprocessHitObjects()
{
}
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
}
}

View File

@ -3,41 +3,43 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Scoring
namespace osu.Game.Rulesets.Difficulty
{
public abstract class PerformanceCalculator
{
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
}
public abstract class PerformanceCalculator<TObject> : PerformanceCalculator
where TObject : HitObject
{
private readonly Dictionary<string, double> attributes = new Dictionary<string, double>();
protected IDictionary<string, double> Attributes => attributes;
protected readonly Beatmap<TObject> Beatmap;
protected readonly IBeatmap Beatmap;
protected readonly Score Score;
protected PerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score)
protected double TimeRate { get; private set; } = 1;
protected PerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
{
Score = score;
var converter = CreateBeatmapConverter();
foreach (var mod in score.Mods.OfType<IApplicableToBeatmapConverter<TObject>>())
mod.ApplyToBeatmapConverter(converter);
Beatmap = converter.Convert(beatmap);
Beatmap = beatmap;
var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods);
diffCalc.Calculate(attributes);
ApplyMods(score.Mods);
}
protected abstract BeatmapConverter<TObject> CreateBeatmapConverter();
protected virtual void ApplyMods(Mod[] mods)
{
var clock = new StopwatchClock();
mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock));
TimeRate = clock.Rate;
}
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
}
}

View File

@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Edit
private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool;
protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap, true);
protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap);
protected abstract IReadOnlyList<ICompositionTool> CompositionTools { get; }

View File

@ -10,13 +10,12 @@ namespace osu.Game.Rulesets.Mods
/// Interface for a <see cref="Mod"/> that applies changes to a <see cref="BeatmapConverter{TObject}"/>.
/// </summary>
/// <typeparam name="TObject">The type of converted <see cref="HitObject"/>.</typeparam>
public interface IApplicableToBeatmapConverter<TObject> : IApplicableMod
where TObject : HitObject
public interface IApplicableToBeatmapConverter : IApplicableMod
{
/// <summary>
/// Applies this <see cref="Mod"/> to a <see cref="BeatmapConverter{TObject}"/>.
/// </summary>
/// <param name="beatmapConverter">The <see cref="BeatmapConverter{TObject}"/> to apply to.</param>
void ApplyToBeatmapConverter(BeatmapConverter<TObject> beatmapConverter);
void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter);
}
}

View File

@ -8,13 +8,12 @@ namespace osu.Game.Rulesets.Mods
/// <summary>
/// An interface for <see cref="Mod"/>s that can be applied to <see cref="HitObject"/>s.
/// </summary>
public interface IApplicableToHitObject<in TObject> : IApplicableMod
where TObject : HitObject
public interface IApplicableToHitObject : IApplicableMod
{
/// <summary>
/// Applies this <see cref="IApplicableToHitObject{TObject}"/> to a <see cref="HitObject"/>.
/// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> to apply to.</param>
void ApplyToHitObject(TObject hitObject);
void ApplyToHitObject(HitObject hitObject);
}
}

View File

@ -51,16 +51,10 @@ namespace osu.Game.Rulesets.Objects
private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY;
private HitWindows hitWindows;
/// <summary>
/// The hit windows for this <see cref="HitObject"/>.
/// </summary>
public HitWindows HitWindows
{
get => hitWindows ?? (hitWindows = new HitWindows(overallDifficulty));
protected set => hitWindows = value;
}
public HitWindows HitWindows { get; set; }
private readonly SortedList<HitObject> nestedHitObjects = new SortedList<HitObject>((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
@ -78,7 +72,11 @@ namespace osu.Game.Rulesets.Objects
nestedHitObjects.Clear();
CreateNestedHitObjects();
nestedHitObjects.ForEach(h => h.ApplyDefaults(controlPointInfo, difficulty));
nestedHitObjects.ForEach(h =>
{
h.HitWindows = HitWindows;
h.ApplyDefaults(controlPointInfo, difficulty);
});
}
protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
@ -89,8 +87,9 @@ namespace osu.Game.Rulesets.Objects
Kiai = effectPoint.KiaiMode;
SampleControlPoint = samplePoint;
overallDifficulty = difficulty.OverallDifficulty;
hitWindows = null;
if (HitWindows == null)
HitWindows = CreateHitWindows();
HitWindows?.SetDifficulty(difficulty.OverallDifficulty);
}
protected virtual void CreateNestedHitObjects()
@ -98,5 +97,14 @@ namespace osu.Game.Rulesets.Objects
}
protected void AddNested(HitObject hitObject) => nestedHitObjects.Add(hitObject);
/// <summary>
/// Creates the <see cref="HitWindows"/> for this <see cref="HitObject"/>.
/// This can be null to indicate that the <see cref="HitObject"/> has no <see cref="HitWindows"/>.
/// <para>
/// This will only be invoked if <see cref="HitWindows"/> hasn't been set externally (e.g. from a <see cref="BeatmapConverter"/>.
/// </para>
/// </summary>
protected virtual HitWindows CreateHitWindows() => new HitWindows();
}
}

View File

@ -63,10 +63,10 @@ namespace osu.Game.Rulesets.Objects
public bool AllowsOk;
/// <summary>
/// Constructs hit windows by fitting a parameter to a 2-part piecewise linear function for each hit window.
/// Sets hit windows with values that correspond to a difficulty parameter.
/// </summary>
/// <param name="difficulty">The parameter.</param>
public HitWindows(double difficulty)
public virtual void SetDifficulty(double difficulty)
{
Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);

View File

@ -13,5 +13,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public float X { get; set; }
public bool NewCombo { get; set; }
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Objects.Legacy.Mania
{
public class ConvertHitWindows : HitWindows
{
private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
{
{ HitResult.Perfect, (44.8, 38.8, 27.8) },
{ HitResult.Great, (128, 98, 68 ) },
{ HitResult.Good, (194, 164, 134) },
{ HitResult.Ok, (254, 224, 194) },
{ HitResult.Meh, (302, 272, 242) },
{ HitResult.Miss, (376, 346, 316) },
};
public override void SetDifficulty(double difficulty)
{
Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]);
Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
}
}
}

View File

@ -12,5 +12,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public double EndTime { get; set; }
public double Duration => EndTime - StartTime;
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
}
}

View File

@ -13,5 +13,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public float X { get; set; }
public bool NewCombo { get; set; }
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
}
}

View File

@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public double Duration => EndTime - StartTime;
public float X { get; set; }
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
}
}

View File

@ -18,5 +18,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public float Y => Position.Y;
public bool NewCombo { get; set; }
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Objects.Legacy.Osu
{
public class ConvertHitWindows : HitWindows
{
private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
{
{ HitResult.Great, (160, 100, 40) },
{ HitResult.Good, (280, 200, 120) },
{ HitResult.Meh, (400, 300, 200) },
{ HitResult.Miss, (400, 400, 400) },
};
public override void SetDifficulty(double difficulty)
{
Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
}
}
}

View File

@ -18,5 +18,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public float Y => Position.Y;
public bool NewCombo { get; set; }
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
}
}

View File

@ -20,5 +20,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public float X => Position.X;
public float Y => Position.Y;
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
}
}

View File

@ -11,5 +11,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
internal sealed class ConvertHit : HitObject, IHasCombo
{
public bool NewCombo { get; set; }
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Objects.Legacy.Taiko
{
public class ConvertHitWindows : HitWindows
{
private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
{
{ HitResult.Great, (100, 70, 40) },
{ HitResult.Good, (240, 160, 100) },
{ HitResult.Meh, (270, 190, 140) },
{ HitResult.Miss, (400, 400, 400) },
};
public override void SetDifficulty(double difficulty)
{
Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
}
}
}

View File

@ -11,5 +11,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasCombo
{
public bool NewCombo { get; set; }
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
}
}

View File

@ -13,5 +13,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
public double EndTime { get; set; }
public double Duration => EndTime - StartTime;
protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
}
}

View File

@ -99,11 +99,9 @@ namespace osu.Game.Rulesets.Objects
cumulativeLength.Add(l);
}
//TODO: Figure out if the following code is needed in some cases. Judging by the map
// "Transform" http://osu.ppy.sh/s/484689 it seems like we should _not_ be doing this.
// Lengthen slider curves that are too short compared to what's
// in the .osu file.
/*if (l < Length && calculatedPath.Count > 1)
if (l < Distance && calculatedPath.Count > 1)
{
Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2];
double d = diff.Length;
@ -111,9 +109,9 @@ namespace osu.Game.Rulesets.Objects
if (d <= 0)
return;
calculatedPath[calculatedPath.Count - 1] += diff * (float)((Length - l) / d);
cumulativeLength[calculatedPath.Count - 1] = Length;
}*/
calculatedPath[calculatedPath.Count - 1] += diff * (float)((Distance - l) / d);
cumulativeLength[calculatedPath.Count - 1] = Distance;
}
}
public void Calculate()

View File

@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Replays.Types
/// </summary>
/// <param name="legacyFrame">The <see cref="LegacyReplayFrame"/> to extract values from.</param>
/// <param name="beatmap">The beatmap.</param>
void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap);
void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap);
}
}

View File

@ -15,6 +15,7 @@ using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets
{
@ -22,8 +23,6 @@ namespace osu.Game.Rulesets
{
public readonly RulesetInfo RulesetInfo;
public virtual IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap) => new BeatmapStatistic[] { };
public IEnumerable<Mod> GetAllMods() => Enum.GetValues(typeof(ModType)).Cast<ModType>()
// Confine all mods of each mod type into a single IEnumerable<Mod>
.SelectMany(GetModsFor)
@ -52,14 +51,17 @@ namespace osu.Game.Rulesets
/// Attempt to create a hit renderer for a beatmap
/// </summary>
/// <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 RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset);
public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap);
public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null);
public abstract IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap);
public virtual PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => null;
public virtual IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => null;
public abstract DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null);
public virtual PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => null;
public virtual HitObjectComposer CreateHitObjectComposer() => null;
@ -114,7 +116,8 @@ namespace osu.Game.Rulesets
Name = Description,
ShortName = ShortName,
InstantiationInfo = GetType().AssemblyQualifiedName,
ID = LegacyID
ID = LegacyID,
Available = true
};
}
}

View File

@ -112,7 +112,7 @@ namespace osu.Game.Rulesets
try
{
var assembly = Assembly.LoadFrom(file);
loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsSubclassOf(typeof(Ruleset)));
loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset)));
}
catch (Exception)
{

View File

@ -0,0 +1,26 @@
// 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.Beatmaps;
namespace osu.Game.Rulesets.Scoring.Legacy
{
/// <summary>
/// A <see cref="LegacyScoreParser"/> which retrieves the applicable <see cref="Beatmap"/> and <see cref="Ruleset"/>
/// for the score from the database.
/// </summary>
public class DatabasedLegacyScoreParser : LegacyScoreParser
{
private readonly RulesetStore rulesets;
private readonly BeatmapManager beatmaps;
public DatabasedLegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps)
{
this.rulesets = rulesets;
this.beatmaps = beatmaps;
}
protected override Ruleset GetRuleset(int rulesetId) => rulesets.GetRuleset(rulesetId).CreateInstance();
protected override WorkingBeatmap GetBeatmap(string md5Hash) => beatmaps.GetWorkingBeatmap(beatmaps.QueryBeatmap(b => b.MD5Hash == md5Hash));
}
}

View File

@ -14,18 +14,9 @@ using System.Linq;
namespace osu.Game.Rulesets.Scoring.Legacy
{
public class LegacyScoreParser
public abstract class LegacyScoreParser
{
private readonly RulesetStore rulesets;
private readonly BeatmapManager beatmaps;
public LegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps)
{
this.rulesets = rulesets;
this.beatmaps = beatmaps;
}
private Beatmap currentBeatmap;
private IBeatmap currentBeatmap;
private Ruleset currentRuleset;
public Score Parse(Stream stream)
@ -34,33 +25,35 @@ namespace osu.Game.Rulesets.Scoring.Legacy
using (SerializationReader sr = new SerializationReader(stream))
{
score = new Score { Ruleset = rulesets.GetRuleset(sr.ReadByte()) };
currentRuleset = score.Ruleset.CreateInstance();
currentRuleset = GetRuleset(sr.ReadByte());
score = new Score { Ruleset = currentRuleset.RulesetInfo };
/* score.Pass = true;*/
var version = sr.ReadInt32();
/* score.FileChecksum = */
var beatmapHash = sr.ReadString();
score.Beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == beatmapHash);
currentBeatmap = beatmaps.GetWorkingBeatmap(score.Beatmap).Beatmap;
currentBeatmap = GetBeatmap(sr.ReadString()).Beatmap;
score.Beatmap = currentBeatmap.BeatmapInfo;
/* score.PlayerName = */
score.User = new User { Username = sr.ReadString() };
/* var localScoreChecksum = */
sr.ReadString();
/* score.Count300 = */
sr.ReadUInt16();
/* score.Count100 = */
sr.ReadUInt16();
/* score.Count50 = */
sr.ReadUInt16();
/* score.CountGeki = */
sr.ReadUInt16();
/* score.CountKatu = */
sr.ReadUInt16();
/* score.CountMiss = */
sr.ReadUInt16();
var count300 = sr.ReadUInt16();
var count100 = sr.ReadUInt16();
var count50 = sr.ReadUInt16();
var countGeki = sr.ReadUInt16();
var countKatu = sr.ReadUInt16();
var countMiss = sr.ReadUInt16();
score.Statistics[HitResult.Great] = count300;
score.Statistics[HitResult.Good] = count100;
score.Statistics[HitResult.Meh] = count50;
score.Statistics[HitResult.Perfect] = countGeki;
score.Statistics[HitResult.Ok] = countKatu;
score.Statistics[HitResult.Miss] = countMiss;
score.TotalScore = sr.ReadInt32();
score.MaxCombo = sr.ReadUInt16();
/* score.Perfect = */
@ -81,6 +74,34 @@ namespace osu.Game.Rulesets.Scoring.Legacy
/*OnlineId =*/
sr.ReadInt32();
switch (score.Ruleset.ID)
{
case 0:
{
int totalHits = count50 + count100 + count300 + countMiss;
score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + count300 * 300) / (totalHits * 300) : 1;
break;
}
case 1:
{
int totalHits = count50 + count100 + count300 + countMiss;
score.Accuracy = totalHits > 0 ? (double)(count100 * 150 + count300 * 300) / (totalHits * 300) : 1;
break;
}
case 2:
{
int totalHits = count50 + count100 + count300 + countMiss + countKatu;
score.Accuracy = totalHits > 0 ? (double)(count50 + count100 + count300 ) / totalHits : 1;
break;
}
case 3:
{
int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu;
score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + countKatu * 200 + (count300 + countGeki) * 300) / (totalHits * 300) : 1;
break;
}
}
using (var replayInStream = new MemoryStream(compressedReplay))
{
byte[] properties = new byte[5];
@ -150,5 +171,19 @@ namespace osu.Game.Rulesets.Scoring.Legacy
return frame;
}
/// <summary>
/// Retrieves the <see cref="Ruleset"/> for a specific id.
/// </summary>
/// <param name="rulesetId">The id.</param>
/// <returns>The <see cref="Ruleset"/>.</returns>
protected abstract Ruleset GetRuleset(int rulesetId);
/// <summary>
/// Retrieves the <see cref="WorkingBeatmap"/> corresponding to an MD5 hash.
/// </summary>
/// <param name="md5Hash">The MD5 hash.</param>
/// <returns>The <see cref="WorkingBeatmap"/>.</returns>
protected abstract WorkingBeatmap GetBeatmap(string md5Hash);
}
}

View File

@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Scoring
public Score ReadReplayFile(string replayFilename)
{
using (Stream s = storage.GetStream(Path.Combine(replay_folder, replayFilename)))
return new LegacyScoreParser(rulesets, beatmaps).Parse(s);
return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(s);
}
}
}

View File

@ -190,11 +190,6 @@ namespace osu.Game.Rulesets.UI
/// </summary>
protected readonly WorkingBeatmap WorkingBeatmap;
/// <summary>
/// Whether the specified beatmap is assumed to be specific to the current ruleset.
/// </summary>
public readonly bool IsForCurrentRuleset;
public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor<TObject>(this);
protected override Container<Drawable> Content => content;
@ -206,43 +201,18 @@ namespace osu.Game.Rulesets.UI
/// </summary>
/// <param name="ruleset">The ruleset being repesented.</param>
/// <param name="workingBeatmap">The beatmap to create the hit renderer for.</param>
/// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param>
protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap, bool isForCurrentRuleset)
protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap)
: base(ruleset)
{
Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap.");
WorkingBeatmap = workingBeatmap;
IsForCurrentRuleset = isForCurrentRuleset;
// ReSharper disable once PossibleNullReferenceException
Mods = workingBeatmap.Mods.Value;
RelativeSizeAxes = Axes.Both;
BeatmapConverter<TObject> converter = CreateBeatmapConverter();
BeatmapProcessor<TObject> processor = CreateBeatmapProcessor();
// Check if the beatmap can be converted
if (!converter.CanConvert(workingBeatmap.Beatmap))
throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the current ruleset (converter: {converter}).");
// Apply conversion adjustments before converting
foreach (var mod in Mods.OfType<IApplicableToBeatmapConverter<TObject>>())
mod.ApplyToBeatmapConverter(converter);
// Convert the beatmap
Beatmap = converter.Convert(workingBeatmap.Beatmap);
// Apply difficulty adjustments from mods before using Difficulty.
foreach (var mod in Mods.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty);
// Post-process the beatmap
processor.PostProcess(Beatmap);
// Apply defaults
foreach (var h in Beatmap.HitObjects)
h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
Beatmap = (Beatmap<TObject>)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo);
KeyBindingInputManager = CreateInputManager();
KeyBindingInputManager.RelativeSizeAxes = Axes.Both;
@ -277,10 +247,6 @@ namespace osu.Game.Rulesets.UI
if (mods == null)
return;
foreach (var mod in mods.OfType<IApplicableToHitObject<TObject>>())
foreach (var obj in Beatmap.HitObjects)
mod.ApplyToHitObject(obj);
foreach (var mod in mods.OfType<IApplicableToRulesetContainer<TObject>>())
mod.ApplyToRulesetContainer(this);
}
@ -324,13 +290,6 @@ namespace osu.Game.Rulesets.UI
Playfield.Size = GetAspectAdjustedSize() * PlayfieldArea;
}
/// <summary>
/// Creates a processor to perform post-processing operations
/// on HitObjects in converted Beatmaps.
/// </summary>
/// <returns>The Beatmap processor.</returns>
protected virtual BeatmapProcessor<TObject> CreateBeatmapProcessor() => new BeatmapProcessor<TObject>();
/// <summary>
/// Computes the size of the <see cref="Playfield"/> in relative coordinate space after aspect adjustments.
/// </summary>
@ -344,12 +303,6 @@ namespace osu.Game.Rulesets.UI
/// </summary>
protected virtual Vector2 PlayfieldArea => new Vector2(0.75f); // A sane default
/// <summary>
/// Creates a converter to convert Beatmap to a specific mode.
/// </summary>
/// <returns>The Beatmap converter.</returns>
protected abstract BeatmapConverter<TObject> CreateBeatmapConverter();
/// <summary>
/// Creates a DrawableHitObject from a HitObject.
/// </summary>
@ -377,9 +330,8 @@ namespace osu.Game.Rulesets.UI
/// </summary>
/// <param name="ruleset">The ruleset being repesented.</param>
/// <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 RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
: base(ruleset, beatmap, isForCurrentRuleset)
protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
}

View File

@ -30,8 +30,8 @@ namespace osu.Game.Rulesets.UI.Scrolling
/// <returns></returns>
protected readonly SortedList<MultiplierControlPoint> DefaultControlPoints = new SortedList<MultiplierControlPoint>(Comparer<MultiplierControlPoint>.Default);
protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
: base(ruleset, beatmap, isForCurrentRuleset)
protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}

View File

@ -0,0 +1,34 @@
// 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.Framework.Input;
using osu.Game.Overlays;
using OpenTK.Input;
namespace osu.Game.Screens.Menu
{
public class ExitConfirmOverlay : HoldToConfirmOverlay
{
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (args.Key == Key.Escape && !args.Repeat)
{
BeginConfirm();
return true;
}
return base.OnKeyDown(state, args);
}
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
{
if (args.Key == Key.Escape)
{
AbortConfirm();
return true;
}
return base.OnKeyUp(state, args);
}
}
}

View File

@ -14,7 +14,7 @@ using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Charts;
using osu.Game.Screens.Direct;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Multiplayer;
using osu.Game.Screens.Multi.Screens;
using osu.Game.Screens.Select;
using osu.Game.Screens.Tournament;
@ -39,6 +39,10 @@ namespace osu.Game.Screens.Menu
Children = new Drawable[]
{
new ExitConfirmOverlay
{
Action = Exit,
},
new ParallaxContainer
{
ParallaxAmount = 0.01f,

View File

@ -9,7 +9,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Online.Multiplayer;
namespace osu.Game.Screens.Multiplayer
namespace osu.Game.Screens.Multi.Components
{
public class DrawableGameType : CircularContainer, IHasTooltip
{

View File

@ -1,8 +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 OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
@ -17,8 +15,10 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Multiplayer;
using osu.Game.Users;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Screens.Multiplayer
namespace osu.Game.Screens.Multi.Components
{
public class DrawableRoom : OsuClickableContainer
{

View File

@ -1,14 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Online.Multiplayer;
using OpenTK;
namespace osu.Game.Screens.Multiplayer
namespace osu.Game.Screens.Multi.Components
{
public class ModeTypeInfo : Container
{

View File

@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.Linq;
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -11,8 +10,9 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Users;
using OpenTK;
namespace osu.Game.Screens.Multiplayer
namespace osu.Game.Screens.Multi.Components
{
public class ParticipantInfo : Container
{

View File

@ -2,8 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
@ -20,8 +18,10 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Multiplayer;
using osu.Game.Users;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Screens.Multiplayer
namespace osu.Game.Screens.Multi.Components
{
public class RoomInspector : Container
{

View File

@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
namespace osu.Game.Screens.Multiplayer
namespace osu.Game.Screens.Multi.Screens
{
public class Lobby : ScreenWhiteBox
{

View File

@ -3,14 +3,14 @@
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play;
using OpenTK.Graphics;
using osu.Game.Screens.Select;
using osu.Framework.Graphics;
using OpenTK.Graphics;
namespace osu.Game.Screens.Multiplayer
namespace osu.Game.Screens.Multi.Screens
{
public class Match : ScreenWhiteBox
{

View File

@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
namespace osu.Game.Screens.Multiplayer
namespace osu.Game.Screens.Multi.Screens
{
public class MatchCreate : ScreenWhiteBox
{

View File

@ -1,50 +1,19 @@
// 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.Framework.Allocation;
using System;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
using OpenTK.Graphics;
using osu.Game.Overlays;
namespace osu.Game.Screens.Play
{
public class HotkeyRetryOverlay : Container, IKeyBindingHandler<GlobalAction>
public class HotkeyRetryOverlay : HoldToConfirmOverlay, IKeyBindingHandler<GlobalAction>
{
public Action Action;
private Box overlay;
private const int activate_delay = 400;
private const int fadeout_delay = 200;
private bool fired;
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.Both;
AlwaysPresent = true;
Children = new Drawable[]
{
overlay = new Box
{
Alpha = 0,
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
}
};
}
public bool OnPressed(GlobalAction action)
{
if (action != GlobalAction.QuickRetry) return false;
overlay.FadeIn(activate_delay, Easing.Out);
BeginConfirm();
return true;
}
@ -52,18 +21,8 @@ namespace osu.Game.Screens.Play
{
if (action != GlobalAction.QuickRetry) return false;
overlay.FadeOut(fadeout_delay, Easing.Out);
AbortConfirm();
return true;
}
protected override void Update()
{
base.Update();
if (!fired && overlay.Alpha == 1)
{
fired = true;
Action?.Invoke();
}
}
}
}

View File

@ -93,7 +93,7 @@ namespace osu.Game.Screens.Play
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
Beatmap beatmap;
IBeatmap beatmap;
try
{
@ -107,7 +107,7 @@ namespace osu.Game.Screens.Play
try
{
RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working, ruleset.ID == beatmap.BeatmapInfo.Ruleset.ID);
RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working);
}
catch (BeatmapInvalidForRulesetException)
{
@ -115,7 +115,7 @@ namespace osu.Game.Screens.Play
// let's try again forcing the beatmap's ruleset.
ruleset = beatmap.BeatmapInfo.Ruleset;
rulesetInstance = ruleset.CreateInstance();
RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap, true);
RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap);
}
if (!RulesetContainer.Objects.Any())
@ -162,7 +162,7 @@ namespace osu.Game.Screens.Play
hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused;
},
OnResume = () => hudOverlay.KeyCounter.IsCounting = true,
Children = new Drawable[]
Children = new[]
{
storyboardContainer = new Container
{
@ -174,12 +174,12 @@ namespace osu.Game.Screens.Play
RelativeSizeAxes = Axes.Both,
Child = RulesetContainer
},
new SkipOverlay(firstObjectTime)
new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, scoreProcessor)
{
Clock = Clock, // skip button doesn't want to use the audio clock directly
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ProcessCustomClock = false,
AdjustableClock = adjustableClock,
FramedClock = offsetClock,
Breaks = beatmap.Breaks
},
hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, working, offsetClock, adjustableClock)
{
@ -188,13 +188,14 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, scoreProcessor)
RulesetContainer.Cursor?.CreateProxy() ?? new Container(),
new SkipOverlay(firstObjectTime)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Clock = Clock, // skip button doesn't want to use the audio clock directly
ProcessCustomClock = false,
Breaks = beatmap.Breaks
}
AdjustableClock = adjustableClock,
FramedClock = offsetClock,
},
}
},
failOverlay = new FailOverlay

View File

@ -85,11 +85,13 @@ namespace osu.Game.Screens.Play
if (currentSecond != previousSecond && songCurrentTime < songLength)
{
timeCurrent.Text = TimeSpan.FromSeconds(currentSecond).ToString(songCurrentTime < 0 ? @"\-m\:ss" : @"m\:ss");
timeLeft.Text = TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime).ToString(@"\-m\:ss");
timeCurrent.Text = formatTime(TimeSpan.FromSeconds(currentSecond));
timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime));
previousSecond = currentSecond;
}
}
private string formatTime(TimeSpan timeSpan) => $"{(timeSpan < TimeSpan.Zero ? "-" : "")}{timeSpan.Duration().TotalMinutes:N0}:{timeSpan.Duration().Seconds:D2}";
}
}

View File

@ -4,9 +4,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@ -21,6 +23,8 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
using osu.Game.Rulesets;
using osu.Game.Rulesets.UI;
namespace osu.Game.Screens.Select
{
@ -28,6 +32,8 @@ namespace osu.Game.Screens.Select
{
private static readonly Vector2 wedged_container_shear = new Vector2(0.15f, 0);
private readonly IBindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
protected BufferedWedgeInfo Info;
public BeatmapInfoWedge()
@ -46,6 +52,14 @@ namespace osu.Game.Screens.Select
};
}
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] OsuGame osuGame)
{
if (osuGame != null)
ruleset.BindTo(osuGame.Ruleset);
ruleset.ValueChanged += updateRuleset;
}
protected override bool BlockPassThroughMouse => false;
protected override void PopIn()
@ -62,19 +76,39 @@ namespace osu.Game.Screens.Select
this.FadeOut(500, Easing.In);
}
private WorkingBeatmap beatmap;
public void UpdateBeatmap(WorkingBeatmap beatmap)
{
LoadComponentAsync(new BufferedWedgeInfo(beatmap)
{
Shear = -Shear,
Depth = Info?.Depth + 1 ?? 0,
}, newInfo =>
this.beatmap = beatmap;
loadBeatmap();
}
private void updateRuleset(RulesetInfo ruleset) => loadBeatmap();
private void loadBeatmap()
{
void updateState()
{
State = beatmap == null ? Visibility.Hidden : Visibility.Visible;
Info?.FadeOut(250);
Info?.Expire();
}
if (beatmap == null)
{
updateState();
return;
}
LoadComponentAsync(new BufferedWedgeInfo(beatmap, ruleset.Value)
{
Shear = -Shear,
Depth = Info?.Depth + 1 ?? 0,
}, newInfo =>
{
updateState();
Add(Info = newInfo);
});
}
@ -90,9 +124,13 @@ namespace osu.Game.Screens.Select
private UnicodeBindableString titleBinding;
private UnicodeBindableString artistBinding;
public BufferedWedgeInfo(WorkingBeatmap working)
private readonly RulesetInfo ruleset;
public BufferedWedgeInfo(WorkingBeatmap working, RulesetInfo userRuleset)
{
this.working = working;
ruleset = userRuleset ?? working.BeatmapInfo.Ruleset;
}
[BackgroundDependencyLoader]
@ -211,11 +249,10 @@ namespace osu.Game.Screens.Select
private InfoLabel[] getInfoLabels()
{
var beatmap = working.Beatmap;
var info = working.BeatmapInfo;
List<InfoLabel> labels = new List<InfoLabel>();
if (beatmap?.HitObjects?.Count > 0)
if (beatmap?.HitObjects?.Any() == true)
{
HitObject lastObject = beatmap.HitObjects.LastOrDefault();
double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0;
@ -224,7 +261,7 @@ namespace osu.Game.Screens.Select
{
Name = "Length",
Icon = FontAwesome.fa_clock_o,
Content = beatmap.HitObjects.Count == 0 ? "-" : TimeSpan.FromMilliseconds(endTime - beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"),
Content = TimeSpan.FromMilliseconds(endTime - beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"),
}));
labels.Add(new InfoLabel(new BeatmapStatistic
@ -234,14 +271,26 @@ namespace osu.Game.Screens.Select
Content = getBPMRange(beatmap),
}));
//get statistics from the current ruleset.
labels.AddRange(info.Ruleset.CreateInstance().GetBeatmapStatistics(working).Select(s => new InfoLabel(s)));
IBeatmap playableBeatmap;
try
{
// Try to get the beatmap with the user's ruleset
playableBeatmap = working.GetPlayableBeatmap(ruleset);
}
catch (BeatmapInvalidForRulesetException)
{
// Can't be converted to the user's ruleset, so use the beatmap's own ruleset
playableBeatmap = working.GetPlayableBeatmap(working.BeatmapInfo.Ruleset);
}
labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)));
}
return labels.ToArray();
}
private string getBPMRange(Beatmap beatmap)
private string getBPMRange(IBeatmap beatmap)
{
double bpmMax = beatmap.ControlPointInfo.BPMMaximum;
double bpmMin = beatmap.ControlPointInfo.BPMMinimum;

View File

@ -10,12 +10,14 @@ using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Tests.Beatmaps
{
[TestFixture]
public abstract class BeatmapConversionTest<TConvertValue>
public abstract class BeatmapConversionTest<TRuleset, TConvertValue>
where TRuleset : Ruleset, new()
where TConvertValue : IEquatable<TConvertValue>
{
private const string resource_namespace = "Testing.Beatmaps";
@ -79,6 +81,9 @@ namespace osu.Game.Tests.Beatmaps
{
var beatmap = getBeatmap(name);
var rulesetInstance = new TRuleset();
beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo();
var result = new ConvertResult();
var converter = CreateConverter(beatmap);
@ -92,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps
result.Mappings.Add(mapping);
};
converter.Convert(beatmap);
converter.Convert();
return result;
}
@ -107,7 +112,7 @@ namespace osu.Game.Tests.Beatmaps
}
}
private Beatmap getBeatmap(string name)
private IBeatmap getBeatmap(string name)
{
using (var resStream = openResource($"{resource_namespace}.{name}.osu"))
using (var stream = new StreamReader(resStream))
@ -125,7 +130,7 @@ namespace osu.Game.Tests.Beatmaps
}
protected abstract IEnumerable<TConvertValue> CreateConvertValue(HitObject hitObject);
protected abstract IBeatmapConverter CreateConverter(Beatmap beatmap);
protected abstract IBeatmapConverter CreateConverter(IBeatmap beatmap);
private class ConvertMapping
{

View File

@ -12,8 +12,14 @@ namespace osu.Game.Tests.Beatmaps
public class TestBeatmap : Beatmap
{
public TestBeatmap(RulesetInfo ruleset)
: base(createTestBeatmap())
{
var baseBeatmap = createTestBeatmap();
BeatmapInfo = baseBeatmap.BeatmapInfo;
ControlPointInfo = baseBeatmap.ControlPointInfo;
Breaks = baseBeatmap.Breaks;
HitObjects = baseBeatmap.HitObjects;
BeatmapInfo.Ruleset = ruleset;
}

View File

@ -17,14 +17,14 @@ namespace osu.Game.Tests.Beatmaps
{
}
public TestWorkingBeatmap(Beatmap beatmap)
public TestWorkingBeatmap(IBeatmap beatmap)
: base(beatmap.BeatmapInfo)
{
this.beatmap = beatmap;
}
private readonly Beatmap beatmap;
protected override Beatmap GetBeatmap() => beatmap;
private readonly IBeatmap beatmap;
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null;
protected override Track GetTrack()

View File

@ -259,9 +259,9 @@ namespace osu.Game.Tests.Visual
private readonly OsuSpriteText text;
private readonly Score score;
private readonly Beatmap beatmap;
private readonly IBeatmap beatmap;
public PerformanceDisplay(Score score, Beatmap beatmap)
public PerformanceDisplay(Score score, IBeatmap beatmap)
{
this.score = score;
this.beatmap = beatmap;

View File

@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual
}
}
protected virtual Beatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo);
protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo);
private Player loadPlayerFor(RulesetInfo ri) => loadPlayerFor(ri.CreateInstance());