1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 14:32:55 +08:00

Merge remote-tracking branch 'refs/remotes/ppy/master' into user_profile_modes

This commit is contained in:
EVAST9919 2019-06-08 11:31:28 +03:00
commit ea492772cf
16 changed files with 746 additions and 205 deletions

View File

@ -54,7 +54,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
for (int i = 0; i < max_sprites; i++)
{
parts[i].InvalidationID = 0;
// InvalidationID 1 forces an update of each part of the cursor trail the first time ApplyState is run on the draw node
// This is to prevent garbage data from being sent to the vertex shader, resulting in visual issues on some platforms
parts[i].InvalidationID = 1;
parts[i].WasUpdated = true;
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneFailAnimation : AllPlayersTestScene
{
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Array.Empty<Mod>();
return new FailPlayer();
}
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(AllPlayersTestScene),
typeof(TestPlayer),
typeof(Player),
};
protected override void AddCheckSteps()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State == Visibility.Visible);
}
private class FailPlayer : TestPlayer
{
public new FailOverlay FailOverlay => base.FailOverlay;
public FailPlayer()
: base(false, false)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
ScoreProcessor.FailConditions += _ => true;
}
}
}
}

View File

@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestPauseAfterFail()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddAssert("fail overlay shown", () => Player.FailOverlayVisible);
AddUntilStep("fail overlay shown", () => Player.FailOverlayVisible);
confirmClockRunning(false);

View File

@ -84,7 +84,6 @@ namespace osu.Game.Tests.Visual.UserInterface
testLocalCursor();
testUserCursorOverride();
testMultipleLocalCursors();
ReturnUserInput();
}
/// <summary>

View File

@ -0,0 +1,194 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.UserInterface
{
[TestFixture]
public class TestSceneOsuHoverContainer : ManualInputManagerTestScene
{
private OsuHoverTestContainer hoverContainer;
private Box colourContainer;
[SetUp]
public void SetUp() => Schedule(() =>
{
Child = hoverContainer = new OsuHoverTestContainer
{
Enabled = { Value = true },
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(100),
Child = colourContainer = new Box
{
RelativeSizeAxes = Axes.Both,
},
};
doMoveOut();
});
[Description("Checks IsHovered property value on a container when it is hovered/unhovered.")]
[TestCase(true, TestName = "Enabled_Check_IsHovered")]
[TestCase(false, TestName = "Disabled_Check_IsHovered")]
public void TestIsHoveredHasProperValue(bool isEnabled)
{
setContainerEnabledTo(isEnabled);
checkNotHovered();
moveToText();
checkHovered();
moveOut();
checkNotHovered();
moveToText();
checkHovered();
moveOut();
checkNotHovered();
}
[Test]
[Description("Checks colour fading on an enabled container when it is hovered/unhovered.")]
public void TestTransitionWhileEnabled()
{
enableContainer();
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
moveToText();
waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR);
moveOut();
waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR);
moveToText();
waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR);
moveOut();
waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR);
}
[Test]
[Description("Checks colour fading on a disabled container when it is hovered/unhovered.")]
public void TestNoTransitionWhileDisabled()
{
disableContainer();
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
moveToText();
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
moveOut();
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
moveToText();
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
moveOut();
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
}
[Test]
[Description("Checks that when a disabled & hovered container gets enabled, colour fading happens")]
public void TestBecomesEnabledTransition()
{
disableContainer();
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
moveToText();
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
enableContainer();
waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR);
}
[Test]
[Description("Checks that when an enabled & hovered container gets disabled, colour fading happens")]
public void TestBecomesDisabledTransition()
{
enableContainer();
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
moveToText();
waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR);
disableContainer();
waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR);
}
[Test]
[Description("Checks that when a hovered container gets enabled and disabled multiple times, colour fading happens")]
public void TestDisabledChangesMultipleTimes()
{
enableContainer();
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
moveToText();
waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR);
disableContainer();
waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR);
enableContainer();
waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR);
disableContainer();
waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR);
}
private void enableContainer() => setContainerEnabledTo(true);
private void disableContainer() => setContainerEnabledTo(false);
private void setContainerEnabledTo(bool newValue)
{
string word = newValue ? "Enable" : "Disable";
AddStep($"{word} container", () => hoverContainer.Enabled.Value = newValue);
}
private void moveToText() => AddStep("Move mouse to text", () => InputManager.MoveMouseTo(hoverContainer));
private void moveOut() => AddStep("Move out", doMoveOut);
private void checkHovered() => AddAssert("Check hovered", () => hoverContainer.IsHovered);
private void checkNotHovered() => AddAssert("Check not hovered", () => !hoverContainer.IsHovered);
private void checkColour(ColourInfo expectedColour)
=> AddAssert($"Check colour to be '{expectedColour}'", () => currentColour.Equals(expectedColour));
private void waitUntilColourIs(ColourInfo expectedColour)
=> AddUntilStep($"Wait until hover colour is {expectedColour}", () => currentColour.Equals(expectedColour));
private ColourInfo currentColour => colourContainer.DrawColourInfo.Colour;
/// <summary>
/// Moves the cursor to top left corner of the screen
/// </summary>
private void doMoveOut()
=> InputManager.MoveMouseTo(new Vector2(InputManager.ScreenSpaceDrawQuad.TopLeft.X, InputManager.ScreenSpaceDrawQuad.TopLeft.Y));
private sealed class OsuHoverTestContainer : OsuHoverContainer
{
public static readonly Color4 HOVER_COLOUR = Color4.Red;
public static readonly Color4 IDLE_COLOUR = Color4.Green;
public OsuHoverTestContainer()
{
HoverColour = HOVER_COLOUR;
IdleColour = IDLE_COLOUR;
}
}
}
}

View File

@ -1,6 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
@ -12,22 +16,24 @@ using osu.Game.Beatmaps;
namespace osu.Game.Audio
{
/// <summary>
/// A central store for the retrieval of <see cref="PreviewTrack"/>s.
/// </summary>
public class PreviewTrackManager : Component
{
private readonly BindableDouble muteBindable = new BindableDouble();
private AudioManager audio;
private ITrackStore trackStore;
private PreviewTrackStore trackStore;
private TrackManagerPreviewTrack current;
[BackgroundDependencyLoader]
private void load(AudioManager audio, FrameworkConfigManager config)
{
trackStore = audio.GetTrackStore(new OnlineStore());
// this is a temporary solution to get around muting ourselves.
// todo: update this once we have a BackgroundTrackManager or similar.
trackStore = new PreviewTrackStore(new OnlineStore());
audio.AddItem(trackStore);
trackStore.AddAdjustment(AdjustableProperty.Volume, audio.VolumeTrack);
this.audio = audio;
@ -103,5 +109,46 @@ namespace osu.Game.Audio
protected override Track GetTrack() => trackManager.Get($"https://b.ppy.sh/preview/{beatmapSetInfo?.OnlineBeatmapSetID}.mp3");
}
private class PreviewTrackStore : AudioCollectionManager<AdjustableAudioComponent>, ITrackStore
{
private readonly IResourceStore<byte[]> store;
internal PreviewTrackStore(IResourceStore<byte[]> store)
{
this.store = store;
}
public Track GetVirtual(double length = double.PositiveInfinity)
{
if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(PreviewTrackStore)}");
var track = new TrackVirtual(length);
AddItem(track);
return track;
}
public Track Get(string name)
{
if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(PreviewTrackStore)}");
if (string.IsNullOrEmpty(name)) return null;
var dataStream = store.GetStream(name);
if (dataStream == null)
return null;
Track track = new TrackBass(dataStream);
AddItem(track);
return track;
}
public Task<Track> GetAsync(string name) => Task.Run(() => Get(name));
public Stream GetStream(string name) => store.GetStream(name);
public IEnumerable<string> GetAvailableResources() => store.GetAvailableResources();
}
}
}

View File

@ -24,8 +24,13 @@ namespace osu.Game.Graphics.Containers
{
Enabled.ValueChanged += e =>
{
if (!e.NewValue)
unhover();
if (isHovered)
{
if (e.NewValue)
fadeIn();
else
fadeOut();
}
};
}
@ -33,28 +38,28 @@ namespace osu.Game.Graphics.Containers
protected override bool OnHover(HoverEvent e)
{
if (isHovered)
return false;
isHovered = true;
if (!Enabled.Value)
return false;
EffectTargets.ForEach(d => d.FadeColour(HoverColour, FADE_DURATION, Easing.OutQuint));
isHovered = true;
fadeIn();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
unhover();
base.OnHoverLost(e);
}
private void unhover()
{
if (!isHovered)
return;
isHovered = false;
EffectTargets.ForEach(d => d.FadeColour(IdleColour, FADE_DURATION, Easing.OutQuint));
fadeOut();
base.OnHoverLost(e);
}
[BackgroundDependencyLoader]
@ -69,5 +74,9 @@ namespace osu.Game.Graphics.Containers
base.LoadComplete();
EffectTargets.ForEach(d => d.FadeColour(IdleColour));
}
private void fadeIn() => EffectTargets.ForEach(d => d.FadeColour(HoverColour, FADE_DURATION, Easing.OutQuint));
private void fadeOut() => EffectTargets.ForEach(d => d.FadeColour(IdleColour, FADE_DURATION, Easing.OutQuint));
}
}

View File

@ -1,40 +1,40 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osuTK;
using osuTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Mods;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods.Sections;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Overlays.Mods
{
public class ModSelectOverlay : WaveOverlayContainer
{
private const float content_width = 0.8f;
protected Color4 LowMultiplierColour, HighMultiplierColour;
protected readonly TriangleButton DeselectAllButton;
protected readonly OsuSpriteText MultiplierLabel, UnrankedLabel;
private readonly FillFlowContainer footerContainer;
protected readonly TriangleButton CloseButton;
protected readonly OsuSpriteText MultiplierLabel;
protected readonly OsuSpriteText UnrankedLabel;
protected override bool BlockNonPositionalInput => false;
@ -46,154 +46,14 @@ namespace osu.Game.Overlays.Mods
protected readonly IBindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, IBindable<RulesetInfo> ruleset, AudioManager audio, Bindable<IReadOnlyList<Mod>> mods)
{
LowMultiplierColour = colours.Red;
HighMultiplierColour = colours.Green;
UnrankedLabel.Colour = colours.Blue;
protected Color4 LowMultiplierColour;
protected Color4 HighMultiplierColour;
Ruleset.BindTo(ruleset);
if (mods != null) SelectedMods.BindTo(mods);
sampleOn = audio.Samples.Get(@"UI/check-on");
sampleOff = audio.Samples.Get(@"UI/check-off");
}
protected override void LoadComplete()
{
base.LoadComplete();
Ruleset.BindValueChanged(rulesetChanged, true);
SelectedMods.BindValueChanged(selectedModsChanged, true);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
Ruleset.UnbindAll();
SelectedMods.UnbindAll();
}
private void rulesetChanged(ValueChangedEvent<RulesetInfo> e)
{
if (e.NewValue == null) return;
var instance = e.NewValue.CreateInstance();
foreach (ModSection section in ModSectionsContainer.Children)
section.Mods = instance.GetModsFor(section.ModType);
// attempt to re-select any already selected mods.
// this may be the first time we are receiving the ruleset, in which case they will still match.
selectedModsChanged(new ValueChangedEvent<IReadOnlyList<Mod>>(SelectedMods.Value, SelectedMods.Value));
// write the mods back to the SelectedMods bindable in the case a change was not applicable.
// this generally isn't required as the previous line will perform deselection; just here for safety.
refreshSelectedMods();
}
private void selectedModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> e)
{
foreach (ModSection section in ModSectionsContainer.Children)
section.SelectTypes(e.NewValue.Select(m => m.GetType()).ToList());
updateMods();
}
private void updateMods()
{
double multiplier = 1.0;
bool ranked = true;
foreach (Mod mod in SelectedMods.Value)
{
multiplier *= mod.ScoreMultiplier;
ranked &= mod.Ranked;
}
MultiplierLabel.Text = $"{multiplier:N2}x";
if (multiplier > 1.0)
MultiplierLabel.FadeColour(HighMultiplierColour, 200);
else if (multiplier < 1.0)
MultiplierLabel.FadeColour(LowMultiplierColour, 200);
else
MultiplierLabel.FadeColour(Color4.White, 200);
UnrankedLabel.FadeTo(ranked ? 0 : 1, 200);
}
protected override void PopOut()
{
base.PopOut();
footerContainer.MoveToX(footerContainer.DrawSize.X, WaveContainer.DISAPPEAR_DURATION, Easing.InSine);
footerContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine);
foreach (ModSection section in ModSectionsContainer.Children)
{
section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), WaveContainer.DISAPPEAR_DURATION, Easing.InSine);
section.ButtonsContainer.MoveToX(100f, WaveContainer.DISAPPEAR_DURATION, Easing.InSine);
section.ButtonsContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine);
}
}
protected override void PopIn()
{
base.PopIn();
footerContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint);
footerContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
foreach (ModSection section in ModSectionsContainer.Children)
{
section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), WaveContainer.APPEAR_DURATION, Easing.OutQuint);
section.ButtonsContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint);
section.ButtonsContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
}
}
public void DeselectAll()
{
foreach (ModSection section in ModSectionsContainer.Children)
section.DeselectAll();
refreshSelectedMods();
}
/// <summary>
/// Deselect one or more mods.
/// </summary>
/// <param name="modTypes">The types of <see cref="Mod"/>s which should be deselected.</param>
/// <param name="immediate">Set to true to bypass animations and update selections immediately.</param>
public void DeselectTypes(Type[] modTypes, bool immediate = false)
{
if (modTypes.Length == 0) return;
foreach (ModSection section in ModSectionsContainer.Children)
section.DeselectTypes(modTypes, immediate);
}
private const float content_width = 0.8f;
private readonly FillFlowContainer footerContainer;
private SampleChannel sampleOn, sampleOff;
private void modButtonPressed(Mod selectedMod)
{
if (selectedMod != null)
{
if (State == Visibility.Visible) sampleOn?.Play();
DeselectTypes(selectedMod.IncompatibleMods, true);
}
else
{
if (State == Visibility.Visible) sampleOff?.Play();
}
refreshSelectedMods();
}
private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray();
public ModSelectOverlay()
{
Waves.FirstWaveColour = OsuColour.FromHex(@"19b0e2");
@ -364,6 +224,16 @@ namespace osu.Game.Overlays.Mods
Right = 20
}
},
CloseButton = new TriangleButton
{
Width = 180,
Text = "Close",
Action = Hide,
Margin = new MarginPadding
{
Right = 20
}
},
new OsuSpriteText
{
Text = @"Score Multiplier:",
@ -401,5 +271,171 @@ namespace osu.Game.Overlays.Mods
},
};
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, IBindable<RulesetInfo> ruleset, AudioManager audio, Bindable<IReadOnlyList<Mod>> mods)
{
LowMultiplierColour = colours.Red;
HighMultiplierColour = colours.Green;
UnrankedLabel.Colour = colours.Blue;
Ruleset.BindTo(ruleset);
if (mods != null) SelectedMods.BindTo(mods);
sampleOn = audio.Samples.Get(@"UI/check-on");
sampleOff = audio.Samples.Get(@"UI/check-off");
}
public void DeselectAll()
{
foreach (var section in ModSectionsContainer.Children)
section.DeselectAll();
refreshSelectedMods();
}
/// <summary>
/// Deselect one or more mods.
/// </summary>
/// <param name="modTypes">The types of <see cref="Mod"/>s which should be deselected.</param>
/// <param name="immediate">Set to true to bypass animations and update selections immediately.</param>
public void DeselectTypes(Type[] modTypes, bool immediate = false)
{
if (modTypes.Length == 0) return;
foreach (var section in ModSectionsContainer.Children)
section.DeselectTypes(modTypes, immediate);
}
protected override void LoadComplete()
{
base.LoadComplete();
Ruleset.BindValueChanged(rulesetChanged, true);
SelectedMods.BindValueChanged(selectedModsChanged, true);
}
protected override void PopOut()
{
base.PopOut();
footerContainer.MoveToX(footerContainer.DrawSize.X, WaveContainer.DISAPPEAR_DURATION, Easing.InSine);
footerContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine);
foreach (var section in ModSectionsContainer.Children)
{
section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), WaveContainer.DISAPPEAR_DURATION, Easing.InSine);
section.ButtonsContainer.MoveToX(100f, WaveContainer.DISAPPEAR_DURATION, Easing.InSine);
section.ButtonsContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine);
}
}
protected override void PopIn()
{
base.PopIn();
footerContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint);
footerContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
foreach (var section in ModSectionsContainer.Children)
{
section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), WaveContainer.APPEAR_DURATION, Easing.OutQuint);
section.ButtonsContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint);
section.ButtonsContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
}
}
protected override bool OnKeyDown(KeyDownEvent e)
{
switch (e.Key)
{
case Key.Number1:
DeselectAllButton.Click();
return true;
case Key.Number2:
CloseButton.Click();
return true;
}
return base.OnKeyDown(e);
}
private void rulesetChanged(ValueChangedEvent<RulesetInfo> e)
{
if (e.NewValue == null) return;
var instance = e.NewValue.CreateInstance();
foreach (var section in ModSectionsContainer.Children)
section.Mods = instance.GetModsFor(section.ModType);
// attempt to re-select any already selected mods.
// this may be the first time we are receiving the ruleset, in which case they will still match.
selectedModsChanged(new ValueChangedEvent<IReadOnlyList<Mod>>(SelectedMods.Value, SelectedMods.Value));
// write the mods back to the SelectedMods bindable in the case a change was not applicable.
// this generally isn't required as the previous line will perform deselection; just here for safety.
refreshSelectedMods();
}
private void selectedModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> e)
{
foreach (var section in ModSectionsContainer.Children)
section.SelectTypes(e.NewValue.Select(m => m.GetType()).ToList());
updateMods();
}
private void updateMods()
{
var multiplier = 1.0;
var ranked = true;
foreach (var mod in SelectedMods.Value)
{
multiplier *= mod.ScoreMultiplier;
ranked &= mod.Ranked;
}
MultiplierLabel.Text = $"{multiplier:N2}x";
if (multiplier > 1.0)
MultiplierLabel.FadeColour(HighMultiplierColour, 200);
else if (multiplier < 1.0)
MultiplierLabel.FadeColour(LowMultiplierColour, 200);
else
MultiplierLabel.FadeColour(Color4.White, 200);
UnrankedLabel.FadeTo(ranked ? 0 : 1, 200);
}
private void modButtonPressed(Mod selectedMod)
{
if (selectedMod != null)
{
if (State == Visibility.Visible) sampleOn?.Play();
DeselectTypes(selectedMod.IncompatibleMods, true);
}
else
{
if (State == Visibility.Visible) sampleOff?.Play();
}
refreshSelectedMods();
}
private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray();
#region Disposal
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
Ruleset.UnbindAll();
SelectedMods.UnbindAll();
}
#endregion
}
}

View File

@ -49,7 +49,6 @@ namespace osu.Game.Overlays.Profile.Sections
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 2),
},
MoreButton = new ShowMoreButton

View File

@ -31,6 +31,7 @@ namespace osu.Game.Overlays.Toolbar
public ToolbarRulesetSelector()
{
RelativeSizeAxes = Axes.Y;
AutoSizeAxes = Axes.X;
Children = new[]
{
@ -111,12 +112,6 @@ namespace osu.Game.Overlays.Toolbar
private void disabledChanged(bool isDisabled) => this.FadeColour(isDisabled ? Color4.Gray : Color4.White, 300);
protected override void Update()
{
base.Update();
Size = new Vector2(modeButtons.DrawSize.X, 1);
}
private void rulesetChanged(ValueChangedEvent<RulesetInfo> e)
{
foreach (ToolbarRulesetButton m in modeButtons.Children.Cast<ToolbarRulesetButton>())

View File

@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.UI
/// <summary>
/// The playfield.
/// </summary>
public Playfield Playfield => playfield.Value;
public override Playfield Playfield => playfield.Value;
/// <summary>
/// Place to put drawables above hit objects but below UI.
@ -342,6 +342,11 @@ namespace osu.Game.Rulesets.UI
/// </summary>
public readonly BindableBool IsPaused = new BindableBool();
/// <summary>
/// The playfield.
/// </summary>
public abstract Playfield Playfield { get; }
/// <summary>
/// The frame-stable clock which is being used for playfield display.
/// </summary>

View File

@ -0,0 +1,113 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Game.Rulesets.UI;
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Play
{
/// <summary>
/// Manage the animation to be applied when a player fails.
/// Single file; automatically disposed after use.
/// </summary>
public class FailAnimation : Component
{
public Action OnComplete;
private readonly DrawableRuleset drawableRuleset;
private readonly BindableDouble trackFreq = new BindableDouble(1);
private Track track;
private const float duration = 2500;
private SampleChannel failSample;
public FailAnimation(DrawableRuleset drawableRuleset)
{
this.drawableRuleset = drawableRuleset;
}
[BackgroundDependencyLoader]
private void load(AudioManager audio, IBindable<WorkingBeatmap> beatmap)
{
track = beatmap.Value.Track;
failSample = audio.Samples.Get(@"Gameplay/failsound");
}
private bool started;
/// <summary>
/// Start the fail animation playing.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if started more than once.</exception>
public void Start()
{
if (started) throw new InvalidOperationException("Animation cannot be started more than once.");
started = true;
failSample.Play();
this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ =>
{
OnComplete?.Invoke();
Expire();
});
track.AddAdjustment(AdjustableProperty.Frequency, trackFreq);
applyToPlayfield(drawableRuleset.Playfield);
drawableRuleset.Playfield.HitObjectContainer.FlashColour(Color4.Red, 500);
drawableRuleset.Playfield.HitObjectContainer.FadeOut(duration / 2);
}
protected override void Update()
{
base.Update();
if (!started)
return;
applyToPlayfield(drawableRuleset.Playfield);
}
private readonly List<DrawableHitObject> appliedObjects = new List<DrawableHitObject>();
private void applyToPlayfield(Playfield playfield)
{
foreach (var nested in playfield.NestedPlayfields)
applyToPlayfield(nested);
foreach (DrawableHitObject obj in playfield.HitObjectContainer.AliveObjects)
{
if (appliedObjects.Contains(obj))
continue;
obj.RotateTo(RNG.NextSingle(-90, 90), duration);
obj.ScaleTo(obj.Scale * 0.5f, duration);
obj.MoveToOffset(new Vector2(0, 400), duration);
appliedObjects.Add(obj);
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq);
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
@ -173,7 +173,8 @@ namespace osu.Game.Screens.Play
fadeOut(true);
Restart();
},
}
},
failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }
};
DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true);
@ -345,13 +346,13 @@ namespace osu.Game.Screens.Play
protected FailOverlay FailOverlay { get; private set; }
private FailAnimation failAnimation;
private bool onFail()
{
if (Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
return false;
GameplayClockContainer.Stop();
HasFailed = true;
// There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer)
@ -360,9 +361,17 @@ namespace osu.Game.Screens.Play
if (PauseOverlay.State == Visibility.Visible)
PauseOverlay.Hide();
failAnimation.Start();
return true;
}
// Called back when the transform finishes
private void onFailComplete()
{
GameplayClockContainer.Stop();
FailOverlay.Retries = RestartCount;
FailOverlay.Show();
return true;
}
#endregion
@ -489,6 +498,13 @@ namespace osu.Game.Screens.Play
// still want to block if we are within the cooldown period and not already paused.
return true;
if (HasFailed && ValidForResume && !FailOverlay.IsPresent)
// ValidForResume is false when restarting
{
failAnimation.FinishTransforms(true);
return true;
}
GameplayClockContainer.ResetLocalAdjustments();
fadeOut();

View File

@ -3,8 +3,13 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing.Input;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual
{
@ -15,21 +20,92 @@ namespace osu.Game.Tests.Visual
protected readonly ManualInputManager InputManager;
private readonly TriangleButton buttonTest;
private readonly TriangleButton buttonLocal;
protected ManualInputManagerTestScene()
{
base.Content.Add(InputManager = new ManualInputManager
base.Content.AddRange(new Drawable[]
{
UseParentInput = true,
Child = content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both },
InputManager = new ManualInputManager
{
UseParentInput = true,
Child = content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both },
},
new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Margin = new MarginPadding(5),
CornerRadius = 5,
Masking = true,
Children = new Drawable[]
{
new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
Alpha = 0.5f,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Margin = new MarginPadding(5),
Spacing = new Vector2(5),
Children = new Drawable[]
{
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "Input Priority"
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Margin = new MarginPadding(5),
Spacing = new Vector2(5),
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
buttonLocal = new TriangleButton
{
Text = "local",
Size = new Vector2(50, 30),
Action = returnUserInput
},
buttonTest = new TriangleButton
{
Text = "test",
Size = new Vector2(50, 30),
Action = returnTestInput
},
}
},
}
},
}
},
});
}
/// <summary>
/// Returns input back to the user.
/// </summary>
protected void ReturnUserInput()
protected override void Update()
{
AddStep("Return user input", () => InputManager.UseParentInput = true);
base.Update();
buttonTest.Enabled.Value = InputManager.UseParentInput;
buttonLocal.Enabled.Value = !InputManager.UseParentInput;
}
private void returnUserInput() =>
InputManager.UseParentInput = true;
private void returnTestInput() =>
InputManager.UseParentInput = false;
}
}

View File

@ -15,7 +15,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.4" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.518.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.606.1" />
<PackageReference Include="ppy.osu.Framework" Version="2019.607.0" />
<PackageReference Include="SharpCompress" Version="0.23.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />

View File

@ -104,9 +104,9 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.128.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.606.1" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.606.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.518.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.607.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.607.0" />
<PackageReference Include="SharpCompress" Version="0.22.0" />
<PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />