Merge branch 'master' into add-accuracyheatmap-test

This commit is contained in:
Bartłomiej Dach 2020-11-12 15:50:48 +01:00 committed by GitHub
commit 9e467f8812
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 300 additions and 53 deletions

View File

@ -125,6 +125,9 @@ public Drawable GetDrawableComponent(ISkinComponent component)
{
if (!enabled) return null;
if (component is OsuSkinComponent osuComponent && osuComponent.Component == OsuSkinComponents.SliderBody)
return null;
return new OsuSpriteText
{
Text = identifier,

View File

@ -27,17 +27,23 @@ public class OsuModHidden : ModHidden
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var d in drawables)
d.ApplyCustomUpdateState += applyFadeInAdjustment;
{
d.HitObjectApplied += applyFadeInAdjustment;
applyFadeInAdjustment(d);
}
base.ApplyToDrawableHitObjects(drawables);
}
private void applyFadeInAdjustment(DrawableHitObject hitObject, ArmedState state)
private void applyFadeInAdjustment(DrawableHitObject hitObject)
{
if (!(hitObject is DrawableOsuHitObject d))
return;
d.HitObject.TimeFadeIn = d.HitObject.TimePreempt * fade_in_duration_multiplier;
foreach (var nested in d.NestedHitObjects)
applyFadeInAdjustment(nested);
}
private double lastSliderHeadFadeOutStartTime;

View File

@ -49,7 +49,7 @@ private void load(GameHost host)
}
[SetUpSteps]
public void SetUpSteps()
public virtual void SetUpSteps()
{
AddStep("Create new game instance", () =>
{

View File

@ -2,23 +2,33 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Overlays;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Navigation
{
public class TestScenePerformFromScreen : OsuGameTestScene
{
private bool actionPerformed;
public override void SetUpSteps()
{
AddStep("reset status", () => actionPerformed = false);
base.SetUpSteps();
}
[Test]
public void TestPerformAtMenu()
{
AddAssert("could perform immediately", () =>
{
bool actionPerformed = false;
Game.PerformFromScreen(_ => actionPerformed = true);
return actionPerformed;
});
AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddAssert("did perform", () => actionPerformed);
}
[Test]
@ -26,12 +36,9 @@ public void TestPerformAtSongSelect()
{
PushAndConfirm(() => new PlaySongSelect());
AddAssert("could perform immediately", () =>
{
bool actionPerformed = false;
Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) });
return actionPerformed;
});
AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }));
AddAssert("did perform", () => actionPerformed);
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
}
[Test]
@ -39,7 +46,6 @@ public void TestPerformAtMenuFromSongSelect()
{
PushAndConfirm(() => new PlaySongSelect());
bool actionPerformed = false;
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddUntilStep("returned to menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
AddAssert("did perform", () => actionPerformed);
@ -51,7 +57,6 @@ public void TestPerformAtSongSelectFromPlayerLoader()
PushAndConfirm(() => new PlaySongSelect());
PushAndConfirm(() => new PlayerLoader(() => new Player()));
bool actionPerformed = false;
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }));
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
AddAssert("did perform", () => actionPerformed);
@ -63,10 +68,105 @@ public void TestPerformAtMenuFromPlayerLoader()
PushAndConfirm(() => new PlaySongSelect());
PushAndConfirm(() => new PlayerLoader(() => new Player()));
bool actionPerformed = false;
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu);
AddAssert("did perform", () => actionPerformed);
}
[TestCase(true)]
[TestCase(false)]
public void TestPerformBlockedByDialog(bool confirmed)
{
DialogBlockingScreen blocker = null;
PushAndConfirm(() => blocker = new DialogBlockingScreen());
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddWaitStep("wait a bit", 10);
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is DialogBlockingScreen);
AddAssert("did not perform", () => !actionPerformed);
AddAssert("only one exit attempt", () => blocker.ExitAttempts == 1);
AddUntilStep("wait for dialog display", () => Game.Dependencies.Get<DialogOverlay>().IsLoaded);
if (confirmed)
{
AddStep("accept dialog", () => InputManager.Key(Key.Number1));
AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get<DialogOverlay>().CurrentDialog == null);
AddUntilStep("did perform", () => actionPerformed);
}
else
{
AddStep("cancel dialog", () => InputManager.Key(Key.Number2));
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is DialogBlockingScreen);
AddAssert("did not perform", () => !actionPerformed);
}
}
[TestCase(true)]
[TestCase(false)]
public void TestPerformBlockedByDialogNested(bool confirmSecond)
{
DialogBlockingScreen blocker = null;
DialogBlockingScreen blocker2 = null;
PushAndConfirm(() => blocker = new DialogBlockingScreen());
PushAndConfirm(() => blocker2 = new DialogBlockingScreen());
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddUntilStep("wait for dialog", () => blocker2.ExitAttempts == 1);
AddWaitStep("wait a bit", 10);
AddUntilStep("wait for dialog display", () => Game.Dependencies.Get<DialogOverlay>().IsLoaded);
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen == blocker2);
AddAssert("did not perform", () => !actionPerformed);
AddAssert("only one exit attempt", () => blocker2.ExitAttempts == 1);
AddStep("accept dialog", () => InputManager.Key(Key.Number1));
AddUntilStep("screen changed", () => Game.ScreenStack.CurrentScreen == blocker);
AddUntilStep("wait for second dialog", () => blocker.ExitAttempts == 1);
AddAssert("did not perform", () => !actionPerformed);
AddAssert("only one exit attempt", () => blocker.ExitAttempts == 1);
if (confirmSecond)
{
AddStep("accept dialog", () => InputManager.Key(Key.Number1));
AddUntilStep("did perform", () => actionPerformed);
}
else
{
AddStep("cancel dialog", () => InputManager.Key(Key.Number2));
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen == blocker);
AddAssert("did not perform", () => !actionPerformed);
}
}
public class DialogBlockingScreen : OsuScreen
{
[Resolved]
private DialogOverlay dialogOverlay { get; set; }
private int dialogDisplayCount;
public int ExitAttempts { get; private set; }
public override bool OnExiting(IScreen next)
{
ExitAttempts++;
if (dialogDisplayCount++ < 1)
{
dialogOverlay.Push(new ConfirmExitDialog(this.Exit, () => { }));
return true;
}
return base.OnExiting(next);
}
}
}
}

View File

@ -16,8 +16,6 @@ namespace osu.Game.Beatmaps.Formats
{
public class LegacyBeatmapDecoder : LegacyDecoder<Beatmap>
{
public const int LATEST_VERSION = 14;
private Beatmap beatmap;
private ConvertHitObjectParser parser;

View File

@ -16,6 +16,8 @@ namespace osu.Game.Beatmaps.Formats
public abstract class LegacyDecoder<T> : Decoder<T>
where T : new()
{
public const int LATEST_VERSION = 14;
protected readonly int FormatVersion;
protected LegacyDecoder(int version)

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Beatmaps.Legacy;
@ -23,15 +24,15 @@ public class LegacyStoryboardDecoder : LegacyDecoder<Storyboard>
private readonly Dictionary<string, string> variables = new Dictionary<string, string>();
public LegacyStoryboardDecoder()
: base(0)
public LegacyStoryboardDecoder(int version = LATEST_VERSION)
: base(version)
{
}
public static void Register()
{
// note that this isn't completely correct
AddDecoder<Storyboard>(@"osu file format v", m => new LegacyStoryboardDecoder());
AddDecoder<Storyboard>(@"osu file format v", m => new LegacyStoryboardDecoder(Parsing.ParseInt(m.Split('v').Last())));
AddDecoder<Storyboard>(@"[Events]", m => new LegacyStoryboardDecoder());
SetFallbackDecoder<Storyboard>(() => new LegacyStoryboardDecoder());
}
@ -133,6 +134,11 @@ private void handleEvents(string line)
var y = Parsing.ParseFloat(split[5], Parsing.MAX_COORDINATE_VALUE);
var frameCount = Parsing.ParseInt(split[6]);
var frameDelay = Parsing.ParseDouble(split[7]);
if (FormatVersion < 6)
// this is random as hell but taken straight from osu-stable.
frameDelay = Math.Round(0.015 * frameDelay) * 1.186 * (1000 / 60f);
var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever;
storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType);
storyboard.GetLayer(layer).Add(storyboardSprite);

View File

@ -463,7 +463,7 @@ private void updateModDefaults()
#endregion
private ScheduledDelegate performFromMainMenuTask;
private PerformFromMenuRunner performFromMainMenuTask;
/// <summary>
/// Perform an action only after returning to a specific screen as indicated by <paramref name="validScreens"/>.
@ -474,34 +474,7 @@ private void updateModDefaults()
public void PerformFromScreen(Action<IScreen> action, IEnumerable<Type> validScreens = null)
{
performFromMainMenuTask?.Cancel();
validScreens ??= Enumerable.Empty<Type>();
validScreens = validScreens.Append(typeof(MainMenu));
CloseAllOverlays(false);
// we may already be at the target screen type.
if (validScreens.Contains(ScreenStack.CurrentScreen?.GetType()) && !Beatmap.Disabled)
{
action(ScreenStack.CurrentScreen);
return;
}
// find closest valid target
IScreen screen = ScreenStack.CurrentScreen;
while (screen != null)
{
if (validScreens.Contains(screen.GetType()))
{
screen.MakeCurrent();
break;
}
screen = screen.GetParentScreen();
}
performFromMainMenuTask = Schedule(() => PerformFromScreen(action, validScreens));
Add(performFromMainMenuTask = new PerformFromMenuRunner(action, validScreens, () => ScreenStack.CurrentScreen));
}
/// <summary>

View File

@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer<APIUserMostPlayedBeatmap>
{
public PaginatedMostPlayedBeatmapContainer(Bindable<User> user)
: base(user, "Most Played Beatmaps", "No records. :(")
: base(user, "Most Played Beatmaps", "No records. :(", CounterVisibilityState.AlwaysVisible)
{
ItemsPerPage = 5;
}
@ -27,6 +27,8 @@ private void load()
ItemsContainer.Direction = FillDirection.Vertical;
}
protected override int GetCount(User user) => user.BeatmapPlaycountsCount;
protected override APIRequest<List<APIUserMostPlayedBeatmap>> CreateRequest() =>
new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage);

View File

@ -0,0 +1,145 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens.Menu;
namespace osu.Game
{
internal class PerformFromMenuRunner : Component
{
private readonly Action<IScreen> finalAction;
private readonly Type[] validScreens;
private readonly Func<IScreen> getCurrentScreen;
[Resolved]
private NotificationOverlay notifications { get; set; }
[Resolved]
private DialogOverlay dialogOverlay { get; set; }
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; }
[Resolved(canBeNull: true)]
private OsuGame game { get; set; }
private readonly ScheduledDelegate task;
private PopupDialog lastEncounteredDialog;
private IScreen lastEncounteredDialogScreen;
/// <summary>
/// Perform an action only after returning to a specific screen as indicated by <paramref name="validScreens"/>.
/// Eagerly tries to exit the current screen until it succeeds.
/// </summary>
/// <param name="finalAction">The action to perform once we are in the correct state.</param>
/// <param name="validScreens">An optional collection of valid screen types. If any of these screens are already current we can perform the action immediately, else the first valid parent will be made current before performing the action. <see cref="MainMenu"/> is used if not specified.</param>
/// <param name="getCurrentScreen">A function to retrieve the currently displayed game screen.</param>
public PerformFromMenuRunner(Action<IScreen> finalAction, IEnumerable<Type> validScreens, Func<IScreen> getCurrentScreen)
{
validScreens ??= Enumerable.Empty<Type>();
validScreens = validScreens.Append(typeof(MainMenu));
this.finalAction = finalAction;
this.validScreens = validScreens.ToArray();
this.getCurrentScreen = getCurrentScreen;
Scheduler.Add(task = new ScheduledDelegate(checkCanComplete, 0, 200));
}
/// <summary>
/// Cancel this runner from running.
/// </summary>
public void Cancel()
{
task.Cancel();
Expire();
}
private void checkCanComplete()
{
// find closest valid target
IScreen current = getCurrentScreen();
// a dialog may be blocking the execution for now.
if (checkForDialog(current)) return;
// we may already be at the target screen type.
if (validScreens.Contains(getCurrentScreen().GetType()) && !beatmap.Disabled)
{
complete();
return;
}
game.CloseAllOverlays(false);
while (current != null)
{
if (validScreens.Contains(current.GetType()))
{
current.MakeCurrent();
break;
}
current = current.GetParentScreen();
}
}
/// <summary>
/// Check whether there is currently a dialog requiring user interaction.
/// </summary>
/// <param name="current"></param>
/// <returns>Whether a dialog blocked interaction.</returns>
private bool checkForDialog(IScreen current)
{
var currentDialog = dialogOverlay.CurrentDialog;
if (lastEncounteredDialog != null)
{
if (lastEncounteredDialog == currentDialog)
// still waiting on user interaction
return true;
if (lastEncounteredDialogScreen != current)
{
// a dialog was previously encountered but has since been dismissed.
// if the screen changed, the user likely confirmed an exit dialog and we should continue attempting the action.
lastEncounteredDialog = null;
lastEncounteredDialogScreen = null;
return false;
}
// the last dialog encountered has been dismissed but the screen has not changed, abort.
Cancel();
notifications.Post(new SimpleNotification { Text = @"An action was interrupted due to a dialog being displayed." });
return true;
}
if (currentDialog == null)
return false;
// a new dialog was encountered.
lastEncounteredDialog = currentDialog;
lastEncounteredDialogScreen = current;
return true;
}
private void complete()
{
finalAction(getCurrentScreen());
Cancel();
}
}
}

View File

@ -26,8 +26,16 @@ namespace osu.Game.Rulesets.Objects.Drawables
[Cached(typeof(DrawableHitObject))]
public abstract class DrawableHitObject : SkinReloadableDrawable
{
/// <summary>
/// Invoked after this <see cref="DrawableHitObject"/>'s applied <see cref="HitObject"/> has had its defaults applied.
/// </summary>
public event Action<DrawableHitObject> DefaultsApplied;
/// <summary>
/// Invoked after a <see cref="HitObject"/> has been applied to this <see cref="DrawableHitObject"/>.
/// </summary>
public event Action<DrawableHitObject> HitObjectApplied;
/// <summary>
/// The <see cref="HitObject"/> currently represented by this <see cref="DrawableHitObject"/>.
/// </summary>
@ -192,6 +200,7 @@ public void Apply(HitObject hitObject)
HitObject.DefaultsApplied += onDefaultsApplied;
OnApply(hitObject);
HitObjectApplied?.Invoke(this);
// If not loaded, the state update happens in LoadComplete(). Otherwise, the update is scheduled to allow for lifetime updates.
if (IsLoaded)

View File

@ -144,6 +144,9 @@ public class UserCover
[JsonProperty(@"scores_first_count")]
public int ScoresFirstCount;
[JsonProperty(@"beatmap_playcounts_count")]
public int BeatmapPlaycountsCount;
[JsonProperty]
private string[] playstyle
{