1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-06 09:07:25 +08:00

Merge branch 'master' into update-changelog-notification

This commit is contained in:
Dean Herbert 2017-08-23 00:12:59 +09:00 committed by GitHub
commit 630d67405f
35 changed files with 337 additions and 246 deletions

@ -1 +1 @@
Subproject commit f1527e5456cd228ddfb68cf6d56eb5d28dc360bf Subproject commit 74f644bad039606e242d8782014d8c249a38b6a3

View File

@ -115,18 +115,18 @@ namespace osu.Desktop.Tests.Visual
Assert.AreEqual(1, speedAdjustments[3].ControlPoint.Multiplier); Assert.AreEqual(1, speedAdjustments[3].ControlPoint.Multiplier);
// Check insertion of hit objects // Check insertion of hit objects
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[0].Contains(hitObjects[0])); Assert.IsTrue(hitObjectContainer.SpeedAdjustments[4].Contains(hitObjects[0]));
Assert.IsTrue(speedAdjustments[0].Contains(hitObjects[1])); Assert.IsTrue(hitObjectContainer.SpeedAdjustments[3].Contains(hitObjects[1]));
Assert.IsTrue(speedAdjustments[1].Contains(hitObjects[2])); Assert.IsTrue(hitObjectContainer.SpeedAdjustments[2].Contains(hitObjects[2]));
Assert.IsTrue(speedAdjustments[2].Contains(hitObjects[3])); Assert.IsTrue(hitObjectContainer.SpeedAdjustments[1].Contains(hitObjects[3]));
Assert.IsTrue(speedAdjustments[3].Contains(hitObjects[4])); Assert.IsTrue(hitObjectContainer.SpeedAdjustments[0].Contains(hitObjects[4]));
Assert.IsTrue(speedAdjustments[3].Contains(hitObjects[5])); Assert.IsTrue(hitObjectContainer.SpeedAdjustments[0].Contains(hitObjects[5]));
hitObjectContainer.RemoveSpeedAdjustment(speedAdjustments[1]); hitObjectContainer.RemoveSpeedAdjustment(hitObjectContainer.SpeedAdjustments[3]);
// The hit object contained in this speed adjustment should be resorted into the previous one // The hit object contained in this speed adjustment should be resorted into the one occuring before it
Assert.IsTrue(speedAdjustments[0].Contains(hitObjects[2])); Assert.IsTrue(hitObjectContainer.SpeedAdjustments[3].Contains(hitObjects[1]));
} }
private class TestRulesetContainer : ScrollingRulesetContainer<TestPlayfield, TestHitObject, TestJudgement> private class TestRulesetContainer : ScrollingRulesetContainer<TestPlayfield, TestHitObject, TestJudgement>

View File

@ -28,12 +28,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private Pattern lastPattern = new Pattern(); private Pattern lastPattern = new Pattern();
private FastRandom random; private FastRandom random;
private Beatmap beatmap; private Beatmap beatmap;
private bool isForCurrentRuleset;
protected override Beatmap<ManiaHitObject> ConvertBeatmap(Beatmap original, bool isForCurrentRuleset) private readonly int availableColumns;
private readonly bool isForCurrentRuleset;
public ManiaBeatmapConverter(bool isForCurrentRuleset, int availableColumns)
{ {
this.isForCurrentRuleset = isForCurrentRuleset; if (availableColumns <= 0) throw new ArgumentOutOfRangeException(nameof(availableColumns));
this.isForCurrentRuleset = isForCurrentRuleset;
this.availableColumns = availableColumns;
}
protected override Beatmap<ManiaHitObject> ConvertBeatmap(Beatmap original)
{
beatmap = original; beatmap = original;
BeatmapDifficulty difficulty = original.BeatmapInfo.Difficulty; BeatmapDifficulty difficulty = original.BeatmapInfo.Difficulty;
@ -41,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate); int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
random = new FastRandom(seed); random = new FastRandom(seed);
return base.ConvertBeatmap(original, isForCurrentRuleset); return base.ConvertBeatmap(original);
} }
protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, Beatmap beatmap) protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, Beatmap beatmap)
@ -89,7 +97,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// <returns>The hit objects generated.</returns> /// <returns>The hit objects generated.</returns>
private IEnumerable<ManiaHitObject> generateSpecific(HitObject original) private IEnumerable<ManiaHitObject> generateSpecific(HitObject original)
{ {
var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern); var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, availableColumns, lastPattern);
Pattern newPattern = generator.Generate(); Pattern newPattern = generator.Generate();
lastPattern = newPattern; lastPattern = newPattern;
@ -113,14 +121,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
Patterns.PatternGenerator conversion = null; Patterns.PatternGenerator conversion = null;
if (distanceData != null) if (distanceData != null)
conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern); conversion = new DistanceObjectPatternGenerator(random, original, beatmap, availableColumns, lastPattern);
else if (endTimeData != null) else if (endTimeData != null)
conversion = new EndTimeObjectPatternGenerator(random, original, beatmap); conversion = new EndTimeObjectPatternGenerator(random, original, beatmap, availableColumns);
else if (positionData != null) else if (positionData != null)
{ {
computeDensity(original.StartTime); computeDensity(original.StartTime);
conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair); conversion = new HitObjectPatternGenerator(random, original, beatmap, availableColumns, lastPattern, lastTime, lastPosition, density, lastStair);
recordNote(original.StartTime, positionData.Position); recordNote(original.StartTime, positionData.Position);
} }
@ -142,8 +150,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// </summary> /// </summary>
private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator
{ {
public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern) public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern)
: base(random, hitObject, beatmap, previousPattern) : base(random, hitObject, beatmap, availableColumns, previousPattern)
{ {
} }

View File

@ -29,8 +29,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private PatternType convertType; private PatternType convertType;
public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern) public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern)
: base(random, hitObject, beatmap, previousPattern) : base(random, hitObject, beatmap, availableColumns, previousPattern)
{ {
convertType = PatternType.None; convertType = PatternType.None;
if (Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode) if (Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode)

View File

@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
private readonly double endTime; private readonly double endTime;
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap) public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns)
: base(random, hitObject, beatmap, new Pattern()) : base(random, hitObject, beatmap, availableColumns, new Pattern())
{ {
var endtimeData = HitObject as IHasEndTime; var endtimeData = HitObject as IHasEndTime;

View File

@ -20,9 +20,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private readonly PatternType convertType; private readonly PatternType convertType;
public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair) public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair)
: base(random, hitObject, beatmap, previousPattern) : base(random, hitObject, beatmap, availableColumns, previousPattern)
{ {
if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime));
if (density < 0) throw new ArgumentOutOfRangeException(nameof(density));
StairType = lastStair; StairType = lastStair;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);

View File

@ -25,11 +25,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary> /// </summary>
protected readonly FastRandom Random; protected readonly FastRandom Random;
protected PatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern) protected PatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern)
: base(hitObject, beatmap, previousPattern) : base(hitObject, beatmap, availableColumns, previousPattern)
{ {
Random = random; if (random == null) throw new ArgumentNullException(nameof(random));
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
if (availableColumns <= 0) throw new ArgumentOutOfRangeException(nameof(availableColumns));
if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern));
Random = random;
RandomStart = AvailableColumns == 8 ? 1 : 0; RandomStart = AvailableColumns == 8 ? 1 : 0;
} }
@ -62,6 +66,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <returns>The amount of notes to be generated.</returns> /// <returns>The amount of notes to be generated.</returns>
protected int GetRandomNoteCount(double p2, double p3, double p4 = 0, double p5 = 0, double p6 = 0) protected int GetRandomNoteCount(double p2, double p3, double p4 = 0, double p5 = 0, double p6 = 0)
{ {
if (p2 < 0 || p2 > 1) throw new ArgumentOutOfRangeException(nameof(p2));
if (p3 < 0 || p3 > 1) throw new ArgumentOutOfRangeException(nameof(p3));
if (p4 < 0 || p4 > 1) throw new ArgumentOutOfRangeException(nameof(p4));
if (p5 < 0 || p5 > 1) throw new ArgumentOutOfRangeException(nameof(p5));
if (p6 < 0 || p6 > 1) throw new ArgumentOutOfRangeException(nameof(p6));
double val = Random.NextDouble(); double val = Random.NextDouble();
if (val >= 1 - p6) if (val >= 1 - p6)
return 6; return 6;

View File

@ -32,13 +32,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
/// </summary> /// </summary>
protected readonly Beatmap Beatmap; protected readonly Beatmap Beatmap;
protected PatternGenerator(HitObject hitObject, Beatmap beatmap, Pattern previousPattern) protected PatternGenerator(HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern)
{ {
PreviousPattern = previousPattern; if (hitObject == null) throw new ArgumentNullException(nameof(hitObject));
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
if (availableColumns <= 0) throw new ArgumentOutOfRangeException(nameof(availableColumns));
if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern));
HitObject = hitObject; HitObject = hitObject;
Beatmap = beatmap; Beatmap = beatmap;
AvailableColumns = availableColumns;
AvailableColumns = (int)Math.Round(beatmap.BeatmapInfo.Difficulty.CircleSize); PreviousPattern = previousPattern;
} }
/// <summary> /// <summary>

View File

@ -6,6 +6,7 @@ using osu.Game.Rulesets.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using System.Collections.Generic; using System.Collections.Generic;
using System;
namespace osu.Game.Rulesets.Mania namespace osu.Game.Rulesets.Mania
{ {
@ -21,6 +22,6 @@ namespace osu.Game.Rulesets.Mania
return 0; return 0;
} }
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter(); protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.Difficulty.CircleSize)));
} }
} }

View File

@ -125,6 +125,7 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < columnCount; i++) for (int i = 0; i < columnCount; i++)
{ {
var c = new Column(); var c = new Column();
c.Reversed.BindTo(Reversed);
c.VisibleTimeRange.BindTo(VisibleTimeRange); c.VisibleTimeRange.BindTo(VisibleTimeRange);
columns.Add(c); columns.Add(c);

View File

@ -33,9 +33,10 @@ namespace osu.Game.Rulesets.Mania.UI
public class ManiaRulesetContainer : ScrollingRulesetContainer<ManiaPlayfield, ManiaHitObject, ManiaJudgement> public class ManiaRulesetContainer : ScrollingRulesetContainer<ManiaPlayfield, ManiaHitObject, ManiaJudgement>
{ {
/// <summary> /// <summary>
/// Preferred column count. This will only have an effect during the initialization of the play field. /// The number of columns which the <see cref="ManiaPlayfield"/> should display, and which
/// the beatmap converter will attempt to convert beatmaps to use.
/// </summary> /// </summary>
public int PreferredColumns; private int availableColumns;
public IEnumerable<DrawableBarLine> BarLines; public IEnumerable<DrawableBarLine> BarLines;
@ -76,26 +77,35 @@ namespace osu.Game.Rulesets.Mania.UI
BarLines.ForEach(Playfield.Add); BarLines.ForEach(Playfield.Add);
} }
protected override void ApplyBeatmap() protected sealed override Playfield<ManiaHitObject, ManiaJudgement> CreatePlayfield() => new ManiaPlayfield(availableColumns)
{
base.ApplyBeatmap();
PreferredColumns = (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.Difficulty.CircleSize));
}
protected sealed override Playfield<ManiaHitObject, ManiaJudgement> CreatePlayfield() => new ManiaPlayfield(PreferredColumns)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
// Invert by default for now (should be moved to config/skin later)
Scale = new Vector2(1, -1)
}; };
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo); public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo);
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter(); protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter()
{
if (IsForCurrentRuleset)
availableColumns = (int)Math.Max(1, Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize));
else
{
float percentSliderOrSpinner = (float)WorkingBeatmap.Beatmap.HitObjects.Count(h => h is IHasEndTime) / WorkingBeatmap.Beatmap.HitObjects.Count;
if (percentSliderOrSpinner < 0.2)
availableColumns = 7;
else if (percentSliderOrSpinner < 0.3 || Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize) >= 5)
availableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 5 ? 7 : 6;
else if (percentSliderOrSpinner > 0.6)
availableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 4 ? 5 : 4;
else
availableColumns = Math.Max(4, Math.Min((int)Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) + 1, 7));
}
return new ManiaBeatmapConverter(IsForCurrentRuleset, availableColumns);
}
protected override DrawableHitObject<ManiaHitObject, ManiaJudgement> GetVisualRepresentation(ManiaHitObject h) protected override DrawableHitObject<ManiaHitObject, ManiaJudgement> GetVisualRepresentation(ManiaHitObject h)
{ {

View File

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

View File

@ -28,14 +28,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
FillMode = FillMode.Fit; FillMode = FillMode.Fit;
} }
protected override void LoadComplete()
{
base.LoadComplete();
// We need to set this here because RelativeSizeAxes won't/can't set our size by default with a different RelativeChildSize
Width *= Parent.RelativeChildSize.X;
}
protected override void CheckJudgement(bool userTriggered) protected override void CheckJudgement(bool userTriggered)
{ {
if (!userTriggered) if (!userTriggered)
@ -71,6 +63,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return UpdateJudgement(true); return UpdateJudgement(true);
} }
protected override void Update()
{
base.Update();
Size = BaseSize * Parent.RelativeChildSize;
}
protected override void UpdateState(ArmedState state) protected override void UpdateState(ArmedState state)
{ {
var circlePiece = MainPiece as CirclePiece; var circlePiece = MainPiece as CirclePiece;

View File

@ -199,6 +199,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
base.Update(); base.Update();
Size = BaseSize * Parent.RelativeChildSize;
// Make the swell stop at the hit target // Make the swell stop at the hit target
X = (float)Math.Max(Time.Current, HitObject.StartTime); X = (float)Math.Max(Time.Current, HitObject.StartTime);

View File

@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
protected readonly Vector2 BaseSize;
protected readonly TaikoPiece MainPiece; protected readonly TaikoPiece MainPiece;
public new TaikoHitType HitObject; public new TaikoHitType HitObject;
@ -29,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Origin = Anchor.Custom; Origin = Anchor.Custom;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Size = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
Add(MainPiece = CreateMainPiece()); Add(MainPiece = CreateMainPiece());
MainPiece.KiaiMode = HitObject.Kiai; MainPiece.KiaiMode = HitObject.Kiai;

View File

@ -135,6 +135,6 @@ namespace osu.Game.Rulesets.Taiko
return difficulty; return difficulty;
} }
protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter(); protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter(true);
} }
} }

View File

@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.UI
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter(); protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter(IsForCurrentRuleset);
public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);

View File

@ -30,11 +30,15 @@ namespace osu.Game.Beatmaps
public abstract class DifficultyCalculator<T> : DifficultyCalculator where T : HitObject public abstract class DifficultyCalculator<T> : DifficultyCalculator where T : HitObject
{ {
protected readonly Beatmap Beatmap;
protected List<T> Objects; protected List<T> Objects;
protected DifficultyCalculator(Beatmap beatmap) protected DifficultyCalculator(Beatmap beatmap)
{ {
Objects = CreateBeatmapConverter().Convert(beatmap, true).HitObjects; Beatmap = beatmap;
Objects = CreateBeatmapConverter().Convert(beatmap).HitObjects;
foreach (var h in Objects) foreach (var h in Objects)
h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty); h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty);

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Configuration; using osu.Game.Configuration;
using System; using System;
using System.Diagnostics;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
namespace osu.Game.Graphics.Cursor namespace osu.Game.Graphics.Cursor
@ -21,20 +22,31 @@ namespace osu.Game.Graphics.Cursor
private bool dragging; private bool dragging;
private bool startRotation;
protected override bool OnMouseMove(InputState state) protected override bool OnMouseMove(InputState state)
{ {
if (dragging) if (dragging)
{ {
Vector2 offset = state.Mouse.Position - state.Mouse.PositionMouseDown ?? state.Mouse.Delta; Debug.Assert(state.Mouse.PositionMouseDown != null);
float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f;
// Always rotate in the direction of least distance // don't start rotating until we're moved a minimum distance away from the mouse down location,
float diff = (degrees - ActiveCursor.Rotation) % 360; // else it can have an annoying effect.
if (diff < -180) diff += 360; startRotation |= Vector2.Distance(state.Mouse.Position, state.Mouse.PositionMouseDown.Value) > 30;
if (diff > 180) diff -= 360;
degrees = ActiveCursor.Rotation + diff;
ActiveCursor.RotateTo(degrees, 600, Easing.OutQuint); if (startRotation)
{
Vector2 offset = state.Mouse.Position - state.Mouse.PositionMouseDown.Value;
float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f;
// Always rotate in the direction of least distance
float diff = (degrees - ActiveCursor.Rotation) % 360;
if (diff < -180) diff += 360;
if (diff > 180) diff -= 360;
degrees = ActiveCursor.Rotation + diff;
ActiveCursor.RotateTo(degrees, 600, Easing.OutQuint);
}
} }
return base.OnMouseMove(state); return base.OnMouseMove(state);
@ -61,6 +73,7 @@ namespace osu.Game.Graphics.Cursor
if (!state.Mouse.HasMainButtonPressed) if (!state.Mouse.HasMainButtonPressed)
{ {
dragging = false; dragging = false;
startRotation = false;
((Cursor)ActiveCursor).AdditiveLayer.FadeOut(500, Easing.OutQuint); ((Cursor)ActiveCursor).AdditiveLayer.FadeOut(500, Easing.OutQuint);
ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), Easing.OutElasticHalf); ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), Easing.OutElasticHalf);

View File

@ -3,11 +3,11 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Threading; using osu.Framework.Threading;
using OpenTK; using OpenTK;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Input.Bindings;
namespace osu.Game.Graphics.UserInterface.Volume namespace osu.Game.Graphics.UserInterface.Volume
{ {
@ -64,15 +64,25 @@ namespace osu.Game.Graphics.UserInterface.Volume
volumeMeterMusic.Bindable.ValueChanged -= volumeChanged; volumeMeterMusic.Bindable.ValueChanged -= volumeChanged;
} }
public void Adjust(InputState state) public bool Adjust(GlobalAction action)
{ {
if (State == Visibility.Hidden) switch (action)
{ {
Show(); case GlobalAction.DecreaseVolume:
return; if (State == Visibility.Hidden)
Show();
else
volumeMeterMaster.Decrease();
return true;
case GlobalAction.IncreaseVolume:
if (State == Visibility.Hidden)
Show();
else
volumeMeterMaster.Increase();
return true;
} }
volumeMeterMaster.TriggerOnWheel(state); return false;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -3,32 +3,16 @@
using System; using System;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input.Bindings;
using OpenTK.Input; using osu.Game.Input.Bindings;
namespace osu.Game.Graphics.UserInterface.Volume namespace osu.Game.Graphics.UserInterface.Volume
{ {
internal class VolumeControlReceptor : Container internal class VolumeControlReceptor : Container, IKeyBindingHandler<GlobalAction>
{ {
public Action<InputState> ActionRequested; public Func<GlobalAction, bool> ActionRequested;
protected override bool OnWheel(InputState state) public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false;
{ public bool OnReleased(GlobalAction action) => false;
ActionRequested?.Invoke(state);
return true;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
switch (args.Key)
{
case Key.Up:
case Key.Down:
ActionRequested?.Invoke(state);
return true;
}
return base.OnKeyDown(state, args);
}
} }
} }

View File

@ -4,15 +4,16 @@
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
namespace osu.Game.Graphics.UserInterface.Volume namespace osu.Game.Graphics.UserInterface.Volume
{ {
internal class VolumeMeter : Container internal class VolumeMeter : Container, IKeyBindingHandler<GlobalAction>
{ {
private readonly Box meterFill; private readonly Box meterFill;
public BindableDouble Bindable { get; } = new BindableDouble(); public BindableDouble Bindable { get; } = new BindableDouble();
@ -76,12 +77,35 @@ namespace osu.Game.Graphics.UserInterface.Volume
} }
} }
protected override bool OnWheel(InputState state) public void Increase()
{ {
Volume += 0.05f * state.Mouse.WheelDelta; Volume += 0.05f;
return true; }
public void Decrease()
{
Volume -= 0.05f;
} }
private void updateFill() => meterFill.ScaleTo(new Vector2(1, (float)Volume), 300, Easing.OutQuint); private void updateFill() => meterFill.ScaleTo(new Vector2(1, (float)Volume), 300, Easing.OutQuint);
public bool OnPressed(GlobalAction action)
{
if (!IsHovered) return false;
switch (action)
{
case GlobalAction.DecreaseVolume:
Decrease();
return true;
case GlobalAction.IncreaseVolume:
Increase();
return true;
}
return false;
}
public bool OnReleased(GlobalAction action) => false;
} }
} }

View File

@ -26,7 +26,10 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings), new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings), new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleDirect), new KeyBinding(new[] { InputKey.Up }, GlobalAction.IncreaseVolume),
new KeyBinding(new[] { InputKey.MouseWheelUp }, GlobalAction.IncreaseVolume),
new KeyBinding(new[] { InputKey.Down }, GlobalAction.DecreaseVolume),
new KeyBinding(new[] { InputKey.MouseWheelDown }, GlobalAction.DecreaseVolume),
}; };
protected override IEnumerable<Drawable> KeyBindingInputQueue => protected override IEnumerable<Drawable> KeyBindingInputQueue =>
@ -47,5 +50,9 @@ namespace osu.Game.Input.Bindings
ToggleSettings, ToggleSettings,
[Description("Toggle osu!direct")] [Description("Toggle osu!direct")]
ToggleDirect, ToggleDirect,
[Description("Increase Volume")]
IncreaseVolume,
[Description("Decrease Volume")]
DecreaseVolume,
} }
} }

View File

@ -8,7 +8,6 @@ using osu.Game.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Framework.Input;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Graphics.UserInterface.Volume; using osu.Game.Graphics.UserInterface.Volume;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -160,7 +159,7 @@ namespace osu.Game
new VolumeControlReceptor new VolumeControlReceptor
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
ActionRequested = delegate(InputState state) { volume.Adjust(state); } ActionRequested = action => volume.Adjust(action)
}, },
mainContent = new Container mainContent = new Container
{ {

View File

@ -136,40 +136,49 @@ namespace osu.Game.Overlays.KeyBinding
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{ {
if (HasFocus) if (!HasFocus || !bindTarget.IsHovered)
{ return base.OnMouseDown(state, args);
if (bindTarget.IsHovered)
{
if (!AllowMainMouseButtons)
{
switch (args.Button)
{
case MouseButton.Left:
case MouseButton.Right:
return true;
}
}
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); if (!AllowMainMouseButtons)
return true; {
switch (args.Button)
{
case MouseButton.Left:
case MouseButton.Right:
return true;
} }
} }
return base.OnMouseDown(state, args); bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state));
return true;
} }
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{ {
if (HasFocus && !state.Mouse.Buttons.Any()) // don't do anything until the last button is released.
if (!HasFocus || state.Mouse.Buttons.Any())
return base.OnMouseUp(state, args);
if (bindTarget.IsHovered)
finalise();
else
updateBindTarget();
return true;
}
protected override bool OnWheel(InputState state)
{
if (HasFocus)
{ {
if (bindTarget.IsHovered) if (bindTarget.IsHovered)
{
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state));
finalise(); finalise();
else return true;
updateBindTarget(); }
return true;
} }
return base.OnMouseUp(state, args); return base.OnWheel(state);
} }
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
@ -196,13 +205,10 @@ namespace osu.Game.Overlays.KeyBinding
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
{ {
if (HasFocus) if (!HasFocus) return base.OnKeyUp(state, args);
{
finalise();
return true;
}
return base.OnKeyUp(state, args); finalise();
return true;
} }
private void finalise() private void finalise()

View File

@ -22,7 +22,7 @@ namespace osu.Game.Overlays
private ScrollContainer scrollContainer; private ScrollContainer scrollContainer;
private FlowContainer<NotificationSection> sections; private FlowContainer<NotificationSection> sections;
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Width = width; Width = width;
@ -72,6 +72,13 @@ namespace osu.Game.Overlays
private int runningDepth; private int runningDepth;
private void notificationClosed()
{
// hide ourselves if all notifications have been dismissed.
if (sections.Select(c => c.DisplayedCount).Sum() > 0)
State = Visibility.Hidden;
}
public void Post(Notification notification) public void Post(Notification notification)
{ {
Schedule(() => Schedule(() =>
@ -81,6 +88,8 @@ namespace osu.Game.Overlays
++runningDepth; ++runningDepth;
notification.Depth = notification.DisplayOnTop ? runningDepth : -runningDepth; notification.Depth = notification.DisplayOnTop ? runningDepth : -runningDepth;
notification.Closed += notificationClosed;
var hasCompletionTarget = notification as IHasCompletionTarget; var hasCompletionTarget = notification as IHasCompletionTarget;
if (hasCompletionTarget != null) if (hasCompletionTarget != null)
hasCompletionTarget.CompletionTarget = Post; hasCompletionTarget.CompletionTarget = Post;

View File

@ -19,9 +19,9 @@ namespace osu.Game.Overlays.Notifications
public abstract class Notification : Container public abstract class Notification : Container
{ {
/// <summary> /// <summary>
/// Use requested close. /// User requested close.
/// </summary> /// </summary>
public Action Closed; public event Action Closed;
/// <summary> /// <summary>
/// Run on user activating the notification. Return true to close. /// Run on user activating the notification. Return true to close.

View File

@ -24,6 +24,8 @@ namespace osu.Game.Overlays.Notifications
private FlowContainer<Notification> notifications; private FlowContainer<Notification> notifications;
public int DisplayedCount => notifications.Count;
public void Add(Notification notification) public void Add(Notification notification)
{ {
notifications.Add(notification); notifications.Add(notification);

View File

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

View File

@ -10,12 +10,10 @@ namespace osu.Game.Rulesets.Timing
/// </summary> /// </summary>
internal class LinearScrollingContainer : ScrollingContainer internal class LinearScrollingContainer : ScrollingContainer
{ {
private readonly Axes scrollingAxes;
private readonly MultiplierControlPoint controlPoint; private readonly MultiplierControlPoint controlPoint;
public LinearScrollingContainer(Axes scrollingAxes, MultiplierControlPoint controlPoint) public LinearScrollingContainer(MultiplierControlPoint controlPoint)
{ {
this.scrollingAxes = scrollingAxes;
this.controlPoint = controlPoint; this.controlPoint = controlPoint;
} }
@ -23,8 +21,8 @@ namespace osu.Game.Rulesets.Timing
{ {
base.Update(); base.Update();
if ((scrollingAxes & Axes.X) > 0) X = (float)(controlPoint.StartTime - Time.Current); if ((ScrollingAxes & Axes.X) > 0) X = (float)(controlPoint.StartTime - Time.Current);
if ((scrollingAxes & Axes.Y) > 0) Y = (float)(controlPoint.StartTime - Time.Current); if ((ScrollingAxes & Axes.Y) > 0) Y = (float)(controlPoint.StartTime - Time.Current);
} }
} }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq; using System.Linq;
using osu.Framework.Caching; using osu.Framework.Caching;
using osu.Framework.Configuration; using osu.Framework.Configuration;
@ -45,50 +46,34 @@ namespace osu.Game.Rulesets.Timing
RelativePositionAxes = Axes.Both; RelativePositionAxes = Axes.Both;
} }
public override void InvalidateFromChild(Invalidation invalidation) protected override int Compare(Drawable x, Drawable y)
{ {
// We only want to re-compute our size when a child's size or position has changed var hX = (DrawableHitObject)x;
if ((invalidation & Invalidation.RequiredParentSizeToFit) == 0) var hY = (DrawableHitObject)y;
{
base.InvalidateFromChild(invalidation);
return;
}
int result = hY.HitObject.StartTime.CompareTo(hX.HitObject.StartTime);
if (result != 0)
return result;
return base.Compare(y, x);
}
public override void Add(DrawableHitObject drawable)
{
durationBacking.Invalidate(); durationBacking.Invalidate();
base.Add(drawable);
base.InvalidateFromChild(invalidation);
} }
private double computeDuration() public override bool Remove(DrawableHitObject drawable)
{ {
if (!Children.Any()) durationBacking.Invalidate();
return 0; return base.Remove(drawable);
double baseDuration = Children.Max(c => (c.HitObject as IHasEndTime)?.EndTime ?? c.HitObject.StartTime) - ControlPoint.StartTime;
// If we have a singular hit object at the timing section's start time, let's set a sane default duration
if (baseDuration == 0)
baseDuration = 1;
// This container needs to resize such that it completely encloses the hit objects to avoid masking optimisations. This is done by converting the largest
// absolutely-sized element along the scrolling axes and adding a corresponding duration value. This introduces a bit of error, but will never under-estimate.ion.
// Find the largest element that is absolutely-sized along ScrollingAxes
float maxAbsoluteSize = Children.Where(c => (c.RelativeSizeAxes & ScrollingAxes) == 0)
.Select(c => (ScrollingAxes & Axes.X) > 0 ? c.Width : c.Height)
.DefaultIfEmpty().Max();
float ourAbsoluteSize = (ScrollingAxes & Axes.X) > 0 ? DrawWidth : DrawHeight;
// Add the extra duration to account for the absolute size
baseDuration *= 1 + maxAbsoluteSize / ourAbsoluteSize;
return baseDuration;
} }
// Todo: This may underestimate the size of the hit object in some cases, but won't be too much of a problem for now
private double computeDuration() => Math.Max(0, Children.Select(c => (c.HitObject as IHasEndTime)?.EndTime ?? c.HitObject.StartTime).DefaultIfEmpty().Max() - ControlPoint.StartTime) + 1000;
/// <summary> /// <summary>
/// The maximum duration of any one hit object inside this <see cref="ScrollingContainer"/>. This is calculated as the maximum /// An approximate total duration of this scrolling container.
/// duration of all hit objects relative to this <see cref="ScrollingContainer"/>'s <see cref="MultiplierControlPoint.StartTime"/>.
/// </summary> /// </summary>
public double Duration => durationBacking.IsValid ? durationBacking : (durationBacking.Value = computeDuration()); public double Duration => durationBacking.IsValid ? durationBacking : (durationBacking.Value = computeDuration());
@ -96,6 +81,8 @@ namespace osu.Game.Rulesets.Timing
{ {
base.Update(); base.Update();
RelativeChildOffset = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)ControlPoint.StartTime : 0, (ScrollingAxes & Axes.Y) > 0 ? (float)ControlPoint.StartTime : 0);
// We want our size and position-space along the scrolling axes to span our duration to completely enclose all the hit objects // We want our size and position-space along the scrolling axes to span our duration to completely enclose all the hit objects
Size = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)Duration : Size.X, (ScrollingAxes & Axes.Y) > 0 ? (float)Duration : Size.Y); Size = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)Duration : Size.X, (ScrollingAxes & Axes.Y) > 0 ? (float)Duration : Size.Y);
// And we need to make sure the hit object's position-space doesn't change due to our resizing // And we need to make sure the hit object's position-space doesn't change due to our resizing

View File

@ -31,7 +31,11 @@ namespace osu.Game.Rulesets.Timing
/// <summary> /// <summary>
/// The axes which the content of this container will scroll through. /// The axes which the content of this container will scroll through.
/// </summary> /// </summary>
public Axes ScrollingAxes { get; internal set; } public Axes ScrollingAxes
{
get { return scrollingContainer.ScrollingAxes; }
set { scrollingContainer.ScrollingAxes = value; }
}
public override bool RemoveWhenNotAlive => false; public override bool RemoveWhenNotAlive => false;
@ -52,11 +56,8 @@ namespace osu.Game.Rulesets.Timing
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
scrollingContainer = CreateScrollingContainer(); scrollingContainer = CreateScrollingContainer();
scrollingContainer.ScrollingAxes = ScrollingAxes;
scrollingContainer.ControlPoint = ControlPoint; scrollingContainer.ControlPoint = ControlPoint;
scrollingContainer.VisibleTimeRange.BindTo(VisibleTimeRange); scrollingContainer.VisibleTimeRange.BindTo(VisibleTimeRange);
scrollingContainer.RelativeChildOffset = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)ControlPoint.StartTime : 0, (ScrollingAxes & Axes.Y) > 0 ? (float)ControlPoint.StartTime : 0);
AddInternal(content = scrollingContainer); AddInternal(content = scrollingContainer);
} }
@ -98,11 +99,6 @@ namespace osu.Game.Rulesets.Timing
base.Add(drawable); base.Add(drawable);
} }
/// <summary>
/// Whether a <see cref="DrawableHitObject"/> falls within this <see cref="SpeedAdjustmentContainer"/>s affecting timespan.
/// </summary>
public bool CanContain(DrawableHitObject hitObject) => CanContain(hitObject.HitObject.StartTime);
/// <summary> /// <summary>
/// Whether a point in time falls within this <see cref="SpeedAdjustmentContainer"/>s affecting timespan. /// Whether a point in time falls within this <see cref="SpeedAdjustmentContainer"/>s affecting timespan.
/// </summary> /// </summary>
@ -112,6 +108,6 @@ namespace osu.Game.Rulesets.Timing
/// Creates the <see cref="ScrollingContainer"/> which contains the scrolling <see cref="DrawableHitObject"/>s of this container. /// Creates the <see cref="ScrollingContainer"/> which contains the scrolling <see cref="DrawableHitObject"/>s of this container.
/// </summary> /// </summary>
/// <returns>The <see cref="ScrollingContainer"/>.</returns> /// <returns>The <see cref="ScrollingContainer"/>.</returns>
protected virtual ScrollingContainer CreateScrollingContainer() => new LinearScrollingContainer(ScrollingAxes, ControlPoint); protected virtual ScrollingContainer CreateScrollingContainer() => new LinearScrollingContainer(ControlPoint);
} }
} }

View File

@ -139,16 +139,30 @@ namespace osu.Game.Rulesets.UI
protected IEnumerable<Mod> Mods; protected IEnumerable<Mod> Mods;
/// <summary> /// <summary>
/// The <see cref="WorkingBeatmap"/> this <see cref="RulesetContainer{TObject}"/> was created with.
/// </summary>
protected readonly WorkingBeatmap WorkingBeatmap;
/// <summary>
/// Whether the specified beatmap is assumed to be specific to the current ruleset.
/// </summary>
protected readonly bool IsForCurrentRuleset;
/// <summary>
/// Whether to assume the beatmap passed into this <see cref="RulesetContainer{TObject}"/> is for the current ruleset.
/// Creates a hit renderer for a beatmap. /// Creates a hit renderer for a beatmap.
/// </summary> /// </summary>
/// <param name="ruleset">The ruleset being repesented.</param> /// <param name="ruleset">The ruleset being repesented.</param>
/// <param name="beatmap">The beatmap to create the hit renderer for.</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> /// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param>
internal RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) : base(ruleset) internal RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap, bool isForCurrentRuleset)
: base(ruleset)
{ {
Debug.Assert(beatmap != null, "RulesetContainer initialized with a null beatmap."); Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap.");
Mods = beatmap.Mods.Value; WorkingBeatmap = workingBeatmap;
IsForCurrentRuleset = isForCurrentRuleset;
Mods = workingBeatmap.Mods.Value;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -156,11 +170,11 @@ namespace osu.Game.Rulesets.UI
BeatmapProcessor<TObject> processor = CreateBeatmapProcessor(); BeatmapProcessor<TObject> processor = CreateBeatmapProcessor();
// Check if the beatmap can be converted // Check if the beatmap can be converted
if (!converter.CanConvert(beatmap.Beatmap)) if (!converter.CanConvert(workingBeatmap.Beatmap))
throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the current ruleset (converter: {converter})."); throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the current ruleset (converter: {converter}).");
// Convert the beatmap // Convert the beatmap
Beatmap = converter.Convert(beatmap.Beatmap, isForCurrentRuleset); Beatmap = converter.Convert(workingBeatmap.Beatmap);
// Apply difficulty adjustments from mods before using Difficulty. // Apply difficulty adjustments from mods before using Difficulty.
foreach (var mod in Mods.OfType<IApplicableToDifficulty>()) foreach (var mod in Mods.OfType<IApplicableToDifficulty>())
@ -173,8 +187,6 @@ namespace osu.Game.Rulesets.UI
// Post-process the beatmap // Post-process the beatmap
processor.PostProcess(Beatmap); processor.PostProcess(Beatmap);
ApplyBeatmap();
// Add mods, should always be the last thing applied to give full control to mods // Add mods, should always be the last thing applied to give full control to mods
applyMods(Mods); applyMods(Mods);
} }
@ -192,11 +204,6 @@ namespace osu.Game.Rulesets.UI
mod.ApplyToRulesetContainer(this); mod.ApplyToRulesetContainer(this);
} }
/// <summary>
/// Called when the beatmap of this hit renderer has been set. Used to apply any default values from the beatmap.
/// </summary>
protected virtual void ApplyBeatmap() { }
/// <summary> /// <summary>
/// Creates a processor to perform post-processing operations /// Creates a processor to perform post-processing operations
/// on HitObjects in converted Beatmaps. /// on HitObjects in converted Beatmaps.

View File

@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
public readonly BindableBool Reversed = new BindableBool(); public readonly BindableBool Reversed = new BindableBool();
private readonly Container<SpeedAdjustmentContainer> speedAdjustments; private readonly SortedContainer speedAdjustments;
public IReadOnlyList<SpeedAdjustmentContainer> SpeedAdjustments => speedAdjustments; public IReadOnlyList<SpeedAdjustmentContainer> SpeedAdjustments => speedAdjustments;
private readonly SpeedAdjustmentContainer defaultSpeedAdjustment; private readonly SpeedAdjustmentContainer defaultSpeedAdjustment;
@ -166,14 +166,15 @@ namespace osu.Game.Rulesets.UI
{ {
this.scrollingAxes = scrollingAxes; this.scrollingAxes = scrollingAxes;
AddInternal(speedAdjustments = new Container<SpeedAdjustmentContainer> { RelativeSizeAxes = Axes.Both }); AddInternal(speedAdjustments = new SortedContainer { RelativeSizeAxes = Axes.Both });
// Default speed adjustment // Default speed adjustment
AddSpeedAdjustment(defaultSpeedAdjustment = new SpeedAdjustmentContainer(new MultiplierControlPoint(0))); AddSpeedAdjustment(defaultSpeedAdjustment = new SpeedAdjustmentContainer(new MultiplierControlPoint(0)));
} }
/// <summary> /// <summary>
/// Adds a <see cref="SpeedAdjustmentContainer"/> to this container. /// Adds a <see cref="SpeedAdjustmentContainer"/> to this container, re-sorting all hit objects
/// in the last <see cref="SpeedAdjustmentContainer"/> that occurred (time-wise) before it.
/// </summary> /// </summary>
/// <param name="speedAdjustment">The <see cref="SpeedAdjustmentContainer"/>.</param> /// <param name="speedAdjustment">The <see cref="SpeedAdjustmentContainer"/>.</param>
public void AddSpeedAdjustment(SpeedAdjustmentContainer speedAdjustment) public void AddSpeedAdjustment(SpeedAdjustmentContainer speedAdjustment)
@ -181,26 +182,27 @@ namespace osu.Game.Rulesets.UI
speedAdjustment.ScrollingAxes = scrollingAxes; speedAdjustment.ScrollingAxes = scrollingAxes;
speedAdjustment.VisibleTimeRange.BindTo(VisibleTimeRange); speedAdjustment.VisibleTimeRange.BindTo(VisibleTimeRange);
speedAdjustment.Reversed.BindTo(Reversed); speedAdjustment.Reversed.BindTo(Reversed);
speedAdjustments.Add(speedAdjustment);
// We now need to re-sort the hit objects in the last speed adjustment prior to this one, to see if they need a new parent if (speedAdjustments.Count > 0)
var previousSpeedAdjustment = speedAdjustments.LastOrDefault(s => s != speedAdjustment && s.ControlPoint.StartTime <= speedAdjustment.ControlPoint.StartTime);
if (previousSpeedAdjustment == null)
return;
for (int i = 0; i < previousSpeedAdjustment.Children.Count; i++)
{ {
DrawableHitObject hitObject = previousSpeedAdjustment[i]; // We need to re-sort all hit objects in the speed adjustment container prior to figure out if they
// should now lie within this one
var existingAdjustment = adjustmentContainerAt(speedAdjustment.ControlPoint.StartTime);
for (int i = 0; i < existingAdjustment.Count; i++)
{
DrawableHitObject hitObject = existingAdjustment[i];
var newSpeedAdjustment = adjustmentContainerFor(hitObject); if (!speedAdjustment.CanContain(hitObject.HitObject.StartTime))
if (newSpeedAdjustment == previousSpeedAdjustment) continue;
continue;
previousSpeedAdjustment.Remove(hitObject); existingAdjustment.Remove(hitObject);
newSpeedAdjustment.Add(hitObject); speedAdjustment.Add(hitObject);
i--; i--;
}
} }
speedAdjustments.Add(speedAdjustment);
} }
/// <summary> /// <summary>
@ -237,27 +239,32 @@ namespace osu.Game.Rulesets.UI
if (!(hitObject is IScrollingHitObject)) if (!(hitObject is IScrollingHitObject))
throw new InvalidOperationException($"Hit objects added to a {nameof(ScrollingHitObjectContainer)} must implement {nameof(IScrollingHitObject)}."); throw new InvalidOperationException($"Hit objects added to a {nameof(ScrollingHitObjectContainer)} must implement {nameof(IScrollingHitObject)}.");
adjustmentContainerFor(hitObject).Add(hitObject); adjustmentContainerAt(hitObject.HitObject.StartTime).Add(hitObject);
} }
public override bool Remove(DrawableHitObject hitObject) => speedAdjustments.Any(s => s.Remove(hitObject)); public override bool Remove(DrawableHitObject hitObject) => speedAdjustments.Any(s => s.Remove(hitObject));
/// <summary>
/// Finds the <see cref="SpeedAdjustmentContainer"/> which provides the speed adjustment active at the start time
/// of a hit object. If there is no <see cref="SpeedAdjustmentContainer"/> active at the start time of the hit object,
/// then the first (time-wise) speed adjustment is returned.
/// </summary>
/// <param name="hitObject">The hit object to find the active <see cref="SpeedAdjustmentContainer"/> for.</param>
/// <returns>The <see cref="SpeedAdjustmentContainer"/> active at <paramref name="hitObject"/>'s start time. Null if there are no speed adjustments.</returns>
private SpeedAdjustmentContainer adjustmentContainerFor(DrawableHitObject hitObject) => speedAdjustments.LastOrDefault(c => c.CanContain(hitObject)) ?? defaultSpeedAdjustment;
/// <summary> /// <summary>
/// Finds the <see cref="SpeedAdjustmentContainer"/> which provides the speed adjustment active at a time. /// Finds the <see cref="SpeedAdjustmentContainer"/> which provides the speed adjustment active at a time.
/// If there is no <see cref="SpeedAdjustmentContainer"/> active at the time, then the first (time-wise) speed adjustment is returned. /// If there is no <see cref="SpeedAdjustmentContainer"/> active at the time, then the first (time-wise) speed adjustment is returned.
/// </summary> /// </summary>
/// <param name="time">The time to find the active <see cref="SpeedAdjustmentContainer"/> at.</param> /// <param name="time">The time to find the active <see cref="SpeedAdjustmentContainer"/> at.</param>
/// <returns>The <see cref="SpeedAdjustmentContainer"/> active at <paramref name="time"/>. Null if there are no speed adjustments.</returns> /// <returns>The <see cref="SpeedAdjustmentContainer"/> active at <paramref name="time"/>. Null if there are no speed adjustments.</returns>
private SpeedAdjustmentContainer adjustmentContainerAt(double time) => speedAdjustments.LastOrDefault(c => c.CanContain(time)) ?? defaultSpeedAdjustment; private SpeedAdjustmentContainer adjustmentContainerAt(double time) => speedAdjustments.FirstOrDefault(c => c.CanContain(time)) ?? defaultSpeedAdjustment;
private class SortedContainer : Container<SpeedAdjustmentContainer>
{
protected override int Compare(Drawable x, Drawable y)
{
var sX = (SpeedAdjustmentContainer)x;
var sY = (SpeedAdjustmentContainer)y;
int result = sY.ControlPoint.StartTime.CompareTo(sX.ControlPoint.StartTime);
if (result != 0)
return result;
return base.Compare(y, x);
}
}
} }
} }
} }

View File

@ -39,17 +39,6 @@ namespace osu.Game.Rulesets.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{
DefaultControlPoints.ForEach(c => applySpeedAdjustment(c, Playfield));
}
private void applySpeedAdjustment(MultiplierControlPoint controlPoint, ScrollingPlayfield<TObject, TJudgement> playfield)
{
playfield.HitObjects.AddSpeedAdjustment(CreateSpeedAdjustmentContainer(controlPoint));
playfield.NestedPlayfields.ForEach(p => applySpeedAdjustment(controlPoint, p));
}
protected override void ApplyBeatmap()
{ {
// Calculate default multiplier control points // Calculate default multiplier control points
var lastTimingPoint = new TimingControlPoint(); var lastTimingPoint = new TimingControlPoint();
@ -95,6 +84,14 @@ namespace osu.Game.Rulesets.UI
// If we have no control points, add a default one // If we have no control points, add a default one
if (DefaultControlPoints.Count == 0) if (DefaultControlPoints.Count == 0)
DefaultControlPoints.Add(new MultiplierControlPoint()); DefaultControlPoints.Add(new MultiplierControlPoint());
DefaultControlPoints.ForEach(c => applySpeedAdjustment(c, Playfield));
}
private void applySpeedAdjustment(MultiplierControlPoint controlPoint, ScrollingPlayfield<TObject, TJudgement> playfield)
{
playfield.HitObjects.AddSpeedAdjustment(CreateSpeedAdjustmentContainer(controlPoint));
playfield.NestedPlayfields.ForEach(p => applySpeedAdjustment(controlPoint, p));
} }
/// <summary> /// <summary>