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

Merge branch 'master' into mania-key-conversion

This commit is contained in:
Dean Herbert 2017-08-22 20:08:39 +09:00 committed by GitHub
commit 9f52ecbb8a
12 changed files with 219 additions and 104 deletions

@ -1 +1 @@
Subproject commit f1527e5456cd228ddfb68cf6d56eb5d28dc360bf
Subproject commit ba70b8eaa9b79d4248873d4399f3b9e918fc3c8f

View File

@ -3,19 +3,23 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using OpenTK;
using osu.Desktop.Tests.Beatmaps;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.UI;
namespace osu.Desktop.Tests.Visual
@ -23,6 +27,7 @@ namespace osu.Desktop.Tests.Visual
/// <summary>
/// The most minimal implementation of a playfield with scrolling hit objects.
/// </summary>
[TestFixture]
public class TestCaseScrollingPlayfield : OsuTestCase
{
public TestCaseScrollingPlayfield()
@ -64,6 +69,66 @@ namespace osu.Desktop.Tests.Visual
});
}
[Test]
public void TestSpeedAdjustmentOrdering()
{
var hitObjectContainer = new ScrollingPlayfield<TestHitObject, TestJudgement>.ScrollingHitObjectContainer(Axes.X);
var speedAdjustments = new[]
{
new SpeedAdjustmentContainer(new MultiplierControlPoint()),
new SpeedAdjustmentContainer(new MultiplierControlPoint(1000)
{
TimingPoint = new TimingControlPoint { BeatLength = 500 }
}),
new SpeedAdjustmentContainer(new MultiplierControlPoint(2000)
{
TimingPoint = new TimingControlPoint { BeatLength = 1000 },
DifficultyPoint = new DifficultyControlPoint { SpeedMultiplier = 2}
}),
new SpeedAdjustmentContainer(new MultiplierControlPoint(3000)
{
TimingPoint = new TimingControlPoint { BeatLength = 1000 },
DifficultyPoint = new DifficultyControlPoint { SpeedMultiplier = 1}
}),
};
var hitObjects = new[]
{
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = -1000 }),
new DrawableTestHitObject(Axes.X, new TestHitObject()),
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 1000 }),
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 2000 }),
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 3000 }),
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 4000 }),
};
hitObjects.ForEach(h => hitObjectContainer.Add(h));
speedAdjustments.ForEach(hitObjectContainer.AddSpeedAdjustment);
// The 0th index in hitObjectContainer.SpeedAdjustments is the "default" control point
// Check multiplier of the default speed adjustment
Assert.AreEqual(1, hitObjectContainer.SpeedAdjustments[0].ControlPoint.Multiplier);
Assert.AreEqual(1, speedAdjustments[0].ControlPoint.Multiplier);
Assert.AreEqual(2, speedAdjustments[1].ControlPoint.Multiplier);
Assert.AreEqual(2, speedAdjustments[2].ControlPoint.Multiplier);
Assert.AreEqual(1, speedAdjustments[3].ControlPoint.Multiplier);
// Check insertion of hit objects
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[0].Contains(hitObjects[0]));
Assert.IsTrue(speedAdjustments[0].Contains(hitObjects[1]));
Assert.IsTrue(speedAdjustments[1].Contains(hitObjects[2]));
Assert.IsTrue(speedAdjustments[2].Contains(hitObjects[3]));
Assert.IsTrue(speedAdjustments[3].Contains(hitObjects[4]));
Assert.IsTrue(speedAdjustments[3].Contains(hitObjects[5]));
hitObjectContainer.RemoveSpeedAdjustment(speedAdjustments[1]);
// The hit object contained in this speed adjustment should be resorted into the previous one
Assert.IsTrue(speedAdjustments[0].Contains(hitObjects[2]));
}
private class TestRulesetContainer : ScrollingRulesetContainer<TestPlayfield, TestHitObject, TestJudgement>
{
private readonly Axes scrollingAxes;
@ -158,4 +223,4 @@ namespace osu.Desktop.Tests.Visual
public override string MaxResultString { get { throw new NotImplementedException(); } }
}
}
}
}

View File

@ -3,11 +3,11 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Threading;
using OpenTK;
using osu.Framework.Audio;
using osu.Framework.Allocation;
using osu.Game.Input.Bindings;
namespace osu.Game.Graphics.UserInterface.Volume
{
@ -64,15 +64,25 @@ namespace osu.Game.Graphics.UserInterface.Volume
volumeMeterMusic.Bindable.ValueChanged -= volumeChanged;
}
public void Adjust(InputState state)
public bool Adjust(GlobalAction action)
{
if (State == Visibility.Hidden)
switch (action)
{
Show();
return;
case GlobalAction.DecreaseVolume:
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]

View File

@ -3,32 +3,16 @@
using System;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using OpenTK.Input;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
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)
{
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);
}
public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false;
public bool OnReleased(GlobalAction action) => false;
}
}

View File

@ -4,15 +4,16 @@
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Game.Graphics.Sprites;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
namespace osu.Game.Graphics.UserInterface.Volume
{
internal class VolumeMeter : Container
internal class VolumeMeter : Container, IKeyBindingHandler<GlobalAction>
{
private readonly Box meterFill;
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;
return true;
Volume += 0.05f;
}
public void Decrease()
{
Volume -= 0.05f;
}
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.T }, GlobalAction.ToggleToolbar),
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 =>
@ -47,5 +50,9 @@ namespace osu.Game.Input.Bindings
ToggleSettings,
[Description("Toggle osu!direct")]
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.Containers;
using osu.Game.Overlays;
using osu.Framework.Input;
using osu.Framework.Logging;
using osu.Game.Graphics.UserInterface.Volume;
using osu.Framework.Allocation;
@ -160,7 +159,7 @@ namespace osu.Game
new VolumeControlReceptor
{
RelativeSizeAxes = Axes.Both,
ActionRequested = delegate(InputState state) { volume.Adjust(state); }
ActionRequested = action => volume.Adjust(action)
},
mainContent = new Container
{
@ -359,6 +358,13 @@ namespace osu.Game
{
base.UpdateAfterChildren();
// we only want to apply these restrictions when we are inside a screen stack.
// the use case for not applying is in visual/unit tests.
bool applyRestrictions = !currentScreen?.AllowBeatmapRulesetChange ?? false;
Ruleset.Disabled = applyRestrictions;
Beatmap.Disabled = applyRestrictions;
mainContent.Padding = new MarginPadding { Top = ToolbarOffset };
Cursor.State = currentScreen?.HasLocalCursorDisplayed == false ? Visibility.Visible : Visibility.Hidden;

View File

@ -136,40 +136,49 @@ namespace osu.Game.Overlays.KeyBinding
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
if (HasFocus)
{
if (bindTarget.IsHovered)
{
if (!AllowMainMouseButtons)
{
switch (args.Button)
{
case MouseButton.Left:
case MouseButton.Right:
return true;
}
}
if (!HasFocus || !bindTarget.IsHovered)
return base.OnMouseDown(state, args);
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state));
return true;
if (!AllowMainMouseButtons)
{
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)
{
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)
{
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state));
finalise();
else
updateBindTarget();
return true;
return true;
}
}
return base.OnMouseUp(state, args);
return base.OnWheel(state);
}
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)
{
if (HasFocus)
{
finalise();
return true;
}
if (!HasFocus) return base.OnKeyUp(state, args);
return base.OnKeyUp(state, args);
finalise();
return true;
}
private void finalise()

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Timing
public readonly BindableBool Reversed = new BindableBool();
protected override Container<DrawableHitObject> Content => content;
private Container<DrawableHitObject> content;
private readonly Container<DrawableHitObject> content;
/// <summary>
/// The axes which the content of this container will scroll through.
@ -41,7 +40,7 @@ namespace osu.Game.Rulesets.Timing
/// </summary>
public readonly MultiplierControlPoint ControlPoint;
private ScrollingContainer scrollingContainer;
private readonly ScrollingContainer scrollingContainer;
/// <summary>
/// Creates a new <see cref="SpeedAdjustmentContainer"/>.
@ -51,11 +50,7 @@ namespace osu.Game.Rulesets.Timing
{
ControlPoint = controlPoint;
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
scrollingContainer = CreateScrollingContainer();
scrollingContainer.ScrollingAxes = ScrollingAxes;

View File

@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.UI
/// <summary>
/// The container that contains the <see cref="SpeedAdjustmentContainer"/>s and <see cref="DrawableHitObject"/>s.
/// </summary>
internal new readonly ScrollingHitObjectContainer HitObjects;
public new readonly ScrollingHitObjectContainer HitObjects;
/// <summary>
/// Creates a new <see cref="ScrollingPlayfield{TObject, TJudgement}"/>.
@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.UI
/// <summary>
/// A container that provides the foundation for sorting <see cref="DrawableHitObject"/>s into <see cref="SpeedAdjustmentContainer"/>s.
/// </summary>
internal class ScrollingHitObjectContainer : HitObjectContainer
public class ScrollingHitObjectContainer : HitObjectContainer
{
/// <summary>
/// Gets or sets the range of time that is visible by the length of the scrolling axes.
@ -152,6 +152,9 @@ namespace osu.Game.Rulesets.UI
public readonly BindableBool Reversed = new BindableBool();
private readonly Container<SpeedAdjustmentContainer> speedAdjustments;
public IReadOnlyList<SpeedAdjustmentContainer> SpeedAdjustments => speedAdjustments;
private readonly SpeedAdjustmentContainer defaultSpeedAdjustment;
private readonly Axes scrollingAxes;
@ -166,7 +169,7 @@ namespace osu.Game.Rulesets.UI
AddInternal(speedAdjustments = new Container<SpeedAdjustmentContainer> { RelativeSizeAxes = Axes.Both });
// Default speed adjustment
AddSpeedAdjustment(new SpeedAdjustmentContainer(new MultiplierControlPoint(0)));
AddSpeedAdjustment(defaultSpeedAdjustment = new SpeedAdjustmentContainer(new MultiplierControlPoint(0)));
}
/// <summary>
@ -181,18 +184,44 @@ namespace osu.Game.Rulesets.UI
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
var previousSpeedAdjustment = speedAdjustments.LastOrDefault(s => s.ControlPoint.StartTime < speedAdjustment.ControlPoint.StartTime);
var previousSpeedAdjustment = speedAdjustments.LastOrDefault(s => s != speedAdjustment && s.ControlPoint.StartTime <= speedAdjustment.ControlPoint.StartTime);
if (previousSpeedAdjustment == null)
return;
foreach (DrawableHitObject h in previousSpeedAdjustment.Children)
for (int i = 0; i < previousSpeedAdjustment.Children.Count; i++)
{
var newSpeedAdjustment = adjustmentContainerFor(h);
DrawableHitObject hitObject = previousSpeedAdjustment[i];
var newSpeedAdjustment = adjustmentContainerFor(hitObject);
if (newSpeedAdjustment == previousSpeedAdjustment)
continue;
previousSpeedAdjustment.Remove(h);
newSpeedAdjustment.Add(h);
previousSpeedAdjustment.Remove(hitObject);
newSpeedAdjustment.Add(hitObject);
i--;
}
}
/// <summary>
/// Removes a <see cref="SpeedAdjustmentContainer"/> from this container, re-sorting all hit objects
/// which it contained into new <see cref="SpeedAdjustmentContainer"/>s.
/// </summary>
/// <param name="speedAdjustment">The <see cref="SpeedAdjustmentContainer"/> to remove.</param>
public void RemoveSpeedAdjustment(SpeedAdjustmentContainer speedAdjustment)
{
if (speedAdjustment == defaultSpeedAdjustment)
throw new InvalidOperationException($"The default {nameof(SpeedAdjustmentContainer)} must not be removed.");
if (!speedAdjustments.Remove(speedAdjustment))
return;
while (speedAdjustment.Count > 0)
{
DrawableHitObject hitObject = speedAdjustment[0];
speedAdjustment.Remove(hitObject);
Add(hitObject);
}
}
@ -208,11 +237,7 @@ namespace osu.Game.Rulesets.UI
if (!(hitObject is IScrollingHitObject))
throw new InvalidOperationException($"Hit objects added to a {nameof(ScrollingHitObjectContainer)} must implement {nameof(IScrollingHitObject)}.");
var target = adjustmentContainerFor(hitObject);
if (target == null)
throw new InvalidOperationException($"A {nameof(SpeedAdjustmentContainer)} to container {hitObject} could not be found.");
target.Add(hitObject);
adjustmentContainerFor(hitObject).Add(hitObject);
}
public override bool Remove(DrawableHitObject hitObject) => speedAdjustments.Any(s => s.Remove(hitObject));
@ -224,7 +249,7 @@ namespace osu.Game.Rulesets.UI
/// </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.FirstOrDefault(c => c.CanContain(hitObject)) ?? speedAdjustments.LastOrDefault();
private SpeedAdjustmentContainer adjustmentContainerFor(DrawableHitObject hitObject) => speedAdjustments.LastOrDefault(c => c.CanContain(hitObject)) ?? defaultSpeedAdjustment;
/// <summary>
/// Finds the <see cref="SpeedAdjustmentContainer"/> which provides the speed adjustment active at a time.
@ -232,7 +257,7 @@ namespace osu.Game.Rulesets.UI
/// </summary>
/// <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>
private SpeedAdjustmentContainer adjustmentContainerAt(double time) => speedAdjustments.FirstOrDefault(c => c.CanContain(time)) ?? speedAdjustments.LastOrDefault();
private SpeedAdjustmentContainer adjustmentContainerAt(double time) => speedAdjustments.LastOrDefault(c => c.CanContain(time)) ?? defaultSpeedAdjustment;
}
}
}

View File

@ -69,19 +69,6 @@ namespace osu.Game.Screens
sampleExit = audio.Sample.Get(@"UI/melodic-1");
}
protected override void Update()
{
if (!IsCurrentScreen) return;
if (ParentScreen != null)
{
// we only want to apply these restrictions when we are inside a screen stack.
// the use case for not applying is in visual/unit tests.
Ruleset.Disabled = !AllowBeatmapRulesetChange;
Beatmap.Disabled = !AllowBeatmapRulesetChange;
}
}
protected override void OnResuming(Screen last)
{
base.OnResuming(last);

View File

@ -23,6 +23,7 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Ranking;
using osu.Framework.Audio.Sample;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
namespace osu.Game.Screens.Play
{
@ -50,6 +51,8 @@ namespace osu.Game.Screens.Play
private RulesetInfo ruleset;
private APIAccess api;
private ScoreProcessor scoreProcessor;
protected RulesetContainer RulesetContainer;
@ -68,10 +71,13 @@ namespace osu.Game.Screens.Play
private bool loadedSuccessfully => RulesetContainer?.Objects.Any() == true;
[BackgroundDependencyLoader(permitNulls: true)]
private void load(AudioManager audio, OsuConfigManager config, OsuGame osu)
[BackgroundDependencyLoader]
private void load(AudioManager audio, OsuConfigManager config, APIAccess api)
{
this.api = api;
dimLevel = config.GetBindable<double>(OsuSetting.DimLevel);
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
sampleRestart = audio.Sample.Get(@"Gameplay/restart");
@ -86,7 +92,7 @@ namespace osu.Game.Screens.Play
if (beatmap == null)
throw new InvalidOperationException("Beatmap was not loaded");
ruleset = osu?.Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset;
ruleset = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset;
var rulesetInstance = ruleset.CreateInstance();
try
@ -235,7 +241,7 @@ namespace osu.Game.Screens.Play
Ruleset = ruleset
};
scoreProcessor.PopulateScore(score);
score.User = RulesetContainer.Replay?.User ?? (Game as OsuGame)?.API?.LocalUser?.Value;
score.User = RulesetContainer.Replay?.User ?? api.LocalUser.Value;
Push(new Results(score));
});
}