mirror of
https://github.com/ppy/osu.git
synced 2024-12-17 16:52:57 +08:00
7c0550d251
Fix oversight in osu! pause input handling
389 lines
17 KiB
C#
389 lines
17 KiB
C#
// 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.Linq;
|
|
using NUnit.Framework;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Audio;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Testing;
|
|
using osu.Framework.Timing;
|
|
using osu.Game.Beatmaps;
|
|
using osu.Game.Configuration;
|
|
using osu.Game.Rulesets;
|
|
using osu.Game.Rulesets.Mania;
|
|
using osu.Game.Rulesets.Osu;
|
|
using osu.Game.Rulesets.Osu.Objects;
|
|
using osu.Game.Rulesets.Osu.UI;
|
|
using osu.Game.Screens.Play.HUD;
|
|
using osu.Game.Skinning;
|
|
using osu.Game.Storyboards;
|
|
using osuTK.Input;
|
|
|
|
namespace osu.Game.Tests.Visual.Gameplay
|
|
{
|
|
public partial class TestScenePauseInputHandling : PlayerTestScene
|
|
{
|
|
private Ruleset currentRuleset = new OsuRuleset();
|
|
|
|
protected override Ruleset CreatePlayerRuleset() => currentRuleset;
|
|
|
|
protected override bool HasCustomSteps => true;
|
|
|
|
[Resolved]
|
|
private AudioManager audioManager { get; set; } = null!;
|
|
|
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
|
|
{
|
|
HitObjects =
|
|
{
|
|
new HitCircle
|
|
{
|
|
Position = OsuPlayfield.BASE_SIZE / 2,
|
|
StartTime = 0,
|
|
},
|
|
new HitCircle
|
|
{
|
|
Position = OsuPlayfield.BASE_SIZE / 2,
|
|
StartTime = 5000,
|
|
},
|
|
new HitCircle
|
|
{
|
|
Position = OsuPlayfield.BASE_SIZE / 2,
|
|
StartTime = 10000,
|
|
},
|
|
new HitCircle
|
|
{
|
|
Position = OsuPlayfield.BASE_SIZE / 2,
|
|
StartTime = 15000,
|
|
}
|
|
}
|
|
};
|
|
|
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) =>
|
|
new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
|
|
|
[SetUp]
|
|
public void SetUp() => Schedule(() =>
|
|
{
|
|
foreach (var key in InputManager.CurrentState.Keyboard.Keys)
|
|
InputManager.ReleaseKey(key);
|
|
|
|
InputManager.MoveMouseTo(Content);
|
|
LocalConfig.SetValue(OsuSetting.KeyOverlay, true);
|
|
});
|
|
|
|
[Test]
|
|
public void TestOsuInputNotReceivedWhilePaused()
|
|
{
|
|
KeyCounter counter = null!;
|
|
|
|
loadPlayer(() => new OsuRuleset());
|
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.LeftButton));
|
|
checkKey(() => counter, 0, false);
|
|
|
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
|
checkKey(() => counter, 1, true);
|
|
|
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
checkKey(() => counter, 1, false);
|
|
|
|
AddStep("pause", () => Player.Pause());
|
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
|
checkKey(() => counter, 1, false);
|
|
|
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
checkKey(() => counter, 1, false);
|
|
|
|
AddStep("resume", () => Player.Resume());
|
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
|
checkKey(() => counter, 2, true);
|
|
|
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
checkKey(() => counter, 2, false);
|
|
|
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
|
checkKey(() => counter, 3, true);
|
|
|
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
checkKey(() => counter, 3, false);
|
|
}
|
|
|
|
[Test]
|
|
public void TestManiaInputNotReceivedWhilePaused()
|
|
{
|
|
KeyCounter counter = null!;
|
|
|
|
loadPlayer(() => new ManiaRuleset());
|
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key4));
|
|
checkKey(() => counter, 0, false);
|
|
|
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
|
checkKey(() => counter, 1, true);
|
|
|
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
|
checkKey(() => counter, 1, false);
|
|
|
|
AddStep("pause", () => Player.Pause());
|
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
|
checkKey(() => counter, 1, false);
|
|
|
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
|
checkKey(() => counter, 1, false);
|
|
|
|
AddStep("resume", () => Player.Resume());
|
|
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
|
|
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
|
checkKey(() => counter, 2, true);
|
|
|
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
|
checkKey(() => counter, 2, false);
|
|
}
|
|
|
|
[Test]
|
|
public void TestOsuPreviouslyHeldInputReleaseOnResume()
|
|
{
|
|
KeyCounter counterZ = null!;
|
|
KeyCounter counterX = null!;
|
|
|
|
loadPlayer(() => new OsuRuleset());
|
|
AddStep("get key counter Z", () => counterZ = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.LeftButton));
|
|
AddStep("get key counter X", () => counterX = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.RightButton));
|
|
|
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
|
AddStep("pause", () => Player.Pause());
|
|
|
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
|
|
AddStep("resume", () => Player.Resume());
|
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
|
AddStep("press and release Z", () => InputManager.Key(Key.Z));
|
|
checkKey(() => counterZ, 1, false);
|
|
|
|
AddStep("press X", () => InputManager.PressKey(Key.X));
|
|
AddStep("pause", () => Player.Pause());
|
|
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
|
checkKey(() => counterX, 1, true);
|
|
|
|
AddStep("resume", () => Player.Resume());
|
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
|
checkKey(() => counterZ, 2, true);
|
|
checkKey(() => counterX, 1, false);
|
|
|
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
checkKey(() => counterZ, 2, false);
|
|
}
|
|
|
|
[Test]
|
|
public void TestManiaPreviouslyHeldInputReleaseOnResume()
|
|
{
|
|
KeyCounter counter = null!;
|
|
|
|
loadPlayer(() => new ManiaRuleset());
|
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key4));
|
|
|
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
|
AddStep("pause", () => Player.Pause());
|
|
|
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
|
checkKey(() => counter, 1, true);
|
|
|
|
AddStep("resume", () => Player.Resume());
|
|
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
|
checkKey(() => counter, 1, false);
|
|
}
|
|
|
|
[Test]
|
|
public void TestOsuHeldInputRemainHeldAfterResume()
|
|
{
|
|
KeyCounter counterZ = null!;
|
|
KeyCounter counterX = null!;
|
|
|
|
loadPlayer(() => new OsuRuleset());
|
|
AddStep("get key counter Z", () => counterZ = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.LeftButton));
|
|
AddStep("get key counter X", () => counterX = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.RightButton));
|
|
|
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
|
AddStep("pause", () => Player.Pause());
|
|
|
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
|
|
AddStep("resume", () => Player.Resume());
|
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
|
checkKey(() => counterZ, 1, true);
|
|
|
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
checkKey(() => counterZ, 1, false);
|
|
|
|
AddStep("press X", () => InputManager.PressKey(Key.X));
|
|
checkKey(() => counterX, 1, true);
|
|
|
|
AddStep("pause", () => Player.Pause());
|
|
|
|
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
|
AddStep("press X", () => InputManager.PressKey(Key.X));
|
|
|
|
AddStep("resume", () => Player.Resume());
|
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
|
checkKey(() => counterZ, 2, true);
|
|
checkKey(() => counterX, 1, true);
|
|
|
|
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
|
checkKey(() => counterX, 1, false);
|
|
|
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
checkKey(() => counterZ, 2, false);
|
|
}
|
|
|
|
[Test]
|
|
public void TestManiaHeldInputRemainHeldAfterResume()
|
|
{
|
|
KeyCounter counter = null!;
|
|
|
|
loadPlayer(() => new ManiaRuleset());
|
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key4));
|
|
|
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
|
checkKey(() => counter, 1, true);
|
|
|
|
AddStep("pause", () => Player.Pause());
|
|
|
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
|
|
|
AddStep("resume", () => Player.Resume());
|
|
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
|
checkKey(() => counter, 1, true);
|
|
|
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
|
checkKey(() => counter, 1, false);
|
|
}
|
|
|
|
[Test]
|
|
public void TestOsuHitCircleNotReceivingInputOnResume()
|
|
{
|
|
KeyCounter counter = null!;
|
|
|
|
loadPlayer(() => new OsuRuleset());
|
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.LeftButton));
|
|
|
|
AddStep("pause", () => Player.Pause());
|
|
AddStep("resume", () => Player.Resume());
|
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
|
|
|
// ensure the input manager receives the Z button press...
|
|
checkKey(() => counter, 1, true);
|
|
AddAssert("button is pressed in kbc", () => Player.DrawableRuleset.Playfield.FindClosestParent<OsuInputManager>()!.PressedActions.Single() == OsuAction.LeftButton);
|
|
|
|
// ...but also ensure the hit circle in front of the cursor isn't hit by checking max combo.
|
|
AddAssert("circle not hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(0));
|
|
|
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
|
|
checkKey(() => counter, 1, false);
|
|
AddAssert("button is released in kbc", () => !Player.DrawableRuleset.Playfield.FindClosestParent<OsuInputManager>()!.PressedActions.Any());
|
|
}
|
|
|
|
[Test]
|
|
public void TestOsuHitCircleNotReceivingInputOnResume_PauseWhileHoldingSameKey()
|
|
{
|
|
KeyCounter counter = null!;
|
|
|
|
loadPlayer(() => new OsuRuleset());
|
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.LeftButton));
|
|
|
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
|
AddAssert("circle hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(1));
|
|
|
|
AddStep("pause", () => Player.Pause());
|
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
|
|
AddStep("resume", () => Player.Resume());
|
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
|
|
checkKey(() => counter, 1, false);
|
|
|
|
seekTo(5000);
|
|
|
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
|
|
|
checkKey(() => counter, 2, true);
|
|
AddAssert("circle hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(2));
|
|
|
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
checkKey(() => counter, 2, false);
|
|
}
|
|
|
|
[Test]
|
|
public void TestOsuHitCircleNotReceivingInputOnResume_PauseWhileHoldingOtherKey()
|
|
{
|
|
loadPlayer(() => new OsuRuleset());
|
|
|
|
AddStep("press X", () => InputManager.PressKey(Key.X));
|
|
AddAssert("circle hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(1));
|
|
|
|
seekTo(5000);
|
|
|
|
AddStep("pause", () => Player.Pause());
|
|
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
|
|
|
AddStep("resume", () => Player.Resume());
|
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
|
|
AddAssert("circle not hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(1));
|
|
|
|
AddStep("press X", () => InputManager.PressKey(Key.X));
|
|
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
|
|
|
AddAssert("circle hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(2));
|
|
}
|
|
|
|
private void loadPlayer(Func<Ruleset> createRuleset)
|
|
{
|
|
AddStep("set ruleset", () => currentRuleset = createRuleset());
|
|
AddStep("load player", LoadPlayer);
|
|
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
|
AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType<SkinnableContainer>().All(s => s.ComponentsLoaded));
|
|
|
|
seekTo(0);
|
|
AddAssert("not in break", () => !Player.IsBreakTime.Value);
|
|
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield));
|
|
}
|
|
|
|
private void seekTo(double time)
|
|
{
|
|
AddStep($"seek to {time}ms", () => Player.GameplayClockContainer.Seek(time));
|
|
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(time).Within(500));
|
|
}
|
|
|
|
private void checkKey(Func<KeyCounter> counter, int count, bool active)
|
|
{
|
|
AddAssert($"key count = {count}", () => counter().CountPresses.Value, () => Is.EqualTo(count));
|
|
AddAssert($"key active = {active}", () => counter().IsActive.Value, () => Is.EqualTo(active));
|
|
}
|
|
|
|
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new PausePlayer();
|
|
|
|
private partial class PausePlayer : TestPlayer
|
|
{
|
|
protected override double PauseCooldownDuration => 0;
|
|
|
|
public PausePlayer()
|
|
: base(allowPause: true, showResults: false)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
}
|