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

refactor: decouple Trigger logic from KeyCounterDisplay

This allows to keep a coeherent state regardless of the progress of the play
This commit is contained in:
tsrk 2023-06-14 21:13:35 +02:00
parent e9ef270e46
commit c637fddf73
No known key found for this signature in database
GPG Key ID: EBD46BB3049B56D6
12 changed files with 180 additions and 137 deletions

View File

@ -13,7 +13,6 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.Break;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Ranking;
using osu.Game.Users.Drawables;
@ -36,14 +35,14 @@ namespace osu.Game.Tests.Visual.Gameplay
var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.ChildrenOfType<KeyCounterDisplay>().FirstOrDefault()?.Counters.Any(kc => kc.CountPresses.Value > 2) ?? false);
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Triggers.Any(kc => kc.ActivationCount.Value > 2));
seekTo(referenceBeatmap.Breaks[0].StartTime);
AddAssert("keys not counting", () => !Player.HUDOverlay.ChildrenOfType<KeyCounterDisplay>().FirstOrDefault()?.IsCounting.Value ?? false);
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting.Value);
AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType<BreakInfo>().Single().AccuracyDisplay.Current.Value == 1);
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
AddUntilStep("key counter reset", () => Player.HUDOverlay.ChildrenOfType<KeyCounterDisplay>().FirstOrDefault()?.Counters.All(kc => kc.CountPresses.Value == 0) ?? false);
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Triggers.All(kc => kc.ActivationCount.Value == 0));
seekTo(referenceBeatmap.HitObjects[^1].GetEndTime());
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);

View File

@ -7,13 +7,11 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Play.HUD;
using osu.Game.Storyboards;
using osuTK;
@ -33,11 +31,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
addSeekStep(3000);
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.ChildrenOfType<KeyCounterDisplay>()?.FirstOrDefault()?.Counters.Select(kc => kc.CountPresses.Value).Sum() == 15);
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Triggers.Select(kc => kc.ActivationCount.Value).Sum() == 15);
AddStep("clear results", () => Player.Results.Clear());
addSeekStep(0);
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
AddUntilStep("key counters reset", () => Player.HUDOverlay.ChildrenOfType<KeyCounterDisplay>().FirstOrDefault()?.Counters.All(kc => kc.CountPresses.Value == 0) ?? false);
AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Triggers.All(kc => kc.ActivationCount.Value == 0));
AddAssert("no results triggered", () => Player.Results.Count == 0);
}

View File

@ -6,7 +6,6 @@ using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -270,7 +269,7 @@ namespace osu.Game.Tests.Visual.Gameplay
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
// Add any key just to display the key counter visually.
hudOverlay.ChildrenOfType<KeyCounterDisplay>().ForEach(k => k.Add(new KeyCounterKeyboardTrigger(Key.Space)));
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
scoreProcessor.Combo.Value = 1;

View File

@ -5,7 +5,9 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
@ -17,64 +19,69 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public partial class TestSceneKeyCounter : OsuManualInputManagerTestScene
{
[Cached]
private readonly KeyCounterController controller;
private readonly KeyCounterDisplay defaultDisplay;
public TestSceneKeyCounter()
{
KeyCounterDisplay defaultDisplay = new DefaultKeyCounterDisplay
Children = new Drawable[]
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Position = new Vector2(0, 72.7f)
controller = new KeyCounterController(),
new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(72.7f),
Children = new[]
{
defaultDisplay = new DefaultKeyCounterDisplay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
},
new ArgonKeyCounterDisplay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
}
}
}
};
KeyCounterDisplay argonDisplay = new ArgonKeyCounterDisplay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Position = new Vector2(0, -72.7f)
};
defaultDisplay.AddRange(new InputTrigger[]
{
new KeyCounterKeyboardTrigger(Key.X),
new KeyCounterKeyboardTrigger(Key.X),
new KeyCounterMouseTrigger(MouseButton.Left),
new KeyCounterMouseTrigger(MouseButton.Right),
});
argonDisplay.AddRange(new InputTrigger[]
controller.AddRange(new InputTrigger[]
{
new KeyCounterKeyboardTrigger(Key.X),
new KeyCounterKeyboardTrigger(Key.X),
new KeyCounterMouseTrigger(MouseButton.Left),
new KeyCounterMouseTrigger(MouseButton.Right),
});
}
[Test]
public void TestDoThings()
{
var testCounter = (DefaultKeyCounter)defaultDisplay.Counters.First();
AddStep("Add random", () =>
{
Key key = (Key)((int)Key.A + RNG.Next(26));
defaultDisplay.Add(new KeyCounterKeyboardTrigger(key));
argonDisplay.Add(new KeyCounterKeyboardTrigger(key));
controller.Add(new KeyCounterKeyboardTrigger(key));
});
Key testKey = ((KeyCounterKeyboardTrigger)defaultDisplay.Counters.First().Trigger).Key;
Key testKey = ((KeyCounterKeyboardTrigger)controller.Triggers.First()).Key;
addPressKeyStep();
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1);
addPressKeyStep();
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2);
AddStep("Disable counting", () =>
{
argonDisplay.IsCounting.Value = false;
defaultDisplay.IsCounting.Value = false;
});
AddStep("Disable counting", () => controller.IsCounting.Value = false);
addPressKeyStep();
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2);
Add(defaultDisplay);
Add(argonDisplay);
void addPressKeyStep() => AddStep($"Press {testKey} key", () => InputManager.Key(testKey));
}
}

View File

@ -6,13 +6,11 @@
using System;
using System.ComponentModel;
using System.Linq;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Tests.Visual.Gameplay
{
@ -29,8 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void AddCheckSteps()
{
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.ChildrenOfType<KeyCounterDisplay>().FirstOrDefault()?.Counters.Any(kc => kc.CountPresses
.Value > 0) ?? false);
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Triggers.Any(kc => kc.ActivationCount.Value > 0));
AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
}

View File

@ -14,7 +14,9 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Tests.Gameplay;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
@ -55,6 +57,11 @@ namespace osu.Game.Tests.Visual.Gameplay
Origin = Anchor.Centre,
};
// Add any key just to display the key counter visually.
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
scoreProcessor.Combo.Value = 1;
return new Container
{
RelativeSizeAxes = Axes.Both,

View File

@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Gameplay
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
// Add any key just to display the key counter visually.
hudOverlay.ChildrenOfType<KeyCounterDisplay>().ForEach(k => k.Add(new KeyCounterKeyboardTrigger(Key.Space)));
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
action?.Invoke(hudOverlay);

View File

@ -164,9 +164,8 @@ namespace osu.Game.Rulesets.UI
{
switch (skinComponent)
{
case KeyCounterDisplay keyCounterDisplay:
attachKeyCounter(keyCounterDisplay);
break;
case KeyCounterController keyCounterDisplay:
attachKeyCounter(keyCounterDisplay); break;
case ClicksPerSecondCalculator clicksPerSecondCalculator:
attachClicksPerSecond(clicksPerSecondCalculator);
@ -178,7 +177,7 @@ namespace osu.Game.Rulesets.UI
{
switch (skinComponent)
{
case KeyCounterDisplay keyCounterDisplay:
case KeyCounterController keyCounterDisplay:
detachKeyCounter(keyCounterDisplay);
break;
@ -192,7 +191,7 @@ namespace osu.Game.Rulesets.UI
#region Key Counter Attachment
private void attachKeyCounter(KeyCounterDisplay keyCounter)
private void attachKeyCounter(KeyCounterController keyCounter)
{
var receptor = new ActionReceptor(keyCounter);
@ -206,25 +205,25 @@ namespace osu.Game.Rulesets.UI
.Select(action => new KeyCounterActionTrigger<T>(action)));
}
private void detachKeyCounter(KeyCounterDisplay keyCounter)
private void detachKeyCounter(KeyCounterController keyCounter)
{
keyCounter.ClearReceptor();
}
private partial class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler<T>
private partial class ActionReceptor : KeyCounterController.Receptor, IKeyBindingHandler<T>
{
public ActionReceptor(KeyCounterDisplay target)
public ActionReceptor(KeyCounterController target)
: base(target)
{
}
public bool OnPressed(KeyBindingPressEvent<T> e) => Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger<T>)
.Select(c => (KeyCounterActionTrigger<T>)c.Trigger)
public bool OnPressed(KeyBindingPressEvent<T> e) => Target.Triggers
.OfType<KeyCounterActionTrigger<T>>()
.Any(c => c.OnPressed(e.Action, Clock.Rate >= 0));
public void OnReleased(KeyBindingReleaseEvent<T> e)
{
foreach (var c
in Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger<T>).Select(c => (KeyCounterActionTrigger<T>)c.Trigger))
foreach (var c in Target.Triggers.OfType<KeyCounterActionTrigger<T>>())
c.OnReleased(e.Action, Clock.Rate >= 0);
}
}

View File

@ -0,0 +1,95 @@
// 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.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public partial class KeyCounterController : CompositeComponent, IAttachableSkinComponent
{
public readonly Bindable<bool> IsCounting = new BindableBool(true);
private Receptor? receptor;
public event Action<InputTrigger>? OnNewTrigger;
private readonly Container<InputTrigger> triggers;
public IReadOnlyList<InputTrigger> Triggers => triggers;
public KeyCounterController()
{
InternalChild = triggers = new Container<InputTrigger>();
}
public void Add(InputTrigger trigger)
{
triggers.Add(trigger);
trigger.IsCounting.BindTo(IsCounting);
OnNewTrigger?.Invoke(trigger);
}
public void AddRange(IEnumerable<InputTrigger> inputTriggers) => inputTriggers.ForEach(Add);
/// <summary>
/// Sets a <see cref="Receptor"/> that will populate keybinding events to this <see cref="KeyCounterController"/>.
/// </summary>
/// <param name="receptor">The receptor to set</param>
/// <exception cref="InvalidOperationException">When a <see cref="Receptor"/> is already active on this <see cref="KeyCounterDisplay"/></exception>
public void SetReceptor(Receptor receptor)
{
if (this.receptor != null)
throw new InvalidOperationException("Cannot set a new receptor when one is already active");
this.receptor = receptor;
}
/// <summary>
/// Clears any <see cref="KeyCounterController.Receptor"/> active
/// </summary>
public void ClearReceptor()
{
receptor = null;
}
public override bool HandleNonPositionalInput => receptor == null;
public override bool HandlePositionalInput => receptor == null;
public partial class Receptor : Drawable
{
protected readonly KeyCounterController Target;
public Receptor(KeyCounterController target)
{
RelativeSizeAxes = Axes.Both;
Depth = float.MinValue;
Target = target;
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
protected override bool Handle(UIEvent e)
{
switch (e)
{
case KeyDownEvent:
case KeyUpEvent:
case MouseDownEvent:
case MouseUpEvent:
return Target.TriggerEvent(e);
}
return base.Handle(e);
}
}
}
}

View File

@ -1,18 +1,13 @@
// 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.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
@ -34,38 +29,13 @@ namespace osu.Game.Screens.Play.HUD
protected abstract FillFlowContainer<KeyCounter> KeyFlow { get; }
/// <summary>
/// Whether the actions reported by all <see cref="InputTrigger"/>s within this <see cref="KeyCounterDisplay"/> should be counted.
/// </summary>
public Bindable<bool> IsCounting { get; } = new BindableBool(true);
protected readonly Bindable<bool> ConfigVisibility = new Bindable<bool>();
[Resolved]
private KeyCounterController controller { get; set; } = null!;
protected abstract void UpdateVisibility();
private Receptor? receptor;
/// <summary>
/// Sets a <see cref="Receptor"/> that will populate keybinding events to this <see cref="KeyCounterDisplay"/>.
/// </summary>
/// <param name="receptor">The receptor to set</param>
/// <exception cref="InvalidOperationException">When a <see cref="Receptor"/> is already active on this <see cref="KeyCounterDisplay"/></exception>
public void SetReceptor(Receptor receptor)
{
if (this.receptor != null)
throw new InvalidOperationException("Cannot set a new receptor when one is already active");
this.receptor = receptor;
}
/// <summary>
/// Clears any <see cref="Receptor"/> active
/// </summary>
public void ClearReceptor()
{
receptor = null;
}
/// <summary>
/// Add a <see cref="InputTrigger"/> to this display.
/// </summary>
@ -74,8 +44,6 @@ namespace osu.Game.Screens.Play.HUD
var keyCounter = CreateCounter(trigger);
KeyFlow.Add(keyCounter);
IsCounting.BindTo(keyCounter.IsCounting);
}
/// <summary>
@ -86,49 +54,29 @@ namespace osu.Game.Screens.Play.HUD
protected abstract KeyCounter CreateCounter(InputTrigger trigger);
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
private void load(OsuConfigManager config, DrawableRuleset? drawableRuleset)
{
config.BindWith(OsuSetting.KeyOverlay, ConfigVisibility);
if (drawableRuleset != null)
AlwaysVisible.BindTo(drawableRuleset.HasReplayLoaded);
}
protected override void LoadComplete()
{
base.LoadComplete();
controller.OnNewTrigger += Add;
AddRange(controller.Triggers);
AlwaysVisible.BindValueChanged(_ => UpdateVisibility());
ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true);
}
public override bool HandleNonPositionalInput => receptor == null;
public override bool HandlePositionalInput => receptor == null;
public partial class Receptor : Drawable
protected override void Dispose(bool isDisposing)
{
protected readonly KeyCounterDisplay Target;
public Receptor(KeyCounterDisplay target)
{
RelativeSizeAxes = Axes.Both;
Depth = float.MinValue;
Target = target;
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
protected override bool Handle(UIEvent e)
{
switch (e)
{
case KeyDownEvent:
case KeyUpEvent:
case MouseDownEvent:
case MouseUpEvent:
return Target.InternalChildren.Any(c => c.TriggerEvent(e));
}
return base.Handle(e);
}
base.Dispose(isDisposing);
controller.OnNewTrigger -= Add;
}
}
}

View File

@ -16,8 +16,10 @@ using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
@ -26,8 +28,6 @@ using osu.Game.Screens.Play.HUD.ClicksPerSecond;
using osu.Game.Screens.Play.HUD.JudgementCounter;
using osu.Game.Skinning;
using osuTK;
using osu.Game.Localisation;
using osu.Game.Rulesets;
namespace osu.Game.Screens.Play
{
@ -54,7 +54,6 @@ namespace osu.Game.Screens.Play
return child == bottomRightElements;
}
public readonly KeyCounterDisplay KeyCounter;
public readonly ModDisplay ModDisplay;
public readonly HoldForMenuButton HoldToQuit;
public readonly PlayerSettingsOverlay PlayerSettingsOverlay;
@ -62,6 +61,9 @@ namespace osu.Game.Screens.Play
[Cached]
private readonly ClicksPerSecondCalculator clicksPerSecondCalculator;
[Cached]
public readonly KeyCounterController KeyCounter;
[Cached]
private readonly JudgementTally tally;
@ -145,7 +147,6 @@ namespace osu.Game.Screens.Play
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
KeyCounter = CreateKeyCounter(),
HoldToQuit = CreateHoldForMenuButton(),
}
},
@ -157,9 +158,10 @@ namespace osu.Game.Screens.Play
Spacing = new Vector2(5)
},
clicksPerSecondCalculator = new ClicksPerSecondCalculator(),
KeyCounter = new KeyCounterController()
};
hideTargets = new List<Drawable> { mainComponents, rulesetComponents, KeyCounter, topRightElements };
hideTargets = new List<Drawable> { mainComponents, rulesetComponents, topRightElements };
if (!alwaysShowLeaderboard)
hideTargets.Add(LeaderboardFlow);
@ -321,7 +323,6 @@ namespace osu.Game.Screens.Play
{
attachTarget.Attach(KeyCounter);
attachTarget.Attach(clicksPerSecondCalculator);
mainComponents.SetAttachTarget(attachTarget);
}
replayLoaded.BindTo(drawableRuleset.HasReplayLoaded);
@ -332,12 +333,6 @@ namespace osu.Game.Screens.Play
ShowHealth = { BindTarget = ShowHealthBar }
};
protected KeyCounterDisplay CreateKeyCounter() => new DefaultKeyCounterDisplay
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
};
protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton
{
Anchor = Anchor.BottomRight,

View File

@ -438,7 +438,6 @@ namespace osu.Game.Screens.Play
{
Value = false
},
AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded },
},
Anchor = Anchor.Centre,
Origin = Anchor.Centre