From ae5b6c3e1068a130396c3a2f8fab554c362603c5 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Sun, 15 May 2022 19:38:37 +0100 Subject: [PATCH 01/50] Use dummy channel to show selector and remove `ChannelListSelector` Add dummy channel `DummySelectorChannel` which should be set as the current channel in the channel manager when the selector in the chat overlay should be shown. Refactors the `ChannelListItem` to not show mention pill and close button when the channel is the dummy selector channel. Ensure that the `ChannelList` selects the dummy channel on clicking the selector item. Removes `ChannelListSelector` as it is no longer needed. Removes the `setCurrent` parameter from `ChannelManager.JoinChannel` method as it is no longer needed. --- .../Visual/Online/TestSceneChannelList.cs | 12 +- .../Visual/Online/TestSceneChatOverlayV2.cs | 5 +- osu.Game/Online/Chat/ChannelManager.cs | 18 +-- .../Overlays/Chat/ChannelList/ChannelList.cs | 10 +- .../Chat/ChannelList/ChannelListItem.cs | 60 +++++++--- .../Chat/ChannelList/ChannelListSelector.cs | 103 ------------------ osu.Game/Overlays/ChatOverlayV2.cs | 13 ++- 7 files changed, 80 insertions(+), 141 deletions(-) delete mode 100644 osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs index 9929642539..53a48fcc58 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs @@ -118,35 +118,37 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Unread Selected", () => { - if (selected.Value != null) + if (validItem) channelList.GetItem(selected.Value).Unread.Value = true; }); AddStep("Read Selected", () => { - if (selected.Value != null) + if (validItem) channelList.GetItem(selected.Value).Unread.Value = false; }); AddStep("Add Mention Selected", () => { - if (selected.Value != null) + if (validItem) channelList.GetItem(selected.Value).Mentions.Value++; }); AddStep("Add 98 Mentions Selected", () => { - if (selected.Value != null) + if (validItem) channelList.GetItem(selected.Value).Mentions.Value += 98; }); AddStep("Clear Mentions Selected", () => { - if (selected.Value != null) + if (validItem) channelList.GetItem(selected.Value).Mentions.Value = 0; }); } + private bool validItem => selected.Value != null && !(selected.Value is DummySelectorChannel); + private Channel createRandomPublicChannel() { int id = RNG.Next(0, 10000); diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index bf1767cc96..48557a4ddb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -372,7 +372,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); - AddStep("Click selector", () => clickDrawable(chatOverlay.ChildrenOfType().Single())); + AddStep("Click selector", () => clickDrawable(channelSelectorButton)); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); AddStep("Click listing", () => clickDrawable(chatOverlay.ChildrenOfType().Single())); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); @@ -407,6 +407,9 @@ namespace osu.Game.Tests.Visual.Online private ChatOverlayTopBar chatOverlayTopBar => chatOverlay.ChildrenOfType().Single(); + private ChannelListItem channelSelectorButton => + chatOverlay.ChildrenOfType().Single(item => item.Channel is DummySelectorChannel); + private void clickDrawable(Drawable d) { InputManager.MoveMouseTo(d); diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 1fe784f68b..9524e8c1a7 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -14,6 +14,7 @@ using osu.Game.Input; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; using osu.Game.Overlays.Chat.Tabs; namespace osu.Game.Online.Chat @@ -133,7 +134,9 @@ namespace osu.Game.Online.Chat private void currentChannelChanged(ValueChangedEvent e) { - if (!(e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel)) + bool isSelectorChannel = e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel || e.NewValue is DummySelectorChannel; + + if (!isSelectorChannel) JoinChannel(e.NewValue); } @@ -194,7 +197,6 @@ namespace osu.Game.Online.Chat createNewPrivateMessageRequest.Failure += exception => { - handlePostException(exception); target.ReplaceMessage(message, null); dequeueAndRun(); }; @@ -420,11 +422,10 @@ namespace osu.Game.Online.Chat /// Joins a channel if it has not already been joined. Must be called from the update thread. /// /// The channel to join. - /// Set the channel to join as the current channel if the current channel is null. /// The joined channel. Note that this may not match the parameter channel as it is a backed object. - public Channel JoinChannel(Channel channel, bool setCurrent = true) => joinChannel(channel, true, setCurrent); + public Channel JoinChannel(Channel channel) => joinChannel(channel, true); - private Channel joinChannel(Channel channel, bool fetchInitialMessages = false, bool setCurrent = true) + private Channel joinChannel(Channel channel, bool fetchInitialMessages = false) { if (channel == null) return null; @@ -440,7 +441,7 @@ namespace osu.Game.Online.Chat case ChannelType.Multiplayer: // join is implicit. happens when you join a multiplayer game. // this will probably change in the future. - joinChannel(channel, fetchInitialMessages, setCurrent); + joinChannel(channel, fetchInitialMessages); return channel; case ChannelType.PM: @@ -461,7 +462,7 @@ namespace osu.Game.Online.Chat default: var req = new JoinChannelRequest(channel); - req.Success += () => joinChannel(channel, fetchInitialMessages, setCurrent); + req.Success += () => joinChannel(channel, fetchInitialMessages); req.Failure += ex => LeaveChannel(channel); api.Queue(req); return channel; @@ -473,8 +474,7 @@ namespace osu.Game.Online.Chat this.fetchInitialMessages(channel); } - if (setCurrent) - CurrentChannel.Value ??= channel; + CurrentChannel.Value ??= channel; return channel; } diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 6bdf5ee084..c9f3a4d380 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -18,13 +18,16 @@ namespace osu.Game.Overlays.Chat.ChannelList { public class ChannelList : Container { - public Action? OnRequestSelect; + public Action? OnRequestSelect; public Action? OnRequestLeave; private readonly Dictionary channelMap = new Dictionary(); + private readonly DummySelectorChannel dummySelectorChannel = new DummySelectorChannel(); + private ChannelListItemFlow publicChannelFlow = null!; private ChannelListItemFlow privateChannelFlow = null!; + private ChannelListItem selector = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -50,16 +53,17 @@ namespace osu.Game.Overlays.Chat.ChannelList Children = new Drawable[] { publicChannelFlow = new ChannelListItemFlow("CHANNELS"), - new ChannelListSelector + selector = new ChannelListItem(dummySelectorChannel) { Margin = new MarginPadding { Bottom = 10 }, - Action = () => OnRequestSelect?.Invoke(null), }, privateChannelFlow = new ChannelListItemFlow("DIRECT MESSAGES"), }, }, }, }; + + selector.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan); } public void AddChannel(Channel channel) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index dd571c9ad9..dfb0b6d781 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private Box hoverBox = null!; private Box selectBox = null!; private OsuSpriteText text = null!; - private ChannelListItemCloseButton close = null!; + private Drawable close = null!; [Resolved] private Bindable selectedChannel { get; set; } = null!; @@ -97,20 +97,8 @@ namespace osu.Game.Overlays.Chat.ChannelList RelativeSizeAxes = Axes.X, Truncate = true, }, - new ChannelListItemMentionPill - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 3 }, - Mentions = { BindTarget = Mentions }, - }, - close = new ChannelListItemCloseButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 3 }, - Action = () => OnRequestLeave?.Invoke(Channel), - } + createMentionPill(), + close = createCloseButton(), } }, }, @@ -131,14 +119,20 @@ namespace osu.Game.Overlays.Chat.ChannelList protected override bool OnHover(HoverEvent e) { hoverBox.FadeIn(300, Easing.OutQuint); - close.FadeIn(300, Easing.OutQuint); + + if (!isSelector) + close.FadeIn(300, Easing.OutQuint); + return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { hoverBox.FadeOut(200, Easing.OutQuint); - close.FadeOut(200, Easing.OutQuint); + + if (!isSelector) + close.FadeOut(200, Easing.OutQuint); + base.OnHoverLost(e); } @@ -158,9 +152,37 @@ namespace osu.Game.Overlays.Chat.ChannelList }; } + private Drawable createMentionPill() + { + if (isSelector) + return Drawable.Empty(); + + return new ChannelListItemMentionPill + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 3 }, + Mentions = { BindTarget = Mentions }, + }; + } + + private Drawable createCloseButton() + { + if (isSelector) + return Drawable.Empty(); + + return new ChannelListItemCloseButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 3 }, + Action = () => OnRequestLeave?.Invoke(Channel), + }; + } + private void updateState() { - bool selected = selectedChannel.Value == Channel; + bool selected = selectedChannel.Value == Channel || (isSelector && selectedChannel.Value == null); if (selected) selectBox.FadeIn(300, Easing.OutQuint); @@ -172,5 +194,7 @@ namespace osu.Game.Overlays.Chat.ChannelList else text.FadeColour(colourProvider.Light3, 200, Easing.OutQuint); } + + private bool isSelector => Channel is DummySelectorChannel; } } diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs deleted file mode 100644 index a07aad2041..0000000000 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable enable - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -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.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.Chat; - -namespace osu.Game.Overlays.Chat.ChannelList -{ - public class ChannelListSelector : OsuClickableContainer - { - private Box hoverBox = null!; - private Box selectBox = null!; - private OsuSpriteText text = null!; - - [Resolved] - private Bindable currentChannel { get; set; } = null!; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - Height = 30; - RelativeSizeAxes = Axes.X; - - Children = new Drawable[] - { - hoverBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background3, - Alpha = 0f, - }, - selectBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4, - Alpha = 0f, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 18, Right = 10 }, - Child = text = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = "Add more channels", - Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold), - Colour = colourProvider.Light3, - Margin = new MarginPadding { Bottom = 2 }, - RelativeSizeAxes = Axes.X, - Truncate = true, - }, - }, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - currentChannel.BindValueChanged(channel => - { - // This logic should be handled by the chat overlay rather than this component. - // Selected state should be moved to an abstract class and shared with ChannelListItem. - if (channel.NewValue == null) - { - text.FadeColour(colourProvider.Content1, 300, Easing.OutQuint); - selectBox.FadeIn(300, Easing.OutQuint); - } - else - { - text.FadeColour(colourProvider.Light3, 200, Easing.OutQuint); - selectBox.FadeOut(200, Easing.OutQuint); - } - }, true); - } - - protected override bool OnHover(HoverEvent e) - { - hoverBox.FadeIn(300, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - hoverBox.FadeOut(200, Easing.OutQuint); - base.OnHoverLost(e); - } - } -} diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 6eec0bbbf4..0307dcfdf3 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -154,7 +154,7 @@ namespace osu.Game.Overlays channelList.OnRequestSelect += channel => channelManager.CurrentChannel.Value = channel; channelList.OnRequestLeave += channel => channelManager.LeaveChannel(channel); - channelListing.OnRequestJoin += channel => channelManager.JoinChannel(channel, false); + channelListing.OnRequestJoin += channel => channelManager.JoinChannel(channel); channelListing.OnRequestLeave += channel => channelManager.LeaveChannel(channel); textBar.OnSearchTermsChanged += searchTerms => channelListing.SearchTerm = searchTerms; @@ -237,7 +237,7 @@ namespace osu.Game.Overlays { Channel? newChannel = channel.NewValue; - if (newChannel == null) + if (newChannel == null || newChannel is DummySelectorChannel) { // null channel denotes that we should be showing the listing. channelListing.State.Value = Visibility.Visible; @@ -293,4 +293,13 @@ namespace osu.Game.Overlays channelManager.PostMessage(message); } } + + public class DummySelectorChannel : Channel + { + public DummySelectorChannel() + { + Name = "Add more channels"; + Type = ChannelType.System; + } + } } From 48b7e0f0900d52a23bb9fa1a753983c373ca6ec8 Mon Sep 17 00:00:00 2001 From: Jay L Date: Tue, 10 May 2022 18:17:40 +1000 Subject: [PATCH 02/50] Stamina Skill Rewrite and Refactor --- .../Difficulty/Skills/Stamina.cs | 139 +++++++++--------- .../Difficulty/TaikoDifficultyCalculator.cs | 20 +-- 2 files changed, 75 insertions(+), 84 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 54cf233d69..b10da95444 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -1,16 +1,50 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; +using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; -using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { + class SingleKeyStamina + { + private double previousHitTime = -1; + + private double strainValueOf(DifficultyHitObject current) + { + if (previousHitTime == -1) + { + previousHitTime = current.StartTime; + return 0; + } + else + { + double objectStrain = 0.5; + objectStrain += speedBonus(current.StartTime - previousHitTime); + previousHitTime = current.StartTime; + return objectStrain; + } + } + + public double StrainValueAt(DifficultyHitObject current) + { + return strainValueOf(current); + } + + /// + /// Applies a speed bonus dependent on the time since the last hit performed using this key. + /// + /// The duration between the current and previous note hit using the same key. + private double speedBonus(double notePairDuration) + { + return 175 / Math.Pow(notePairDuration + 100, 1); + } + } + /// /// Calculates the stamina coefficient of taiko difficulty. /// @@ -22,39 +56,43 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0.4; - /// - /// Maximum number of entries to keep in . - /// - private const int max_history_length = 2; + private SingleKeyStamina[] keyStamina = new SingleKeyStamina[4] + { + new SingleKeyStamina(), + new SingleKeyStamina(), + new SingleKeyStamina(), + new SingleKeyStamina() + }; - /// - /// The index of the hand this instance is associated with. - /// - /// - /// The value of 0 indicates the left hand (full alternating gameplay starting with left hand is assumed). - /// This naturally translates onto index offsets of the objects in the map. - /// - private readonly int hand; - - /// - /// Stores the last durations between notes hit with the hand indicated by . - /// - private readonly LimitedCapacityQueue notePairDurationHistory = new LimitedCapacityQueue(max_history_length); - - /// - /// Stores the of the last object that was hit by the other hand. - /// - private double offhandObjectDuration = double.MaxValue; + private int donIndex = 1; + private int katIndex = 3; /// /// Creates a skill. /// /// Mods for use in skill calculations. - /// Whether this instance is performing calculations for the right hand. - public Stamina(Mod[] mods, bool rightHand) + public Stamina(Mod[] mods) : base(mods) { - hand = rightHand ? 1 : 0; + } + + private SingleKeyStamina getNextSingleKeyStamina(TaikoDifficultyHitObject current) + { + if (current.HitType == HitType.Centre) + { + donIndex = donIndex == 0 ? 1 : 0; + return keyStamina[donIndex]; + } + else + { + katIndex = katIndex == 2 ? 3 : 2; + return keyStamina[katIndex]; + } + } + + private double sigmoid(double val, double center, double width) + { + return Math.Tanh(Math.E * -(val - center) / width); } protected override double StrainValueOf(DifficultyHitObject current) @@ -65,52 +103,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills } TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current; + double objectStrain = getNextSingleKeyStamina(hitObject).StrainValueAt(hitObject); - if (hitObject.ObjectIndex % 2 == hand) - { - double objectStrain = 1; - - if (hitObject.ObjectIndex == 1) - return 1; - - notePairDurationHistory.Enqueue(hitObject.DeltaTime + offhandObjectDuration); - - double shortestRecentNote = notePairDurationHistory.Min(); - objectStrain += speedBonus(shortestRecentNote); - - if (hitObject.StaminaCheese) - objectStrain *= cheesePenalty(hitObject.DeltaTime + offhandObjectDuration); - - return objectStrain; - } - - offhandObjectDuration = hitObject.DeltaTime; - return 0; - } - - /// - /// Applies a penalty for hit objects marked with . - /// - /// The duration between the current and previous note hit using the hand indicated by . - private double cheesePenalty(double notePairDuration) - { - if (notePairDuration > 125) return 1; - if (notePairDuration < 100) return 0.6; - - return 0.6 + (notePairDuration - 100) * 0.016; - } - - /// - /// Applies a speed bonus dependent on the time since the last hit performed using this hand. - /// - /// The duration between the current and previous note hit using the hand indicated by . - private double speedBonus(double notePairDuration) - { - if (notePairDuration >= 200) return 0; - - double bonus = 200 - notePairDuration; - bonus *= bonus; - return bonus / 100000; + return objectStrain; } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index a9d512f076..de69f63bf4 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { private const double rhythm_skill_multiplier = 0.014; private const double colour_skill_multiplier = 0.01; - private const double stamina_skill_multiplier = 0.02; + private const double stamina_skill_multiplier = 0.021; public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -33,8 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { new Colour(mods), new Rhythm(mods), - new Stamina(mods, true), - new Stamina(mods, false), + new Stamina(mods) }; protected override Mod[] DifficultyAdjustmentMods => new Mod[] @@ -58,7 +57,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty ); } - new StaminaCheeseDetector(taikoDifficultyHitObjects).FindCheese(); return taikoDifficultyHitObjects; } @@ -69,17 +67,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty var colour = (Colour)skills[0]; var rhythm = (Rhythm)skills[1]; - var staminaRight = (Stamina)skills[2]; - var staminaLeft = (Stamina)skills[3]; + var stamina = (Stamina)skills[2]; double colourRating = colour.DifficultyValue() * colour_skill_multiplier; double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier; - double staminaRating = (staminaRight.DifficultyValue() + staminaLeft.DifficultyValue()) * stamina_skill_multiplier; + double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier; double staminaPenalty = simpleColourPenalty(staminaRating, colourRating); staminaRating *= staminaPenalty; - double combinedRating = locallyCombinedDifficulty(colour, rhythm, staminaRight, staminaLeft, staminaPenalty); + double combinedRating = locallyCombinedDifficulty(colour, rhythm, stamina, staminaPenalty); double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating); double starRating = 1.4 * separatedRating + 0.5 * combinedRating; starRating = rescale(starRating); @@ -127,20 +124,19 @@ namespace osu.Game.Rulesets.Taiko.Difficulty /// For each section, the peak strains of all separate skills are combined into a single peak strain for the section. /// The resulting partial rating of the beatmap is a weighted sum of the combined peaks (higher peaks are weighted more). /// - private double locallyCombinedDifficulty(Colour colour, Rhythm rhythm, Stamina staminaRight, Stamina staminaLeft, double staminaPenalty) + private double locallyCombinedDifficulty(Colour colour, Rhythm rhythm, Stamina stamina, double staminaPenalty) { List peaks = new List(); var colourPeaks = colour.GetCurrentStrainPeaks().ToList(); var rhythmPeaks = rhythm.GetCurrentStrainPeaks().ToList(); - var staminaRightPeaks = staminaRight.GetCurrentStrainPeaks().ToList(); - var staminaLeftPeaks = staminaLeft.GetCurrentStrainPeaks().ToList(); + var staminaPeaks = stamina.GetCurrentStrainPeaks().ToList(); for (int i = 0; i < colourPeaks.Count; i++) { double colourPeak = colourPeaks[i] * colour_skill_multiplier; double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier; - double staminaPeak = (staminaRightPeaks[i] + staminaLeftPeaks[i]) * stamina_skill_multiplier * staminaPenalty; + double staminaPeak = staminaPeaks[i] * stamina_skill_multiplier * staminaPenalty; double peak = norm(2, colourPeak, rhythmPeak, staminaPeak); From f15738fded36c06d022014871effae3c3c380709 Mon Sep 17 00:00:00 2001 From: Jay L Date: Sun, 15 May 2022 22:09:24 +1000 Subject: [PATCH 03/50] Remove Redundant Detection Not needed, as nerf is not required anymore and has negative effect. --- .../Preprocessing/StaminaCheeseDetector.cs | 145 ------------------ 1 file changed, 145 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs deleted file mode 100644 index 3b1a9ad777..0000000000 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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.Game.Rulesets.Difficulty.Utils; -using osu.Game.Rulesets.Taiko.Objects; - -namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing -{ - /// - /// Detects special hit object patterns which are easier to hit using special techniques - /// than normally assumed in the fully-alternating play style. - /// - /// - /// This component detects two basic types of patterns, leveraged by the following techniques: - /// - /// Rolling allows hitting patterns with quickly and regularly alternating notes with a single hand. - /// TL tapping makes hitting longer sequences of consecutive same-colour notes with little to no colour changes in-between. - /// - /// - public class StaminaCheeseDetector - { - /// - /// The minimum number of consecutive objects with repeating patterns that can be classified as hittable using a roll. - /// - private const int roll_min_repetitions = 12; - - /// - /// The minimum number of consecutive objects with repeating patterns that can be classified as hittable using a TL tap. - /// - private const int tl_min_repetitions = 16; - - /// - /// The list of all s in the map. - /// - private readonly List hitObjects; - - public StaminaCheeseDetector(List hitObjects) - { - this.hitObjects = hitObjects; - } - - /// - /// Finds and marks all objects in that special difficulty-reducing techiques apply to - /// with the flag. - /// - public void FindCheese() - { - findRolls(3); - findRolls(4); - - findTlTap(0, HitType.Rim); - findTlTap(1, HitType.Rim); - findTlTap(0, HitType.Centre); - findTlTap(1, HitType.Centre); - } - - /// - /// Finds and marks all sequences hittable using a roll. - /// - /// The length of a single repeating pattern to consider (triplets/quadruplets). - private void findRolls(int patternLength) - { - var history = new LimitedCapacityQueue(2 * patternLength); - - // for convenience, we're tracking the index of the item *before* our suspected repeat's start, - // as that index can be simply subtracted from the current index to get the number of elements in between - // without off-by-one errors - int indexBeforeLastRepeat = -1; - int lastMarkEnd = 0; - - for (int i = 0; i < hitObjects.Count; i++) - { - history.Enqueue(hitObjects[i]); - if (!history.Full) - continue; - - if (!containsPatternRepeat(history, patternLength)) - { - // we're setting this up for the next iteration, hence the +1. - // right here this index will point at the queue's front (oldest item), - // but that item is about to be popped next loop with an enqueue. - indexBeforeLastRepeat = i - history.Count + 1; - continue; - } - - int repeatedLength = i - indexBeforeLastRepeat; - if (repeatedLength < roll_min_repetitions) - continue; - - markObjectsAsCheese(Math.Max(lastMarkEnd, i - repeatedLength + 1), i); - lastMarkEnd = i; - } - } - - /// - /// Determines whether the objects stored in contain a repetition of a pattern of length . - /// - private static bool containsPatternRepeat(LimitedCapacityQueue history, int patternLength) - { - for (int j = 0; j < patternLength; j++) - { - if (history[j].HitType != history[j + patternLength].HitType) - return false; - } - - return true; - } - - /// - /// Finds and marks all sequences hittable using a TL tap. - /// - /// Whether sequences starting with an odd- (1) or even-indexed (0) hit object should be checked. - /// The type of hit to check for TL taps. - private void findTlTap(int parity, HitType type) - { - int tlLength = -2; - int lastMarkEnd = 0; - - for (int i = parity; i < hitObjects.Count; i += 2) - { - if (hitObjects[i].HitType == type) - tlLength += 2; - else - tlLength = -2; - - if (tlLength < tl_min_repetitions) - continue; - - markObjectsAsCheese(Math.Max(lastMarkEnd, i - tlLength + 1), i); - lastMarkEnd = i; - } - } - - /// - /// Marks all objects from to (inclusive) as . - /// - private void markObjectsAsCheese(int start, int end) - { - for (int i = start; i <= end; i++) - hitObjects[i].StaminaCheese = true; - } - } -} From d174099016363de0cd77f62b5eb0c183d2df0b47 Mon Sep 17 00:00:00 2001 From: Jay L Date: Sun, 15 May 2022 22:09:54 +1000 Subject: [PATCH 04/50] Refactor and Cleanup of Stamina.cs --- .../Difficulty/Skills/Stamina.cs | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index b10da95444..6d4a1fd948 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; @@ -10,29 +9,28 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { - class SingleKeyStamina + /// + /// Stamina of a single key, calculated based on repetition speed. + /// + public class SingleKeyStamina { private double previousHitTime = -1; - private double strainValueOf(DifficultyHitObject current) + /// + /// Similar to + /// + public double StrainValueOf(DifficultyHitObject current) { if (previousHitTime == -1) { previousHitTime = current.StartTime; return 0; } - else - { - double objectStrain = 0.5; - objectStrain += speedBonus(current.StartTime - previousHitTime); - previousHitTime = current.StartTime; - return objectStrain; - } - } - public double StrainValueAt(DifficultyHitObject current) - { - return strainValueOf(current); + double objectStrain = 0.5; + objectStrain += speedBonus(current.StartTime - previousHitTime); + previousHitTime = current.StartTime; + return objectStrain; } /// @@ -41,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// The duration between the current and previous note hit using the same key. private double speedBonus(double notePairDuration) { - return 175 / Math.Pow(notePairDuration + 100, 1); + return 175 / (notePairDuration + 100); } } @@ -56,7 +54,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0.4; - private SingleKeyStamina[] keyStamina = new SingleKeyStamina[4] + /// + /// Stamina of each individual keys, calculated based on repetition speed. + /// + private readonly SingleKeyStamina[] keyStamina = { new SingleKeyStamina(), new SingleKeyStamina(), @@ -64,7 +65,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills new SingleKeyStamina() }; + /// + /// Current index to for a don hit. + /// private int donIndex = 1; + + /// + /// Current index to for a kat hit. + /// private int katIndex = 3; /// @@ -76,23 +84,21 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { } + /// + /// Get the next to use for the given . + /// + /// The current . private SingleKeyStamina getNextSingleKeyStamina(TaikoDifficultyHitObject current) { + // Alternate key for the same color. if (current.HitType == HitType.Centre) { donIndex = donIndex == 0 ? 1 : 0; return keyStamina[donIndex]; } - else - { - katIndex = katIndex == 2 ? 3 : 2; - return keyStamina[katIndex]; - } - } - private double sigmoid(double val, double center, double width) - { - return Math.Tanh(Math.E * -(val - center) / width); + katIndex = katIndex == 2 ? 3 : 2; + return keyStamina[katIndex]; } protected override double StrainValueOf(DifficultyHitObject current) @@ -103,9 +109,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills } TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current; - double objectStrain = getNextSingleKeyStamina(hitObject).StrainValueAt(hitObject); - - return objectStrain; + return getNextSingleKeyStamina(hitObject).StrainValueOf(hitObject); } } } From b44afb19b16a19e7464c9f1281307f4c396e1fc7 Mon Sep 17 00:00:00 2001 From: Jay L Date: Sun, 15 May 2022 22:10:18 +1000 Subject: [PATCH 05/50] Include Convert Penalty For low colour variance --- .../Difficulty/TaikoDifficultyCalculator.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index de69f63bf4..5816b9957c 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -76,6 +76,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double staminaPenalty = simpleColourPenalty(staminaRating, colourRating); staminaRating *= staminaPenalty; + if (beatmap.BeatmapInfo.Ruleset.OnlineID == 0 && colourRating < 0.05) + { + staminaPenalty *= 0.25; + } + double combinedRating = locallyCombinedDifficulty(colour, rhythm, stamina, staminaPenalty); double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating); double starRating = 1.4 * separatedRating + 0.5 * combinedRating; From d94e30bed3985f3301ea8d769ad5060affc94755 Mon Sep 17 00:00:00 2001 From: Jay L Date: Sun, 15 May 2022 22:11:01 +1000 Subject: [PATCH 06/50] Rewrite Performance difficultyValue Calculation To change values in line with the community survey, pp has been rescaled immensely, with changes on both the lower and upper end. --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index a8122551ff..8d99fd3b87 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) { - double difficultyValue = Math.Pow(5.0 * Math.Max(1.0, attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0; + double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.175) - 4.0, 2.25) / 450.0; double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0); difficultyValue *= lengthBonus; From 94294ba351c2c4609328da2ada403a37b89c55b7 Mon Sep 17 00:00:00 2001 From: Jay L Date: Mon, 16 May 2022 10:26:47 +1000 Subject: [PATCH 07/50] Apply Review Changes --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs | 2 +- osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 6d4a1fd948..7846e97a65 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// public class SingleKeyStamina { - private double previousHitTime = -1; + private double previousHitTime; /// /// Similar to diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 5816b9957c..1aa31c6fe4 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -76,6 +76,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double staminaPenalty = simpleColourPenalty(staminaRating, colourRating); staminaRating *= staminaPenalty; + //TODO : This is a temporary fix for the stamina rating of converts, due to their low colour variance. if (beatmap.BeatmapInfo.Ruleset.OnlineID == 0 && colourRating < 0.05) { staminaPenalty *= 0.25; From c7ec95c0d0d4416561ef622e134b1aef81ea285e Mon Sep 17 00:00:00 2001 From: Jay L Date: Mon, 16 May 2022 14:25:07 +1000 Subject: [PATCH 08/50] Rectify Review Changes --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 7846e97a65..d54da8b869 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -14,14 +14,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// public class SingleKeyStamina { - private double previousHitTime; + private double? previousHitTime; /// /// Similar to /// public double StrainValueOf(DifficultyHitObject current) { - if (previousHitTime == -1) + if (previousHitTime == null) { previousHitTime = current.StartTime; return 0; From db6abd86f03bc538fa2c3ffda98120b883f37d5c Mon Sep 17 00:00:00 2001 From: Jay L Date: Mon, 16 May 2022 14:48:39 +1000 Subject: [PATCH 09/50] Rectify null value call tired programming is never a good idea --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index d54da8b869..744dafd57e 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills } double objectStrain = 0.5; - objectStrain += speedBonus(current.StartTime - previousHitTime); + objectStrain += speedBonus(current.StartTime - previousHitTime.Value); previousHitTime = current.StartTime; return objectStrain; } From 934bcaf82e8851415a44656151f30a1c9eb5ed7f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 16 May 2022 14:22:30 +0900 Subject: [PATCH 10/50] Adjust tests --- .../TaikoDifficultyCalculatorTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index 226da7df09..51332a1ece 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Taiko.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; - [TestCase(2.2420075288523802d, 200, "diffcalc-test")] - [TestCase(2.2420075288523802d, 200, "diffcalc-test-strong")] + [TestCase(1.9971301024093662d, 200, "diffcalc-test")] + [TestCase(1.9971301024093662d, 200, "diffcalc-test-strong")] public void Test(double expectedStarRating, int expectedMaxCombo, string name) => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(3.134084469440479d, 200, "diffcalc-test")] - [TestCase(3.134084469440479d, 200, "diffcalc-test-strong")] + [TestCase(3.1645810961313674d, 200, "diffcalc-test")] + [TestCase(3.1645810961313674d, 200, "diffcalc-test-strong")] public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new TaikoModDoubleTime()); From 5ad969246702e79eef2b10f155891fa4b61ee3ce Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 17 May 2022 14:59:18 +0900 Subject: [PATCH 11/50] Adjust minimum health targets to match osu-stable --- osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index dfeb6b4788..e7bb6d79f8 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -27,17 +27,17 @@ namespace osu.Game.Rulesets.Scoring /// /// The minimum health target at an HP drain rate of 0. /// - private const double min_health_target = 0.95; + private const double min_health_target = 0.99; /// /// The minimum health target at an HP drain rate of 5. /// - private const double mid_health_target = 0.70; + private const double mid_health_target = 0.9; /// /// The minimum health target at an HP drain rate of 10. /// - private const double max_health_target = 0.30; + private const double max_health_target = 0.4; private IBeatmap beatmap; From 932442e2422db30333071d6a441b993d4f50667b Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 17 May 2022 18:52:19 +0100 Subject: [PATCH 12/50] Use `null` for empty grid cells in `ChannelListItem` --- .../Chat/ChannelList/ChannelListItem.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index dfb0b6d781..da1cfaf617 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private Box hoverBox = null!; private Box selectBox = null!; private OsuSpriteText text = null!; - private Drawable close = null!; + private Drawable? close; [Resolved] private Bindable selectedChannel { get; set; } = null!; @@ -119,9 +119,7 @@ namespace osu.Game.Overlays.Chat.ChannelList protected override bool OnHover(HoverEvent e) { hoverBox.FadeIn(300, Easing.OutQuint); - - if (!isSelector) - close.FadeIn(300, Easing.OutQuint); + close?.FadeIn(300, Easing.OutQuint); return base.OnHover(e); } @@ -129,17 +127,15 @@ namespace osu.Game.Overlays.Chat.ChannelList protected override void OnHoverLost(HoverLostEvent e) { hoverBox.FadeOut(200, Easing.OutQuint); - - if (!isSelector) - close.FadeOut(200, Easing.OutQuint); + close?.FadeOut(200, Easing.OutQuint); base.OnHoverLost(e); } - private Drawable createIcon() + private UpdateableAvatar? createIcon() { if (Channel.Type != ChannelType.PM) - return Drawable.Empty(); + return null; return new UpdateableAvatar(Channel.Users.First(), isInteractive: false) { @@ -152,10 +148,10 @@ namespace osu.Game.Overlays.Chat.ChannelList }; } - private Drawable createMentionPill() + private ChannelListItemMentionPill? createMentionPill() { if (isSelector) - return Drawable.Empty(); + return null; return new ChannelListItemMentionPill { @@ -166,10 +162,10 @@ namespace osu.Game.Overlays.Chat.ChannelList }; } - private Drawable createCloseButton() + private ChannelListItemCloseButton? createCloseButton() { if (isSelector) - return Drawable.Empty(); + return null; return new ChannelListItemCloseButton { From 4015042e369a6d0900cca61beb0a13ee795d1639 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 18 May 2022 01:35:39 +0100 Subject: [PATCH 13/50] Ensure close button component is not stored as a `Drawable` --- osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index da1cfaf617..eb2bdfdf8a 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private Box hoverBox = null!; private Box selectBox = null!; private OsuSpriteText text = null!; - private Drawable? close; + private ChannelListItemCloseButton? close; [Resolved] private Bindable selectedChannel { get; set; } = null!; @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Chat.ChannelList }, Content = new[] { - new[] + new Drawable?[] { createIcon(), text = new OsuSpriteText From dd4b11c593f7869295e246241e388c701e641f99 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 18 May 2022 01:47:23 +0100 Subject: [PATCH 14/50] Re-add exception handling on PM message request failure --- osu.Game/Online/Chat/ChannelManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 9524e8c1a7..9e85067f15 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -197,6 +197,7 @@ namespace osu.Game.Online.Chat createNewPrivateMessageRequest.Failure += exception => { + handlePostException(exception); target.ReplaceMessage(message, null); dequeueAndRun(); }; From 63c97763658c490de30239d0b1e41799dbed9d2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 May 2022 19:24:18 +0900 Subject: [PATCH 15/50] Fix oversight in bindable logic in new overlay class --- osu.Game/Overlays/ChatOverlayV2.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 0307dcfdf3..42c928014a 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -62,6 +62,9 @@ namespace osu.Game.Overlays [Cached] private readonly Bindable currentChannel = new Bindable(); + private readonly IBindableList availableChannels = new BindableList(); + private readonly IBindableList joinedChannels = new BindableList(); + public ChatOverlayV2() { Height = default_chat_height; @@ -147,9 +150,13 @@ namespace osu.Game.Overlays chatHeight.BindValueChanged(height => { Height = height.NewValue; }, true); currentChannel.BindTo(channelManager.CurrentChannel); - channelManager.CurrentChannel.BindValueChanged(currentChannelChanged, true); - channelManager.JoinedChannels.BindCollectionChanged(joinedChannelsChanged, true); - channelManager.AvailableChannels.BindCollectionChanged(availableChannelsChanged, true); + currentChannel.BindValueChanged(currentChannelChanged, true); + + joinedChannels.BindTo(channelManager.JoinedChannels); + joinedChannels.BindCollectionChanged(joinedChannelsChanged, true); + + availableChannels.BindTo(channelManager.AvailableChannels); + availableChannels.BindCollectionChanged(availableChannelsChanged, true); channelList.OnRequestSelect += channel => channelManager.CurrentChannel.Value = channel; channelList.OnRequestLeave += channel => channelManager.LeaveChannel(channel); @@ -263,8 +270,8 @@ namespace osu.Game.Overlays switch (args.Action) { case NotifyCollectionChangedAction.Add: - IEnumerable joinedChannels = filterChannels(args.NewItems); - foreach (var channel in joinedChannels) + IEnumerable newChannels = filterChannels(args.NewItems); + foreach (var channel in newChannels) channelList.AddChannel(channel); break; From 136ecb45e241b6d0dd4358107ddb2d9b4299f3bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 May 2022 19:26:14 +0900 Subject: [PATCH 16/50] Rename dummy channel and move to a nested class inside the `ChannelListing` itself --- osu.Game.Tests/Visual/Online/TestSceneChannelList.cs | 3 ++- .../Visual/Online/TestSceneChatOverlayV2.cs | 2 +- osu.Game/Online/Chat/ChannelManager.cs | 4 ++-- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 5 +++-- osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs | 3 ++- osu.Game/Overlays/Chat/Listing/ChannelListing.cs | 9 +++++++++ osu.Game/Overlays/ChatOverlayV2.cs | 11 +---------- 7 files changed, 20 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs index 53a48fcc58..e4bc5645b6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs @@ -13,6 +13,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Overlays; using osu.Game.Overlays.Chat.ChannelList; +using osu.Game.Overlays.Chat.Listing; namespace osu.Game.Tests.Visual.Online { @@ -147,7 +148,7 @@ namespace osu.Game.Tests.Visual.Online }); } - private bool validItem => selected.Value != null && !(selected.Value is DummySelectorChannel); + private bool validItem => selected.Value != null && !(selected.Value is ChannelListing.ChannelListingChannel); private Channel createRandomPublicChannel() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index 48557a4ddb..3b8849f7c9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -408,7 +408,7 @@ namespace osu.Game.Tests.Visual.Online chatOverlay.ChildrenOfType().Single(); private ChannelListItem channelSelectorButton => - chatOverlay.ChildrenOfType().Single(item => item.Channel is DummySelectorChannel); + chatOverlay.ChildrenOfType().Single(item => item.Channel is ChannelListing.ChannelListingChannel); private void clickDrawable(Drawable d) { diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 9e85067f15..b7d67de04d 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -14,7 +14,7 @@ using osu.Game.Input; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays; +using osu.Game.Overlays.Chat.Listing; using osu.Game.Overlays.Chat.Tabs; namespace osu.Game.Online.Chat @@ -134,7 +134,7 @@ namespace osu.Game.Online.Chat private void currentChannelChanged(ValueChangedEvent e) { - bool isSelectorChannel = e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel || e.NewValue is DummySelectorChannel; + bool isSelectorChannel = e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel || e.NewValue is ChannelListing.ChannelListingChannel; if (!isSelectorChannel) JoinChannel(e.NewValue); diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index c9f3a4d380..555b54155c 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; +using osu.Game.Overlays.Chat.Listing; namespace osu.Game.Overlays.Chat.ChannelList { @@ -23,7 +24,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private readonly Dictionary channelMap = new Dictionary(); - private readonly DummySelectorChannel dummySelectorChannel = new DummySelectorChannel(); + private readonly ChannelListing.ChannelListingChannel channelListingChannel = new ChannelListing.ChannelListingChannel(); private ChannelListItemFlow publicChannelFlow = null!; private ChannelListItemFlow privateChannelFlow = null!; @@ -53,7 +54,7 @@ namespace osu.Game.Overlays.Chat.ChannelList Children = new Drawable[] { publicChannelFlow = new ChannelListItemFlow("CHANNELS"), - selector = new ChannelListItem(dummySelectorChannel) + selector = new ChannelListItem(channelListingChannel) { Margin = new MarginPadding { Bottom = 10 }, }, diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index eb2bdfdf8a..2967d9df11 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; +using osu.Game.Overlays.Chat.Listing; using osu.Game.Users.Drawables; using osuTK; @@ -191,6 +192,6 @@ namespace osu.Game.Overlays.Chat.ChannelList text.FadeColour(colourProvider.Light3, 200, Easing.OutQuint); } - private bool isSelector => Channel is DummySelectorChannel; + private bool isSelector => Channel is ChannelListing.ChannelListingChannel; } } diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListing.cs b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs index 732c78de15..8a5bc18cbf 100644 --- a/osu.Game/Overlays/Chat/Listing/ChannelListing.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs @@ -75,5 +75,14 @@ namespace osu.Game.Overlays.Chat.Listing protected override void PopIn() => this.FadeIn(); protected override void PopOut() => this.FadeOut(); + + public class ChannelListingChannel : Channel + { + public ChannelListingChannel() + { + Name = "Add more channels"; + Type = ChannelType.System; + } + } } } diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 42c928014a..202d70d99b 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -244,7 +244,7 @@ namespace osu.Game.Overlays { Channel? newChannel = channel.NewValue; - if (newChannel == null || newChannel is DummySelectorChannel) + if (newChannel == null || newChannel is ChannelListing.ChannelListingChannel) { // null channel denotes that we should be showing the listing. channelListing.State.Value = Visibility.Visible; @@ -300,13 +300,4 @@ namespace osu.Game.Overlays channelManager.PostMessage(message); } } - - public class DummySelectorChannel : Channel - { - public DummySelectorChannel() - { - Name = "Add more channels"; - Type = ChannelType.System; - } - } } From c37d1e4fae4fe827e7ae36b485407d280fd29ba5 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Thu, 19 May 2022 11:45:39 +0100 Subject: [PATCH 17/50] Ensure current channel is set to the `ChannelListingChannel` when it becomes `null` --- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 6 +++--- osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs | 2 +- osu.Game/Overlays/ChatOverlayV2.cs | 10 ++++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 555b54155c..d1ceae604c 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -22,9 +22,9 @@ namespace osu.Game.Overlays.Chat.ChannelList public Action? OnRequestSelect; public Action? OnRequestLeave; - private readonly Dictionary channelMap = new Dictionary(); + public readonly ChannelListing.ChannelListingChannel ChannelListingChannel = new ChannelListing.ChannelListingChannel(); - private readonly ChannelListing.ChannelListingChannel channelListingChannel = new ChannelListing.ChannelListingChannel(); + private readonly Dictionary channelMap = new Dictionary(); private ChannelListItemFlow publicChannelFlow = null!; private ChannelListItemFlow privateChannelFlow = null!; @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Chat.ChannelList Children = new Drawable[] { publicChannelFlow = new ChannelListItemFlow("CHANNELS"), - selector = new ChannelListItem(channelListingChannel) + selector = new ChannelListItem(ChannelListingChannel) { Margin = new MarginPadding { Bottom = 10 }, }, diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index 2967d9df11..9ab0c2792a 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -179,7 +179,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private void updateState() { - bool selected = selectedChannel.Value == Channel || (isSelector && selectedChannel.Value == null); + bool selected = selectedChannel.Value == Channel; if (selected) selectBox.FadeIn(300, Easing.OutQuint); diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 202d70d99b..fe3f8ba8cb 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -244,9 +244,15 @@ namespace osu.Game.Overlays { Channel? newChannel = channel.NewValue; - if (newChannel == null || newChannel is ChannelListing.ChannelListingChannel) + // null channel denotes that we should be showing the listing. + if (newChannel == null) + { + currentChannel.Value = channelList.ChannelListingChannel; + return; + } + + if (newChannel is ChannelListing.ChannelListingChannel) { - // null channel denotes that we should be showing the listing. channelListing.State.Value = Visibility.Visible; textBar.ShowSearch.Value = true; } From 3fdff7bbcb5a6f17a736f8e71e59a1fdacf8b3a7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 17 May 2022 15:11:22 +0900 Subject: [PATCH 18/50] Make Meh judgements not drain HP --- osu.Game/Rulesets/Judgements/Judgement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index fd576e9b9f..4fe34e15fd 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Judgements return -DEFAULT_MAX_HEALTH_INCREASE; case HitResult.Meh: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.05; + return DEFAULT_MAX_HEALTH_INCREASE * 0.05; case HitResult.Ok: return DEFAULT_MAX_HEALTH_INCREASE * 0.5; From ce7be940e2dd4f220583318bdf623a63d0f6e4f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 14:34:33 +0900 Subject: [PATCH 19/50] Setup basics for control and test --- .../Editing/TestSceneTapTimingControl.cs | 94 +++++++++ .../UserInterfaceV2/LabelledDrawable.cs | 3 +- .../Screens/Edit/Timing/TapTimingControl.cs | 189 ++++++++++++++++++ osu.Game/Screens/Edit/Timing/TimingSection.cs | 2 + 4 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs create mode 100644 osu.Game/Screens/Edit/Timing/TapTimingControl.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs new file mode 100644 index 0000000000..c2889b2549 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Timing; +using osuTK; + +namespace osu.Game.Tests.Visual.Editing +{ + [TestFixture] + public class TestSceneTapTimingControl : EditorClockTestScene + { + [Cached(typeof(EditorBeatmap))] + [Cached(typeof(IBeatSnapProvider))] + private readonly EditorBeatmap editorBeatmap; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + [Cached] + private Bindable selectedGroup = new Bindable(); + + private TapTimingControl control; + + public TestSceneTapTimingControl() + { + editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); + selectedGroup.Value = editorBeatmap.ControlPointInfo.Groups.First(); + } + + [Test] + public void TestTapThenReset() + { + AddStep("click tap button", () => + { + control.ChildrenOfType() + .First(b => b.Text == "Tap to beat") + .TriggerClick(); + }); + + AddUntilStep("wait for track playing", () => Clock.IsRunning); + + AddStep("click reset button", () => + { + control.ChildrenOfType() + .First(b => b.Text == "Reset") + .TriggerClick(); + }); + + AddUntilStep("wait for track stopped", () => !Clock.IsRunning); + } + + [Test] + public void TestBasic() + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); + Beatmap.Disabled = true; + + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, + Width = 400, + Scale = new Vector2(1.5f), + Child = control = new TapTimingControl(), + }; + } + + protected override void Dispose(bool isDisposing) + { + Beatmap.Disabled = false; + base.Dispose(isDisposing); + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs index 1e6032c1d0..066e1a7978 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs @@ -41,7 +41,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected const float CONTENT_PADDING_VERTICAL = 10; protected const float CONTENT_PADDING_HORIZONTAL = 15; - protected const float CORNER_RADIUS = 15; + + public const float CORNER_RADIUS = 15; /// /// The component that is being displayed. diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs new file mode 100644 index 0000000000..3867a4bcec --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -0,0 +1,189 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Edit.Timing +{ + public class TapTimingControl : CompositeDrawable + { + [Resolved] + private EditorClock editorClock { get; set; } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider, OsuColour colours) + { + Height = 200; + RelativeSizeAxes = Axes.X; + + CornerRadius = LabelledDrawable.CORNER_RADIUS; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colourProvider.Background4, + RelativeSizeAxes = Axes.Both, + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 60), + }, + Content = new[] + { + new Drawable[] + { + new Metronome() + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + new RoundedButton + { + Text = "Reset", + BackgroundColour = colours.Pink, + RelativeSizeAxes = Axes.X, + Width = 0.3f, + Action = reset, + }, + new RoundedButton + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Text = "Tap to beat", + RelativeSizeAxes = Axes.X, + BackgroundColour = colourProvider.Background1, + Width = 0.68f, + Action = tap, + } + } + }, + } + } + }, + }; + } + + private void tap() + { + if (!editorClock.IsRunning) + { + editorClock.Seek(0); + editorClock.Start(); + } + } + + private void reset() + { + editorClock.Stop(); + } + + private class Metronome : BeatSyncedContainer + { + private Container swing; + private Box weight; + private OsuSpriteText bpm; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider overlayColourProvider) + { + Margin = new MarginPadding(10); + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Triangle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(80, 120), + Colour = overlayColourProvider.Background1, + }, + new Circle + { + Y = -25, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + Colour = overlayColourProvider.Content2, + Size = new Vector2(10) + }, + bpm = new OsuSpriteText + { + Colour = overlayColourProvider.Content1, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + swing = new Container + { + RelativeSizeAxes = Axes.Both, + Y = -25, + Height = 0.8f, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Children = new Drawable[] + { + new Box + { + Colour = overlayColourProvider.Content2, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Width = 4, + }, + weight = new Box + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Colour = overlayColourProvider.Content2, + Size = new Vector2(15), + RelativePositionAxes = Axes.Y, + Y = -0.4f, + }, + } + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + swing + .RotateTo(20, 500, Easing.InOutQuad) + .Then() + .RotateTo(-20, 500, Easing.InOutQuad) + .Loop(); + } + + protected override void Update() + { + base.Update(); + + if (CurrentTimingPoint == null) + return; + + weight.Y = Math.Clamp((float)CurrentTimingPoint.BPM / 480, 0, 0.95f); + bpm.Text = $"{CurrentTimingPoint.BPM:F0}"; + } + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 13af04cd4b..be719d0cb6 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -19,6 +19,7 @@ namespace osu.Game.Screens.Edit.Timing { Flow.AddRange(new Drawable[] { + new TapTimingControl(), bpmTextEntry = new BPMTextBox(), timeSignature = new LabelledTimeSignature { @@ -96,5 +97,6 @@ namespace osu.Game.Screens.Edit.Timing } private static double beatLengthToBpm(double beatLength) => 60000 / beatLength; + } } From 42179568f3aca7d67c574ae615dcf8c65920eae3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 15:56:39 +0900 Subject: [PATCH 20/50] Initial animation pass on metronome --- .../Editing/TestSceneTapTimingControl.cs | 58 +++++++--- .../Screens/Edit/Timing/TapTimingControl.cs | 105 +++++++++++++----- osu.Game/Screens/Edit/Timing/TimingSection.cs | 1 - 3 files changed, 117 insertions(+), 47 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs index c2889b2549..9b2618df5b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs @@ -36,10 +36,37 @@ namespace osu.Game.Tests.Visual.Editing public TestSceneTapTimingControl() { - editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); + var playableBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); + + // Ensure time doesn't end while testing + playableBeatmap.BeatmapInfo.Length = 1200000; + + editorBeatmap = new EditorBeatmap(playableBeatmap); + selectedGroup.Value = editorBeatmap.ControlPointInfo.Groups.First(); } + protected override void LoadComplete() + { + base.LoadComplete(); + + Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); + Beatmap.Disabled = true; + + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, + Width = 400, + Scale = new Vector2(1.5f), + Child = control = new TapTimingControl(), + } + }; + } + [Test] public void TestTapThenReset() { @@ -65,24 +92,19 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestBasic() { - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); - Beatmap.Disabled = true; - - Child = new Container + AddStep("set low bpm", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Y, - Width = 400, - Scale = new Vector2(1.5f), - Child = control = new TapTimingControl(), - }; + editorBeatmap.ControlPointInfo.TimingPoints.First().BeatLength = 1000; + }); + + AddStep("click tap button", () => + { + control.ChildrenOfType() + .First(b => b.Text == "Tap to beat") + .TriggerClick(); + }); + + AddSliderStep("BPM", 30, 400, 60, bpm => editorBeatmap.ControlPointInfo.TimingPoints.First().BeatLength = 60000f / bpm); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 3867a4bcec..e23982ddc7 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -3,15 +3,19 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -103,9 +107,13 @@ namespace osu.Game.Screens.Edit.Timing private Container swing; private Box weight; private OsuSpriteText bpm; + private Box stick; + + [Resolved] + private OverlayColourProvider overlayColourProvider { get; set; } [BackgroundDependencyLoader] - private void load(OverlayColourProvider overlayColourProvider) + private void load() { Margin = new MarginPadding(10); AutoSizeAxes = Axes.Both; @@ -117,21 +125,13 @@ namespace osu.Game.Screens.Edit.Timing Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(80, 120), - Colour = overlayColourProvider.Background1, - }, - new Circle - { - Y = -25, - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre, - Colour = overlayColourProvider.Content2, - Size = new Vector2(10) + Colour = overlayColourProvider.Background2, }, bpm = new OsuSpriteText { Colour = overlayColourProvider.Content1, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, }, swing = new Container { @@ -142,10 +142,10 @@ namespace osu.Game.Screens.Edit.Timing Origin = Anchor.BottomCentre, Children = new Drawable[] { - new Box + stick = new Box { - Colour = overlayColourProvider.Content2, RelativeSizeAxes = Axes.Y, + Colour = overlayColourProvider.Colour2, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Width = 4, @@ -154,35 +154,84 @@ namespace osu.Game.Screens.Edit.Timing { Anchor = Anchor.TopCentre, Origin = Anchor.Centre, - Colour = overlayColourProvider.Content2, + Colour = overlayColourProvider.Colour1, Size = new Vector2(15), RelativePositionAxes = Axes.Y, - Y = -0.4f, + Y = 0.4f, }, } }, + new Circle + { + Y = -25, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + Colour = overlayColourProvider.Colour0, + Size = new Vector2(10) + }, }; } - protected override void LoadComplete() - { - base.LoadComplete(); - swing - .RotateTo(20, 500, Easing.InOutQuad) - .Then() - .RotateTo(-20, 500, Easing.InOutQuad) - .Loop(); - } + private double beatLength; + + private TimingControlPoint timingPoint; + + private float bpmRatio; + private bool isSwinging; protected override void Update() { base.Update(); - if (CurrentTimingPoint == null) + timingPoint = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(BeatSyncClock.CurrentTime); + + if (beatLength != timingPoint.BeatLength) + { + beatLength = timingPoint.BeatLength; + bpm.Text = $"{timingPoint.BPM:F0}"; + + EarlyActivationMilliseconds = timingPoint.BeatLength / 2; + + bpmRatio = (float)Interpolation.ApplyEasing(Easing.OutQuad, Math.Clamp((timingPoint.BPM - 30) / 480, 0, 1)); + + weight.MoveToY((float)Interpolation.Lerp(0, 0.9f, bpmRatio), 600, Easing.OutQuint); + } + + if (BeatSyncClock?.IsRunning != true && isSwinging) + { + swing.ClearTransforms(true); + + using (swing.BeginDelayedSequence(350)) + { + swing.RotateTo(0, 1000, Easing.OutQuint); + stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); + } + + isSwinging = false; + } + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + float angle = (float)Interpolation.Lerp(25, 4, bpmRatio); + + if (!IsBeatSyncedWithTrack) return; - weight.Y = Math.Clamp((float)CurrentTimingPoint.BPM / 480, 0, 0.95f); - bpm.Text = $"{CurrentTimingPoint.BPM:F0}"; + isSwinging = true; + + float currentAngle = swing.Rotation; + float targetAngle = currentAngle > 0 ? -angle : angle; + + swing.RotateTo(targetAngle, beatLength, Easing.InOutQuad); + + if (currentAngle != 0 && Math.Abs(currentAngle - targetAngle) > angle * 1.8f && isSwinging) + { + using (stick.BeginDelayedSequence(beatLength / 2)) + stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); + } } } } diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index be719d0cb6..a5abd96d72 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -97,6 +97,5 @@ namespace osu.Game.Screens.Edit.Timing } private static double beatLengthToBpm(double beatLength) => 60000 / beatLength; - } } From cf1ef28f72235a4f5d742e3d404ccfd94d2dcab1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 16:22:16 +0900 Subject: [PATCH 21/50] Add taper and more correctly shaped weight --- .../Screens/Edit/Timing/TapTimingControl.cs | 91 ++++++++++++++----- 1 file changed, 70 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index e23982ddc7..7507a7dd35 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -15,7 +15,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -105,9 +104,11 @@ namespace osu.Game.Screens.Edit.Timing private class Metronome : BeatSyncedContainer { private Container swing; - private Box weight; + private OsuSpriteText bpm; - private Box stick; + + private Drawable weight; + private Drawable stick; [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } @@ -116,33 +117,41 @@ namespace osu.Game.Screens.Edit.Timing private void load() { Margin = new MarginPadding(10); + + const float taper = 10; + + var triangleSize = new Vector2(80, 120); + AutoSizeAxes = Axes.Both; + const float stick_vertical_offset = -23; + InternalChildren = new Drawable[] { - new Triangle + new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(80, 120), - Colour = overlayColourProvider.Background2, - }, - bpm = new OsuSpriteText - { - Colour = overlayColourProvider.Content1, + Masking = true, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, + Size = new Vector2(triangleSize.X * 1.2f, triangleSize.Y - taper), + Child = new Triangle + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = triangleSize, + Colour = overlayColourProvider.Background3, + }, }, swing = new Container { RelativeSizeAxes = Axes.Both, - Y = -25, + Y = stick_vertical_offset, Height = 0.8f, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Children = new Drawable[] + Children = new[] { - stick = new Box + stick = new Circle { RelativeSizeAxes = Axes.Y, Colour = overlayColourProvider.Colour2, @@ -150,24 +159,64 @@ namespace osu.Game.Screens.Edit.Timing Origin = Anchor.BottomCentre, Width = 4, }, - weight = new Box + weight = new Container { Anchor = Anchor.TopCentre, Origin = Anchor.Centre, Colour = overlayColourProvider.Colour1, - Size = new Vector2(15), + Size = new Vector2(10), + Rotation = 180, RelativePositionAxes = Axes.Y, Y = 0.4f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Shear = new Vector2(0.2f, 0), + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Shear = new Vector2(-0.2f, 0), + }, + } }, } }, new Circle { - Y = -25, + Y = stick_vertical_offset, Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, Colour = overlayColourProvider.Colour0, - Size = new Vector2(10) + Size = new Vector2(8) + }, + new Container + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Masking = true, + Height = 0.3f, + Children = new Drawable[] + { + new Triangle + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = triangleSize, + Colour = overlayColourProvider.Background2, + Alpha = 0.8f + }, + } + }, + bpm = new OsuSpriteText + { + Colour = overlayColourProvider.Content1, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Y = -3, }, }; } @@ -194,7 +243,7 @@ namespace osu.Game.Screens.Edit.Timing bpmRatio = (float)Interpolation.ApplyEasing(Easing.OutQuad, Math.Clamp((timingPoint.BPM - 30) / 480, 0, 1)); - weight.MoveToY((float)Interpolation.Lerp(0, 0.9f, bpmRatio), 600, Easing.OutQuint); + weight.MoveToY((float)Interpolation.Lerp(0.07f, 0.9f, bpmRatio), 600, Easing.OutQuint); } if (BeatSyncClock?.IsRunning != true && isSwinging) @@ -215,7 +264,7 @@ namespace osu.Game.Screens.Edit.Timing { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - float angle = (float)Interpolation.Lerp(25, 4, bpmRatio); + float angle = (float)Interpolation.Lerp(30, 5, bpmRatio); if (!IsBeatSyncedWithTrack) return; From 79878a4ec21cab922d4ed10295fa0f9eac8ecda2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 16:25:24 +0900 Subject: [PATCH 22/50] Metrics adjust and labelling --- .../Screens/Edit/Timing/TapTimingControl.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 7507a7dd35..3aff74d359 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -116,26 +116,27 @@ namespace osu.Game.Screens.Edit.Timing [BackgroundDependencyLoader] private void load() { + const float taper = 30; + const float swing_vertical_offset = -23; + + var triangleSize = new Vector2(90, 120 + taper); + Margin = new MarginPadding(10); - const float taper = 10; - - var triangleSize = new Vector2(80, 120); - AutoSizeAxes = Axes.Both; - const float stick_vertical_offset = -23; - InternalChildren = new Drawable[] { new Container { + Name = @"Taper adjust", Masking = true, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Size = new Vector2(triangleSize.X * 1.2f, triangleSize.Y - taper), Child = new Triangle { + Name = @"Main body", Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Size = triangleSize, @@ -144,15 +145,17 @@ namespace osu.Game.Screens.Edit.Timing }, swing = new Container { + Name = @"Swing", RelativeSizeAxes = Axes.Both, - Y = stick_vertical_offset, - Height = 0.8f, + Y = swing_vertical_offset, + Height = 0.80f, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Children = new[] { stick = new Circle { + Name = @"Stick", RelativeSizeAxes = Axes.Y, Colour = overlayColourProvider.Colour2, Anchor = Anchor.BottomCentre, @@ -161,6 +164,7 @@ namespace osu.Game.Screens.Edit.Timing }, weight = new Container { + Name = @"Weight", Anchor = Anchor.TopCentre, Origin = Anchor.Centre, Colour = overlayColourProvider.Colour1, @@ -186,7 +190,8 @@ namespace osu.Game.Screens.Edit.Timing }, new Circle { - Y = stick_vertical_offset, + Name = @"Swing connection point", + Y = swing_vertical_offset, Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, Colour = overlayColourProvider.Colour0, @@ -194,6 +199,7 @@ namespace osu.Game.Screens.Edit.Timing }, new Container { + Name = @"Lower cover", Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.Both, @@ -213,6 +219,7 @@ namespace osu.Game.Screens.Edit.Timing }, bpm = new OsuSpriteText { + Name = @"BPM display", Colour = overlayColourProvider.Content1, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, From 21072a26ef30547bbbee7b802bcd918caac4296c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 16:33:27 +0900 Subject: [PATCH 23/50] Add locking wedge --- .../Screens/Edit/Timing/TapTimingControl.cs | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 3aff74d359..323561f0a8 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -133,14 +133,17 @@ namespace osu.Game.Screens.Edit.Timing Masking = true, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Size = new Vector2(triangleSize.X * 1.2f, triangleSize.Y - taper), - Child = new Triangle + Size = new Vector2(triangleSize.X, triangleSize.Y - taper), + Children = new Drawable[] { - Name = @"Main body", - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = triangleSize, - Colour = overlayColourProvider.Background3, + new Triangle + { + Name = @"Main body", + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = triangleSize, + Colour = overlayColourProvider.Background3, + }, }, }, swing = new Container @@ -188,6 +191,25 @@ namespace osu.Game.Screens.Edit.Timing }, } }, + new Container + { + Name = @"Taper adjust", + Masking = true, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(triangleSize.X, triangleSize.Y - taper), + Children = new Drawable[] + { + new Circle + { + Name = @"Locking wedge", + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Colour = overlayColourProvider.Background1, + Size = new Vector2(8), + } + }, + }, new Circle { Name = @"Swing connection point", From 8487d2c48a41010245ba0b1a527ab821935520ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 16:45:01 +0900 Subject: [PATCH 24/50] Interpolate bpm label and fix incorrect angle logic --- .../Screens/Edit/Timing/TapTimingControl.cs | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 323561f0a8..4ab731c897 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -105,7 +106,7 @@ namespace osu.Game.Screens.Edit.Timing { private Container swing; - private OsuSpriteText bpm; + private OsuSpriteText bpmText; private Drawable weight; private Drawable stick; @@ -116,7 +117,7 @@ namespace osu.Game.Screens.Edit.Timing [BackgroundDependencyLoader] private void load() { - const float taper = 30; + const float taper = 25; const float swing_vertical_offset = -23; var triangleSize = new Vector2(90, 120 + taper); @@ -226,7 +227,7 @@ namespace osu.Game.Screens.Edit.Timing Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.Both, Masking = true, - Height = 0.3f, + Height = 0.28f, Children = new Drawable[] { new Triangle @@ -239,7 +240,7 @@ namespace osu.Game.Screens.Edit.Timing }, } }, - bpm = new OsuSpriteText + bpmText = new OsuSpriteText { Name = @"BPM display", Colour = overlayColourProvider.Content1, @@ -254,9 +255,17 @@ namespace osu.Game.Screens.Edit.Timing private TimingControlPoint timingPoint; - private float bpmRatio; private bool isSwinging; + private readonly BindableInt interpolatedBpm = new BindableInt(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + interpolatedBpm.BindValueChanged(bpm => bpmText.Text = bpm.NewValue.ToString()); + } + protected override void Update() { base.Update(); @@ -266,13 +275,13 @@ namespace osu.Game.Screens.Edit.Timing if (beatLength != timingPoint.BeatLength) { beatLength = timingPoint.BeatLength; - bpm.Text = $"{timingPoint.BPM:F0}"; EarlyActivationMilliseconds = timingPoint.BeatLength / 2; - bpmRatio = (float)Interpolation.ApplyEasing(Easing.OutQuad, Math.Clamp((timingPoint.BPM - 30) / 480, 0, 1)); + float bpmRatio = (float)Interpolation.ApplyEasing(Easing.OutQuad, Math.Clamp((timingPoint.BPM - 30) / 480, 0, 1)); - weight.MoveToY((float)Interpolation.Lerp(0.07f, 0.9f, bpmRatio), 600, Easing.OutQuint); + weight.MoveToY((float)Interpolation.Lerp(0.1f, 0.83f, bpmRatio), 600, Easing.OutQuint); + this.TransformBindableTo(interpolatedBpm, (int)timingPoint.BPM, 600, Easing.OutQuint); } if (BeatSyncClock?.IsRunning != true && isSwinging) @@ -293,7 +302,7 @@ namespace osu.Game.Screens.Edit.Timing { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - float angle = (float)Interpolation.Lerp(30, 5, bpmRatio); + const float angle = 27.5f; if (!IsBeatSyncedWithTrack) return; From 4712e512d71810e0be81428275382372eca91fac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 16:48:07 +0900 Subject: [PATCH 25/50] Apply edge smoothness and make weight more accented --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 4ab731c897..bdab427c58 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -140,6 +140,7 @@ namespace osu.Game.Screens.Edit.Timing new Triangle { Name = @"Main body", + EdgeSmoothness = new Vector2(1), Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Size = triangleSize, @@ -171,7 +172,7 @@ namespace osu.Game.Screens.Edit.Timing Name = @"Weight", Anchor = Anchor.TopCentre, Origin = Anchor.Centre, - Colour = overlayColourProvider.Colour1, + Colour = overlayColourProvider.Colour0, Size = new Vector2(10), Rotation = 180, RelativePositionAxes = Axes.Y, @@ -182,11 +183,13 @@ namespace osu.Game.Screens.Edit.Timing { RelativeSizeAxes = Axes.Both, Shear = new Vector2(0.2f, 0), + EdgeSmoothness = new Vector2(1), }, new Box { RelativeSizeAxes = Axes.Both, Shear = new Vector2(-0.2f, 0), + EdgeSmoothness = new Vector2(1), }, } }, @@ -236,6 +239,7 @@ namespace osu.Game.Screens.Edit.Timing Origin = Anchor.BottomCentre, Size = triangleSize, Colour = overlayColourProvider.Background2, + EdgeSmoothness = new Vector2(1), Alpha = 0.8f }, } From cf97f4e409aa57e7883b0a36d037ae588c4683b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 16:55:07 +0900 Subject: [PATCH 26/50] Add centre marker --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index bdab427c58..a99468fbff 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -119,6 +119,7 @@ namespace osu.Game.Screens.Edit.Timing { const float taper = 25; const float swing_vertical_offset = -23; + const float lower_cover_height = 32; var triangleSize = new Vector2(90, 120 + taper); @@ -148,6 +149,17 @@ namespace osu.Game.Screens.Edit.Timing }, }, }, + new Circle + { + Name = "Centre marker", + Colour = overlayColourProvider.Background5, + RelativeSizeAxes = Axes.Y, + Width = 2, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Y = -(lower_cover_height + 3), + Height = 0.65f, + }, swing = new Container { Name = @"Swing", @@ -228,9 +240,9 @@ namespace osu.Game.Screens.Edit.Timing Name = @"Lower cover", Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, Masking = true, - Height = 0.28f, + Height = lower_cover_height, Children = new Drawable[] { new Triangle From 2e21d75b101d4aabb3a2dba05ef2e7306c6976ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 17:12:35 +0900 Subject: [PATCH 27/50] Move metronome into own class and rename to avoid conflict with mod sounds --- .../Mods/TestSceneOsuModMuted.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 2 +- .../Editing/TestSceneTapTimingControl.cs | 6 +- .../Mods/{Metronome.cs => MetronomeBeat.cs} | 4 +- osu.Game/Rulesets/Mods/ModMuted.cs | 6 +- .../Screens/Edit/Timing/MetronomeDisplay.cs | 255 +++++++++++++++++ .../Screens/Edit/Timing/TapTimingControl.cs | 261 +----------------- 7 files changed, 277 insertions(+), 259 deletions(-) rename osu.Game/Rulesets/Mods/{Metronome.cs => MetronomeBeat.cs} (95%) create mode 100644 osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs index c14dc78f38..e08d66fa31 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods MuteComboCount = { Value = 0 }, }, PassCondition = () => Beatmap.Value.Track.AggregateVolume.Value == 0.0 && - Player.ChildrenOfType().SingleOrDefault()?.AggregateVolume.Value == 1.0, + Player.ChildrenOfType().SingleOrDefault()?.AggregateVolume.Value == 1.0, }); /// diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 5b121f4673..c58c624f5c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -339,7 +339,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - drawableRuleset.Overlays.Add(new Metronome(drawableRuleset.Beatmap.HitObjects.First().StartTime)); + drawableRuleset.Overlays.Add(new MetronomeBeat(drawableRuleset.Beatmap.HitObjects.First().StartTime)); } #endregion diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs index 9b2618df5b..de441995b5 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("click tap button", () => { control.ChildrenOfType() - .First(b => b.Text == "Tap to beat") + .Last() .TriggerClick(); }); @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("click reset button", () => { control.ChildrenOfType() - .First(b => b.Text == "Reset") + .First() .TriggerClick(); }); @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("click tap button", () => { control.ChildrenOfType() - .First(b => b.Text == "Tap to beat") + .Last() .TriggerClick(); }); diff --git a/osu.Game/Rulesets/Mods/Metronome.cs b/osu.Game/Rulesets/Mods/MetronomeBeat.cs similarity index 95% rename from osu.Game/Rulesets/Mods/Metronome.cs rename to osu.Game/Rulesets/Mods/MetronomeBeat.cs index b85a341577..c7a8b02130 100644 --- a/osu.Game/Rulesets/Mods/Metronome.cs +++ b/osu.Game/Rulesets/Mods/MetronomeBeat.cs @@ -11,14 +11,14 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Mods { - public class Metronome : BeatSyncedContainer, IAdjustableAudioComponent + public class MetronomeBeat : BeatSyncedContainer, IAdjustableAudioComponent { private readonly double firstHitTime; private readonly PausableSkinnableSound sample; /// Start time of the first hit object, used for providing a count down. - public Metronome(double firstHitTime) + public MetronomeBeat(double firstHitTime) { this.firstHitTime = firstHitTime; AllowMistimedEventFiring = false; diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index a7d3114f2b..84341faab7 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -79,11 +79,11 @@ namespace osu.Game.Rulesets.Mods { if (EnableMetronome.Value) { - Metronome metronome; + MetronomeBeat metronomeBeat; - drawableRuleset.Overlays.Add(metronome = new Metronome(drawableRuleset.Beatmap.HitObjects.First().StartTime)); + drawableRuleset.Overlays.Add(metronomeBeat = new MetronomeBeat(drawableRuleset.Beatmap.HitObjects.First().StartTime)); - metronome.AddAdjustment(AdjustableProperty.Volume, metronomeVolumeAdjust); + metronomeBeat.AddAdjustment(AdjustableProperty.Volume, metronomeVolumeAdjust); } if (AffectsHitSounds.Value) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs new file mode 100644 index 0000000000..fdd5bd1e4e --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -0,0 +1,255 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Edit.Timing +{ + public class MetronomeDisplay : BeatSyncedContainer + { + private Container swing; + + private OsuSpriteText bpmText; + + private Drawable weight; + private Drawable stick; + + [Resolved] + private OverlayColourProvider overlayColourProvider { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + const float taper = 25; + const float swing_vertical_offset = -23; + const float lower_cover_height = 32; + + var triangleSize = new Vector2(90, 120 + taper); + + Margin = new MarginPadding(10); + + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Container + { + Name = @"Taper adjust", + Masking = true, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(triangleSize.X, triangleSize.Y - taper), + Children = new Drawable[] + { + new Triangle + { + Name = @"Main body", + EdgeSmoothness = new Vector2(1), + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = triangleSize, + Colour = overlayColourProvider.Background3, + }, + }, + }, + new Circle + { + Name = "Centre marker", + Colour = overlayColourProvider.Background5, + RelativeSizeAxes = Axes.Y, + Width = 2, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Y = -(lower_cover_height + 3), + Height = 0.65f, + }, + swing = new Container + { + Name = @"Swing", + RelativeSizeAxes = Axes.Both, + Y = swing_vertical_offset, + Height = 0.80f, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Children = new[] + { + stick = new Circle + { + Name = @"Stick", + RelativeSizeAxes = Axes.Y, + Colour = overlayColourProvider.Colour2, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Width = 4, + }, + weight = new Container + { + Name = @"Weight", + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Colour = overlayColourProvider.Colour0, + Size = new Vector2(10), + Rotation = 180, + RelativePositionAxes = Axes.Y, + Y = 0.4f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Shear = new Vector2(0.2f, 0), + EdgeSmoothness = new Vector2(1), + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Shear = new Vector2(-0.2f, 0), + EdgeSmoothness = new Vector2(1), + }, + } + }, + } + }, + new Container + { + Name = @"Taper adjust", + Masking = true, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(triangleSize.X, triangleSize.Y - taper), + Children = new Drawable[] + { + new Circle + { + Name = @"Locking wedge", + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Colour = overlayColourProvider.Background1, + Size = new Vector2(8), + } + }, + }, + new Circle + { + Name = @"Swing connection point", + Y = swing_vertical_offset, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + Colour = overlayColourProvider.Colour0, + Size = new Vector2(8) + }, + new Container + { + Name = @"Lower cover", + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Masking = true, + Height = lower_cover_height, + Children = new Drawable[] + { + new Triangle + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = triangleSize, + Colour = overlayColourProvider.Background2, + EdgeSmoothness = new Vector2(1), + Alpha = 0.8f + }, + } + }, + bpmText = new OsuSpriteText + { + Name = @"BPM display", + Colour = overlayColourProvider.Content1, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Y = -3, + }, + }; + } + + private double beatLength; + + private TimingControlPoint timingPoint; + + private bool isSwinging; + + private readonly BindableInt interpolatedBpm = new BindableInt(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + interpolatedBpm.BindValueChanged(bpm => bpmText.Text = bpm.NewValue.ToString()); + } + + protected override void Update() + { + base.Update(); + + timingPoint = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(BeatSyncClock.CurrentTime); + + if (beatLength != timingPoint.BeatLength) + { + beatLength = timingPoint.BeatLength; + + EarlyActivationMilliseconds = timingPoint.BeatLength / 2; + + float bpmRatio = (float)Interpolation.ApplyEasing(Easing.OutQuad, Math.Clamp((timingPoint.BPM - 30) / 480, 0, 1)); + + weight.MoveToY((float)Interpolation.Lerp(0.1f, 0.83f, bpmRatio), 600, Easing.OutQuint); + this.TransformBindableTo(interpolatedBpm, (int)timingPoint.BPM, 600, Easing.OutQuint); + } + + if (BeatSyncClock?.IsRunning != true && isSwinging) + { + swing.ClearTransforms(true); + + using (swing.BeginDelayedSequence(350)) + { + swing.RotateTo(0, 1000, Easing.OutQuint); + stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); + } + + isSwinging = false; + } + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + const float angle = 27.5f; + + if (!IsBeatSyncedWithTrack) + return; + + isSwinging = true; + + float currentAngle = swing.Rotation; + float targetAngle = currentAngle > 0 ? -angle : angle; + + swing.RotateTo(targetAngle, beatLength, Easing.InOutQuad); + + if (currentAngle != 0 && Math.Abs(currentAngle - targetAngle) > angle * 1.8f && isSwinging) + { + using (stick.BeginDelayedSequence(beatLength / 2)) + stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index a99468fbff..1b0f0a3f5e 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -1,21 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; -using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -24,6 +18,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private EditorClock editorClock { get; set; } + [Resolved] + private Bindable selectedGroup { get; set; } + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { @@ -52,7 +49,11 @@ namespace osu.Game.Screens.Edit.Timing { new Drawable[] { - new Metronome() + new MetronomeDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } }, new Drawable[] { @@ -74,7 +75,7 @@ namespace osu.Game.Screens.Edit.Timing { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Text = "Tap to beat", + Text = "Play from start", RelativeSizeAxes = Axes.X, BackgroundColour = colourProvider.Background1, Width = 0.68f, @@ -90,252 +91,14 @@ namespace osu.Game.Screens.Edit.Timing private void tap() { - if (!editorClock.IsRunning) - { - editorClock.Seek(0); - editorClock.Start(); - } + editorClock.Seek(selectedGroup.Value.Time); + editorClock.Start(); } private void reset() { editorClock.Stop(); - } - - private class Metronome : BeatSyncedContainer - { - private Container swing; - - private OsuSpriteText bpmText; - - private Drawable weight; - private Drawable stick; - - [Resolved] - private OverlayColourProvider overlayColourProvider { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - const float taper = 25; - const float swing_vertical_offset = -23; - const float lower_cover_height = 32; - - var triangleSize = new Vector2(90, 120 + taper); - - Margin = new MarginPadding(10); - - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - new Container - { - Name = @"Taper adjust", - Masking = true, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = new Vector2(triangleSize.X, triangleSize.Y - taper), - Children = new Drawable[] - { - new Triangle - { - Name = @"Main body", - EdgeSmoothness = new Vector2(1), - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = triangleSize, - Colour = overlayColourProvider.Background3, - }, - }, - }, - new Circle - { - Name = "Centre marker", - Colour = overlayColourProvider.Background5, - RelativeSizeAxes = Axes.Y, - Width = 2, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -(lower_cover_height + 3), - Height = 0.65f, - }, - swing = new Container - { - Name = @"Swing", - RelativeSizeAxes = Axes.Both, - Y = swing_vertical_offset, - Height = 0.80f, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Children = new[] - { - stick = new Circle - { - Name = @"Stick", - RelativeSizeAxes = Axes.Y, - Colour = overlayColourProvider.Colour2, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Width = 4, - }, - weight = new Container - { - Name = @"Weight", - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - Colour = overlayColourProvider.Colour0, - Size = new Vector2(10), - Rotation = 180, - RelativePositionAxes = Axes.Y, - Y = 0.4f, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Shear = new Vector2(0.2f, 0), - EdgeSmoothness = new Vector2(1), - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Shear = new Vector2(-0.2f, 0), - EdgeSmoothness = new Vector2(1), - }, - } - }, - } - }, - new Container - { - Name = @"Taper adjust", - Masking = true, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = new Vector2(triangleSize.X, triangleSize.Y - taper), - Children = new Drawable[] - { - new Circle - { - Name = @"Locking wedge", - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - Colour = overlayColourProvider.Background1, - Size = new Vector2(8), - } - }, - }, - new Circle - { - Name = @"Swing connection point", - Y = swing_vertical_offset, - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre, - Colour = overlayColourProvider.Colour0, - Size = new Vector2(8) - }, - new Container - { - Name = @"Lower cover", - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Masking = true, - Height = lower_cover_height, - Children = new Drawable[] - { - new Triangle - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = triangleSize, - Colour = overlayColourProvider.Background2, - EdgeSmoothness = new Vector2(1), - Alpha = 0.8f - }, - } - }, - bpmText = new OsuSpriteText - { - Name = @"BPM display", - Colour = overlayColourProvider.Content1, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -3, - }, - }; - } - - private double beatLength; - - private TimingControlPoint timingPoint; - - private bool isSwinging; - - private readonly BindableInt interpolatedBpm = new BindableInt(); - - protected override void LoadComplete() - { - base.LoadComplete(); - - interpolatedBpm.BindValueChanged(bpm => bpmText.Text = bpm.NewValue.ToString()); - } - - protected override void Update() - { - base.Update(); - - timingPoint = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(BeatSyncClock.CurrentTime); - - if (beatLength != timingPoint.BeatLength) - { - beatLength = timingPoint.BeatLength; - - EarlyActivationMilliseconds = timingPoint.BeatLength / 2; - - float bpmRatio = (float)Interpolation.ApplyEasing(Easing.OutQuad, Math.Clamp((timingPoint.BPM - 30) / 480, 0, 1)); - - weight.MoveToY((float)Interpolation.Lerp(0.1f, 0.83f, bpmRatio), 600, Easing.OutQuint); - this.TransformBindableTo(interpolatedBpm, (int)timingPoint.BPM, 600, Easing.OutQuint); - } - - if (BeatSyncClock?.IsRunning != true && isSwinging) - { - swing.ClearTransforms(true); - - using (swing.BeginDelayedSequence(350)) - { - swing.RotateTo(0, 1000, Easing.OutQuint); - stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); - } - - isSwinging = false; - } - } - - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) - { - base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - - const float angle = 27.5f; - - if (!IsBeatSyncedWithTrack) - return; - - isSwinging = true; - - float currentAngle = swing.Rotation; - float targetAngle = currentAngle > 0 ? -angle : angle; - - swing.RotateTo(targetAngle, beatLength, Easing.InOutQuad); - - if (currentAngle != 0 && Math.Abs(currentAngle - targetAngle) > angle * 1.8f && isSwinging) - { - using (stick.BeginDelayedSequence(beatLength / 2)) - stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); - } - } + editorClock.Seek(selectedGroup.Value.Time); } } } From 82eb5fd2c9007945d7432e22433ebeded94b7066 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 17:21:56 +0900 Subject: [PATCH 28/50] Add centre line on weight --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index fdd5bd1e4e..5429165fe0 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; @@ -99,7 +100,6 @@ namespace osu.Game.Screens.Edit.Timing Name = @"Weight", Anchor = Anchor.TopCentre, Origin = Anchor.Centre, - Colour = overlayColourProvider.Colour0, Size = new Vector2(10), Rotation = 180, RelativePositionAxes = Axes.Y, @@ -110,14 +110,25 @@ namespace osu.Game.Screens.Edit.Timing { RelativeSizeAxes = Axes.Both, Shear = new Vector2(0.2f, 0), + Colour = overlayColourProvider.Colour1, EdgeSmoothness = new Vector2(1), }, new Box { RelativeSizeAxes = Axes.Both, Shear = new Vector2(-0.2f, 0), + Colour = overlayColourProvider.Colour1, EdgeSmoothness = new Vector2(1), }, + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = ColourInfo.GradientVertical(overlayColourProvider.Colour1, overlayColourProvider.Colour0), + RelativeSizeAxes = Axes.Y, + Width = 1, + Height = 0.9f + }, } }, } From b29172ea45fea63d8e48b85aee346d9d607f0dfa Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 19 May 2022 19:55:51 +0900 Subject: [PATCH 29/50] Increase HP lost for misses --- osu.Game/Rulesets/Judgements/Judgement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 4fe34e15fd..99dce82ec2 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Judgements return -DEFAULT_MAX_HEALTH_INCREASE; case HitResult.Miss: - return -DEFAULT_MAX_HEALTH_INCREASE; + return -DEFAULT_MAX_HEALTH_INCREASE * 2; case HitResult.Meh: return DEFAULT_MAX_HEALTH_INCREASE * 0.05; From c3bfbe888003f733137617e8629725bdadd1c506 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 23:23:51 +0900 Subject: [PATCH 30/50] Allow `BeatSyncedContainer` to prefer `EditorBeatmap` when available --- .../TestSceneBeatSyncedContainer.cs | 19 +------------ .../Containers/BeatSyncedContainer.cs | 28 +++++++++++++------ 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index ede89c6096..1881f6d718 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -258,24 +258,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - Beatmap.BindValueChanged(_ => - { - timingPointCount.Value = 0; - currentTimingPoint.Value = 0; - beatCount.Value = 0; - currentBeat.Value = 0; - beatsPerMinute.Value = 0; - adjustedBeatLength.Value = 0; - timeUntilNextBeat.Value = 0; - timeSinceLastBeat.Value = 0; - }, true); - } - - private List timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ToList(); + private List timingPoints => Beatmap.ControlPointInfo.TimingPoints.ToList(); private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 2024d18570..48936f7aa9 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Screens.Edit; using osu.Game.Screens.Play; namespace osu.Game.Graphics.Containers @@ -79,20 +80,31 @@ namespace osu.Game.Graphics.Containers } [Resolved] - protected IBindable Beatmap { get; private set; } + private IBindable beatmap { get; set; } [Resolved(canBeNull: true)] protected GameplayClock GameplayClock { get; private set; } + [Resolved(canBeNull: true)] + protected EditorBeatmap EditorBeatmap { get; private set; } + + [Resolved(canBeNull: true)] + protected EditorClock EditorClock { get; private set; } + + protected IBeatmap Beatmap => EditorBeatmap ?? beatmap?.Value.Beatmap; + protected IClock BeatSyncClock { get { + if (EditorClock != null) + return EditorClock; + if (GameplayClock != null) return GameplayClock; - if (Beatmap.Value.TrackLoaded) - return Beatmap.Value.Track; + if (beatmap.Value.TrackLoaded) + return beatmap.Value.Track; return null; } @@ -101,7 +113,6 @@ namespace osu.Game.Graphics.Containers protected override void Update() { ITrack track = null; - IBeatmap beatmap = null; TimingControlPoint timingPoint; EffectControlPoint effectPoint; @@ -113,10 +124,9 @@ namespace osu.Game.Graphics.Containers double currentTrackTime = clock.CurrentTime + EarlyActivationMilliseconds; - if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded) + if (this.beatmap.Value.TrackLoaded && this.beatmap.Value.BeatmapLoaded) { - track = Beatmap.Value.Track; - beatmap = Beatmap.Value.Beatmap; + track = this.beatmap.Value.Track; } IsBeatSyncedWithTrack = beatmap != null && clock.IsRunning && track?.Length > 0; @@ -125,8 +135,8 @@ namespace osu.Game.Graphics.Containers { Debug.Assert(beatmap != null); - timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); - effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); + timingPoint = Beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); + effectPoint = Beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); } else { From 8db80b92bb04f642e58e95de70c6309e2be44876 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 23:24:21 +0900 Subject: [PATCH 31/50] Fix metronome not using `EditorBeatmap` causing editor updates to not propagate immediately --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 5429165fe0..d51d7b9c58 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; @@ -30,6 +31,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -205,14 +209,14 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - interpolatedBpm.BindValueChanged(bpm => bpmText.Text = bpm.NewValue.ToString()); + interpolatedBpm.BindValueChanged(bpm => bpmText.Text = bpm.NewValue.ToString(CultureInfo.CurrentCulture)); } protected override void Update() { base.Update(); - timingPoint = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(BeatSyncClock.CurrentTime); + timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(BeatSyncClock.CurrentTime); if (beatLength != timingPoint.BeatLength) { @@ -223,7 +227,7 @@ namespace osu.Game.Screens.Edit.Timing float bpmRatio = (float)Interpolation.ApplyEasing(Easing.OutQuad, Math.Clamp((timingPoint.BPM - 30) / 480, 0, 1)); weight.MoveToY((float)Interpolation.Lerp(0.1f, 0.83f, bpmRatio), 600, Easing.OutQuint); - this.TransformBindableTo(interpolatedBpm, (int)timingPoint.BPM, 600, Easing.OutQuint); + this.TransformBindableTo(interpolatedBpm, (int)Math.Round(timingPoint.BPM), 600, Easing.OutQuint); } if (BeatSyncClock?.IsRunning != true && isSwinging) From d3f115bfe2b321a9309be155995d44e6fc0423a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 May 2022 13:53:58 +0900 Subject: [PATCH 32/50] Remove redundant qualifiers --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 48936f7aa9..bd46a20434 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -124,9 +124,9 @@ namespace osu.Game.Graphics.Containers double currentTrackTime = clock.CurrentTime + EarlyActivationMilliseconds; - if (this.beatmap.Value.TrackLoaded && this.beatmap.Value.BeatmapLoaded) + if (beatmap.Value.TrackLoaded && beatmap.Value.BeatmapLoaded) { - track = this.beatmap.Value.Track; + track = beatmap.Value.Track; } IsBeatSyncedWithTrack = beatmap != null && clock.IsRunning && track?.Length > 0; From a7aa36a825cc9589ee6f3cf5bf2f5792d8df09cd Mon Sep 17 00:00:00 2001 From: maromalo <54760464+maromalo@users.noreply.github.com> Date: Sat, 21 May 2022 04:55:42 -0300 Subject: [PATCH 33/50] Add OnPressed to button --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 13b2c37ded..61798b5413 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -15,9 +15,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Input.Bindings; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -473,8 +476,19 @@ namespace osu.Game.Screens.OnlinePlay.Match /// The room to change the settings of. protected abstract RoomSettingsOverlay CreateRoomSettingsOverlay(Room room); - public class UserModSelectButton : PurpleTriangleButton + public class UserModSelectButton : PurpleTriangleButton, IKeyBindingHandler { + public virtual bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == GlobalAction.ToggleModSelection && !e.Repeat) + { + TriggerClick(); + return true; + } + return false; + } + + public virtual void OnReleased(KeyBindingReleaseEvent e) { } } protected override void Dispose(bool isDisposing) From 2bd4c126d39223990260535b2f4874ef8229e0b8 Mon Sep 17 00:00:00 2001 From: maromalo <54760464+maromalo@users.noreply.github.com> Date: Sat, 21 May 2022 05:07:24 -0300 Subject: [PATCH 34/50] Blank line Forgot to save. --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 61798b5413..0dc6e52770 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -485,6 +485,7 @@ namespace osu.Game.Screens.OnlinePlay.Match TriggerClick(); return true; } + return false; } From 376549ce09a2570811ff8b7ea1e6ce3b1636b812 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 May 2022 21:48:51 +0900 Subject: [PATCH 35/50] Use `ToLocalisableString` instead of `CurrentCulture` --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index d51d7b9c58..98ce9f0f46 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -6,6 +6,7 @@ using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -209,7 +210,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - interpolatedBpm.BindValueChanged(bpm => bpmText.Text = bpm.NewValue.ToString(CultureInfo.CurrentCulture)); + interpolatedBpm.BindValueChanged(bpm => bpmText.Text = bpm.NewValue.ToLocalisableString()); } protected override void Update() From 9a780bcad3b6e7e4b14fe11f3bc1f2c23c13c6d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 May 2022 21:56:05 +0900 Subject: [PATCH 36/50] Remove unused using --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 98ce9f0f46..b59865ac1a 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; From d73afcaf484185ac1d1102b8b2c4df4a118c98b5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 12 May 2022 17:29:28 +0300 Subject: [PATCH 37/50] Fix existing test coverage false-passing Also improves general test coverage to test more realisticly, in order to produce accurate results. --- .../TestSceneMultiSpectatorScreen.cs | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 703b526e8c..c048722804 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . 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 NUnit.Framework; @@ -15,13 +16,14 @@ using osu.Game.Configuration; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; -using osu.Game.Online.Rooms; using osu.Game.Rulesets.UI; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; +using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps.IO; +using osuTK; using osuTK.Graphics; namespace osu.Game.Tests.Visual.Multiplayer @@ -349,15 +351,27 @@ namespace osu.Game.Tests.Visual.Multiplayer } /// - /// Tests spectating with a gameplay start time set to a negative value. - /// Simulating beatmaps with high or negative time storyboard elements. + /// Tests spectating with a beatmap that has a high value. /// [Test] - public void TestNegativeGameplayStartTime() + public void TestAudioLeadIn() => testLeadIn(b => b.BeatmapInfo.AudioLeadIn = 2000); + + /// + /// Tests spectating with a beatmap that has a storyboard element with a negative start time (i.e. intro storyboard element). + /// + [Test] + public void TestIntroStoryboardElement() => testLeadIn(b => + { + var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); + sprite.TimelineGroup.Alpha.Add(Easing.None, -2000, 0, 0, 1); + b.Storyboard.GetLayer("Background").Add(sprite); + }); + + private void testLeadIn(Action applyToBeatmap = null) { start(PLAYER_1_ID); - loadSpectateScreen(false, -500); + loadSpectateScreen(false, applyToBeatmap); // to ensure negative gameplay start time does not affect spectator, send frames exactly after StartGameplay(). // (similar to real spectating sessions in which the first frames get sent between StartGameplay() and player load complete) @@ -371,14 +385,16 @@ namespace osu.Game.Tests.Visual.Multiplayer assertRunning(PLAYER_1_ID); } - private void loadSpectateScreen(bool waitForPlayerLoad = true, double? gameplayStartTime = null) + private void loadSpectateScreen(bool waitForPlayerLoad = true, Action applyToBeatmap = null) { - AddStep(!gameplayStartTime.HasValue ? "load screen" : $"load screen (start = {gameplayStartTime}ms)", () => + AddStep("load screen", () => { Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap); Ruleset.Value = importedBeatmap.Ruleset; - LoadScreen(spectatorScreen = new TestMultiSpectatorScreen(SelectedRoom.Value, playingUsers.ToArray(), gameplayStartTime)); + applyToBeatmap?.Invoke(Beatmap.Value); + + LoadScreen(spectatorScreen = new MultiSpectatorScreen(SelectedRoom.Value, playingUsers.ToArray())); }); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded)); @@ -461,19 +477,5 @@ namespace osu.Game.Tests.Visual.Multiplayer private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType().Single(s => s.User?.Id == userId); private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray(); - - private class TestMultiSpectatorScreen : MultiSpectatorScreen - { - private readonly double? startTime; - - public TestMultiSpectatorScreen(Room room, MultiplayerRoomUser[] users, double? startTime = null) - : base(room, users) - { - this.startTime = startTime; - } - - protected override MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) - => new MasterGameplayClockContainer(beatmap, 0) { StartTime = startTime ?? 0 }; - } } } From 27da293b4053d9fce0ca0cd161f32c1e7127d535 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 13 May 2022 00:07:11 +0300 Subject: [PATCH 38/50] Make catch-up spectator clocks running state immutable externally --- .../Spectate/CatchUpSpectatorPlayerClock.cs | 10 ++++++++++ .../Multiplayer/Spectate/CatchUpSyncManager.cs | 2 ++ .../Multiplayer/Spectate/ISpectatorPlayerClock.cs | 10 ++++++++++ 3 files changed, 22 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs index 20d12d62a3..48d0b063ff 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs @@ -34,6 +34,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public void Stop() => IsRunning = false; + void IAdjustableClock.Start() + { + // Our running state should only be managed by an ISyncManager, ignore calls from external sources. + } + + void IAdjustableClock.Stop() + { + // Our running state should only be managed by an ISyncManager, ignore calls from external sources. + } + public bool Seek(double position) { CurrentTime = position; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index b8f47c16ff..b0fdeddd56 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -144,6 +144,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate // Make sure the player clock is running if it can. if (!clock.WaitingOnFrames.Value) clock.Start(); + else + clock.Stop(); if (clock.IsCatchingUp) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs index de23b4fef7..b2ecb105c2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs @@ -11,6 +11,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public interface ISpectatorPlayerClock : IFrameBasedClock, IAdjustableClock { + /// + /// Starts this . + /// + new void Start(); + + /// + /// Stops this . + /// + new void Stop(); + /// /// Whether this clock is waiting on frames to continue playback. /// From efae934e0199713368a4e9cf563cb77a6202f6a6 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Sat, 21 May 2022 17:35:31 +0100 Subject: [PATCH 39/50] Fix slow loading channel test --- osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index 4f916eec18..e27db00003 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -397,6 +397,7 @@ namespace osu.Game.Tests.Visual.Online chatOverlay.SlowLoading = true; }); AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddAssert("Channel 1 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel1).LoadState == LoadState.Loading); AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2)); From fc25d248ad18d4af683347fe0a3b444504e4161c Mon Sep 17 00:00:00 2001 From: maromalo <54760464+maromalo@users.noreply.github.com> Date: Sat, 21 May 2022 18:16:29 -0300 Subject: [PATCH 40/50] Test coverage + no virtual --- .../TestSceneMultiplayerMatchSubScreen.cs | 42 +++++++++++++++++++ .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 4 +- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index ca79fa9cb8..dd281e7738 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -178,6 +178,48 @@ namespace osu.Game.Tests.Visual.Multiplayer .SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime); } + [Test] + public void TestModSelectKeyWithAllowedMods() + { + AddStep("add playlist item with allowed mod", () => + { + SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) } + }); + }); + + ClickButtonWhenEnabled(); + + AddUntilStep("wait for join", () => RoomJoined); + + AddStep("press toggle mod select key", () => InputManager.Key(osuTK.Input.Key.F1)); + + AddUntilStep("mod select contents loaded", + () => this.ChildrenOfType().Any() && this.ChildrenOfType().All(col => col.IsLoaded && col.ItemsLoaded)); + } + + [Test] + public void TestModSelectKeyWithNoAllowedMods() + { + AddStep("add playlist item with no allowed mods", () => + { + SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + }); + }); + ClickButtonWhenEnabled(); + + AddUntilStep("wait for join", () => RoomJoined); + + AddStep("press toggle mod select key", () => InputManager.Key(osuTK.Input.Key.F1)); + + AddAssert("mod select contents not loaded", + () => !(this.ChildrenOfType().Any() && this.ChildrenOfType().All(col => col.IsLoaded && col.ItemsLoaded))); + } + [Test] public void TestNextPlaylistItemSelectedAfterCompletion() { diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 0dc6e52770..a612b6a0e6 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -478,7 +478,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public class UserModSelectButton : PurpleTriangleButton, IKeyBindingHandler { - public virtual bool OnPressed(KeyBindingPressEvent e) + public bool OnPressed(KeyBindingPressEvent e) { if (e.Action == GlobalAction.ToggleModSelection && !e.Repeat) { @@ -489,7 +489,7 @@ namespace osu.Game.Screens.OnlinePlay.Match return false; } - public virtual void OnReleased(KeyBindingReleaseEvent e) { } + public void OnReleased(KeyBindingReleaseEvent e) { } } protected override void Dispose(bool isDisposing) From eabf57828276ce8ce2b2ad16305e54df271b34ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 May 2022 22:15:53 +0900 Subject: [PATCH 41/50] Use interface to convey beat sync information --- .../TestSceneBeatSyncedContainer.cs | 22 +++++-- .../Containers/BeatSyncedContainer.cs | 65 ++++--------------- .../Graphics/Containers/IBeatSyncProvider.cs | 26 ++++++++ osu.Game/OsuGameBase.cs | 10 ++- osu.Game/Rulesets/Mods/MetronomeBeat.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 10 ++- .../Screens/Edit/Timing/MetronomeDisplay.cs | 12 ++-- .../Play/MasterGameplayClockContainer.cs | 8 ++- osu.Game/Screens/Play/Player.cs | 9 ++- 9 files changed, 96 insertions(+), 68 deletions(-) create mode 100644 osu.Game/Graphics/Containers/IBeatSyncProvider.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 1881f6d718..3cbb7daf51 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Audio.Track; @@ -82,11 +83,15 @@ namespace osu.Game.Tests.Visual.UserInterface if (!allowMistimed) { - AddAssert("trigger is near beat length", () => lastActuationTime != null && lastBeatIndex != null && Precision.AlmostEquals(lastTimingPoint.Time + lastBeatIndex.Value * lastTimingPoint.BeatLength, lastActuationTime.Value, BeatSyncedContainer.MISTIMED_ALLOWANCE)); + AddAssert("trigger is near beat length", + () => lastActuationTime != null && lastBeatIndex != null && Precision.AlmostEquals(lastTimingPoint.Time + lastBeatIndex.Value * lastTimingPoint.BeatLength, lastActuationTime.Value, + BeatSyncedContainer.MISTIMED_ALLOWANCE)); } else { - AddAssert("trigger is not near beat length", () => lastActuationTime != null && lastBeatIndex != null && !Precision.AlmostEquals(lastTimingPoint.Time + lastBeatIndex.Value * lastTimingPoint.BeatLength, lastActuationTime.Value, BeatSyncedContainer.MISTIMED_ALLOWANCE)); + AddAssert("trigger is not near beat length", + () => lastActuationTime != null && lastBeatIndex != null && !Precision.AlmostEquals(lastTimingPoint.Time + lastBeatIndex.Value * lastTimingPoint.BeatLength, + lastActuationTime.Value, BeatSyncedContainer.MISTIMED_ALLOWANCE)); } } @@ -258,7 +263,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private List timingPoints => Beatmap.ControlPointInfo.TimingPoints.ToList(); + private List timingPoints => BeatSyncSource.ControlPoints?.TimingPoints.ToList(); private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { @@ -275,7 +280,11 @@ namespace osu.Game.Tests.Visual.UserInterface if (timingPoints.Count == 0) return 0; if (timingPoints[^1] == current) - return (int)Math.Ceiling((BeatSyncClock.CurrentTime - current.Time) / current.BeatLength); + { + Debug.Assert(BeatSyncSource.Clock != null); + + return (int)Math.Ceiling((BeatSyncSource.Clock.CurrentTime - current.Time) / current.BeatLength); + } return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength); } @@ -283,9 +292,12 @@ namespace osu.Game.Tests.Visual.UserInterface protected override void Update() { base.Update(); + + Debug.Assert(BeatSyncSource.Clock != null); + timeUntilNextBeat.Value = TimeUntilNextBeat; timeSinceLastBeat.Value = TimeSinceLastBeat; - currentTime.Value = BeatSyncClock.CurrentTime; + currentTime.Value = BeatSyncSource.Clock.CurrentTime; } public Action NewBeat; diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index bd46a20434..953731244d 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -5,12 +5,9 @@ using System; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Audio.Track; -using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Screens.Edit; using osu.Game.Screens.Play; namespace osu.Game.Graphics.Containers @@ -75,74 +72,38 @@ namespace osu.Game.Graphics.Containers /// protected bool IsBeatSyncedWithTrack { get; private set; } + [Resolved] + protected IBeatSyncProvider BeatSyncSource { get; private set; } + protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { } - [Resolved] - private IBindable beatmap { get; set; } - - [Resolved(canBeNull: true)] - protected GameplayClock GameplayClock { get; private set; } - - [Resolved(canBeNull: true)] - protected EditorBeatmap EditorBeatmap { get; private set; } - - [Resolved(canBeNull: true)] - protected EditorClock EditorClock { get; private set; } - - protected IBeatmap Beatmap => EditorBeatmap ?? beatmap?.Value.Beatmap; - - protected IClock BeatSyncClock - { - get - { - if (EditorClock != null) - return EditorClock; - - if (GameplayClock != null) - return GameplayClock; - - if (beatmap.Value.TrackLoaded) - return beatmap.Value.Track; - - return null; - } - } - protected override void Update() { - ITrack track = null; - TimingControlPoint timingPoint; EffectControlPoint effectPoint; - IClock clock = BeatSyncClock; + IsBeatSyncedWithTrack = BeatSyncSource.Clock?.IsRunning == true; - if (clock == null) - return; - - double currentTrackTime = clock.CurrentTime + EarlyActivationMilliseconds; - - if (beatmap.Value.TrackLoaded && beatmap.Value.BeatmapLoaded) - { - track = beatmap.Value.Track; - } - - IsBeatSyncedWithTrack = beatmap != null && clock.IsRunning && track?.Length > 0; + double currentTrackTime; if (IsBeatSyncedWithTrack) { - Debug.Assert(beatmap != null); + Debug.Assert(BeatSyncSource.ControlPoints != null); + Debug.Assert(BeatSyncSource.Clock != null); - timingPoint = Beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); - effectPoint = Beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); + currentTrackTime = BeatSyncSource.Clock.CurrentTime + EarlyActivationMilliseconds; + + timingPoint = BeatSyncSource.ControlPoints.TimingPointAt(currentTrackTime); + effectPoint = BeatSyncSource.ControlPoints.EffectPointAt(currentTrackTime); } else { // this may be the case where the beat syncing clock has been paused. // we still want to show an idle animation, so use this container's time instead. currentTrackTime = Clock.CurrentTime + EarlyActivationMilliseconds; + timingPoint = TimingControlPoint.DEFAULT; effectPoint = EffectControlPoint.DEFAULT; } @@ -172,7 +133,7 @@ namespace osu.Game.Graphics.Containers if (AllowMistimedEventFiring || Math.Abs(TimeSinceLastBeat) < MISTIMED_ALLOWANCE) { using (BeginDelayedSequence(-TimeSinceLastBeat)) - OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? ChannelAmplitudes.Empty); + OnNewBeat(beatIndex, timingPoint, effectPoint, BeatSyncSource.Amplitudes ?? ChannelAmplitudes.Empty); } lastBeat = beatIndex; diff --git a/osu.Game/Graphics/Containers/IBeatSyncProvider.cs b/osu.Game/Graphics/Containers/IBeatSyncProvider.cs new file mode 100644 index 0000000000..d5be50297c --- /dev/null +++ b/osu.Game/Graphics/Containers/IBeatSyncProvider.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Timing; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Graphics.Containers +{ + /// + /// Provides various data sources which allow for synchronising visuals to a known beat. + /// Primarily intended for use with . + /// + [Cached(typeof(IBeatSyncProvider))] + public interface IBeatSyncProvider + { + ControlPointInfo? ControlPoints { get; } + + IClock? Clock { get; } + + ChannelAmplitudes? Amplitudes { get; } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 2e4758a134..52052efd5d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Development; using osu.Framework.Extensions; @@ -21,12 +22,15 @@ using osu.Framework.Input; using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Input; using osu.Game.Input.Bindings; @@ -52,7 +56,7 @@ namespace osu.Game /// Unlike , this class will not load any kind of UI, allowing it to be used /// for provide dependencies to test cases without interfering with them. /// - public partial class OsuGameBase : Framework.Game, ICanAcceptFiles + public partial class OsuGameBase : Framework.Game, ICanAcceptFiles, IBeatSyncProvider { public const string OSU_PROTOCOL = "osu://"; @@ -552,5 +556,9 @@ namespace osu.Game if (Host != null) Host.ExceptionThrown -= onExceptionThrown; } + + ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.Beatmap.ControlPointInfo; + IClock IBeatSyncProvider.Clock => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track : (IClock)null; + ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; } } diff --git a/osu.Game/Rulesets/Mods/MetronomeBeat.cs b/osu.Game/Rulesets/Mods/MetronomeBeat.cs index c7a8b02130..149af1e30a 100644 --- a/osu.Game/Rulesets/Mods/MetronomeBeat.cs +++ b/osu.Game/Rulesets/Mods/MetronomeBeat.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mods int timeSignature = timingPoint.TimeSignature.Numerator; // play metronome from one measure before the first object. - if (BeatSyncClock.CurrentTime < firstHitTime - timingPoint.BeatLength * timeSignature) + if (BeatSyncSource.Clock?.CurrentTime < firstHitTime - timingPoint.BeatLength * timeSignature) return; sample.Frequency.Value = beatIndex % timeSignature == 0 ? 1 : 0.5f; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3a2b195eed..a50a70374b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -8,6 +8,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -19,11 +20,14 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; +using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; @@ -53,7 +57,7 @@ namespace osu.Game.Screens.Edit { [Cached(typeof(IBeatSnapProvider))] [Cached] - public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider, ISamplePlaybackDisabler + public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider, ISamplePlaybackDisabler, IBeatSyncProvider { public override float BackgroundParallaxAmount => 0.1f; @@ -954,5 +958,9 @@ namespace osu.Game.Screens.Edit public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); public int BeatDivisor => beatDivisor.Value; + + ControlPointInfo IBeatSyncProvider.ControlPoints => editorBeatmap.ControlPointInfo; + IClock IBeatSyncProvider.Clock => clock; + ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; } } diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index b59865ac1a..7b0b8440e6 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -31,9 +31,6 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } - [Resolved] - private EditorBeatmap editorBeatmap { get; set; } - [BackgroundDependencyLoader] private void load() { @@ -216,7 +213,10 @@ namespace osu.Game.Screens.Edit.Timing { base.Update(); - timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(BeatSyncClock.CurrentTime); + if (BeatSyncSource.ControlPoints == null || BeatSyncSource.Clock == null) + return; + + timingPoint = BeatSyncSource.ControlPoints.TimingPointAt(BeatSyncSource.Clock.CurrentTime); if (beatLength != timingPoint.BeatLength) { @@ -230,7 +230,7 @@ namespace osu.Game.Screens.Edit.Timing this.TransformBindableTo(interpolatedBpm, (int)Math.Round(timingPoint.BPM), 600, Easing.OutQuint); } - if (BeatSyncClock?.IsRunning != true && isSwinging) + if (BeatSyncSource.Clock?.IsRunning != true && isSwinging) { swing.ClearTransforms(true); @@ -258,7 +258,7 @@ namespace osu.Game.Screens.Edit.Timing float currentAngle = swing.Rotation; float targetAngle = currentAngle > 0 ? -angle : angle; - swing.RotateTo(targetAngle, beatLength, Easing.InOutQuad); + swing.RotateTo(targetAngle, beatLength, Easing.InOutSine); if (currentAngle != 0 && Math.Abs(currentAngle - targetAngle) > angle * 1.8f && isSwinging) { diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index ea43fb1546..4ca5541362 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -12,8 +12,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Play { @@ -27,7 +29,7 @@ namespace osu.Game.Screens.Play /// /// This is intended to be used as a single controller for gameplay, or as a reference source for other s. /// - public class MasterGameplayClockContainer : GameplayClockContainer + public class MasterGameplayClockContainer : GameplayClockContainer, IBeatSyncProvider { /// /// Duration before gameplay start time required before skip button displays. @@ -250,6 +252,10 @@ namespace osu.Game.Screens.Play removeSourceClockAdjustments(); } + ControlPointInfo IBeatSyncProvider.ControlPoints => beatmap.Beatmap.ControlPointInfo; + IClock IBeatSyncProvider.Clock => GameplayClock; + ChannelAmplitudes? IBeatSyncProvider.Amplitudes => beatmap.TrackLoaded ? beatmap.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; + private class HardwareCorrectionOffsetClock : FramedOffsetClock { private readonly BindableDouble pauseRateAdjust; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 51c1e6b43b..cbd9b03c32 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; @@ -16,8 +17,10 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; +using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.IO.Archives; @@ -38,7 +41,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play { [Cached] - public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler, ILocalUserPlayInfo + public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler, ILocalUserPlayInfo, IBeatSyncProvider { /// /// The delay upon completion of the beatmap before displaying the results screen. @@ -1108,5 +1111,9 @@ namespace osu.Game.Screens.Play IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; IBindable ILocalUserPlayInfo.IsPlaying => LocalUserPlaying; + + ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.Beatmap.ControlPointInfo; + IClock IBeatSyncProvider.Clock => GameplayClockContainer.GameplayClock; + ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; } } From 3a7233bd6ed5e2a3784de1825ac58ea1ec7ae840 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 May 2022 22:49:24 +0900 Subject: [PATCH 42/50] Move interface to more appropriate namespace --- .../{Graphics/Containers => Beatmaps}/IBeatSyncProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename osu.Game/{Graphics/Containers => Beatmaps}/IBeatSyncProvider.cs (91%) diff --git a/osu.Game/Graphics/Containers/IBeatSyncProvider.cs b/osu.Game/Beatmaps/IBeatSyncProvider.cs similarity index 91% rename from osu.Game/Graphics/Containers/IBeatSyncProvider.cs rename to osu.Game/Beatmaps/IBeatSyncProvider.cs index d5be50297c..573f06ef9f 100644 --- a/osu.Game/Graphics/Containers/IBeatSyncProvider.cs +++ b/osu.Game/Beatmaps/IBeatSyncProvider.cs @@ -7,8 +7,9 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; -namespace osu.Game.Graphics.Containers +namespace osu.Game.Beatmaps { /// /// Provides various data sources which allow for synchronising visuals to a known beat. From 007582afb9082782b1c18a61401a673198fe3370 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 May 2022 23:55:33 +0900 Subject: [PATCH 43/50] Remove unused usings resulting from namespace move --- osu.Game/OsuGameBase.cs | 1 - osu.Game/Screens/Edit/Editor.cs | 1 - osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 52052efd5d..ed2bfbc6e3 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -30,7 +30,6 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Input; using osu.Game.Input.Bindings; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a50a70374b..0bb3f51903 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -27,7 +27,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 4ca5541362..d87d57ec49 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -15,7 +15,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Play { From c9ea87e6be1885fdb18bcc4e93bf21b5d0ad11e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 May 2022 00:03:42 +0900 Subject: [PATCH 44/50] Revert easing for now --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 7b0b8440e6..4dd7a75d4a 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -258,7 +258,7 @@ namespace osu.Game.Screens.Edit.Timing float currentAngle = swing.Rotation; float targetAngle = currentAngle > 0 ? -angle : angle; - swing.RotateTo(targetAngle, beatLength, Easing.InOutSine); + swing.RotateTo(targetAngle, beatLength, Easing.InOutQuad); if (currentAngle != 0 && Math.Abs(currentAngle - targetAngle) > angle * 1.8f && isSwinging) { From 267bef959fd7049dcf80557196cd211e3f81686f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 May 2022 02:00:04 +0900 Subject: [PATCH 45/50] Remove unnecessary cache type specification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Beatmaps/IBeatSyncProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/IBeatSyncProvider.cs b/osu.Game/Beatmaps/IBeatSyncProvider.cs index 573f06ef9f..cc1cfc3cb5 100644 --- a/osu.Game/Beatmaps/IBeatSyncProvider.cs +++ b/osu.Game/Beatmaps/IBeatSyncProvider.cs @@ -15,7 +15,7 @@ namespace osu.Game.Beatmaps /// Provides various data sources which allow for synchronising visuals to a known beat. /// Primarily intended for use with . /// - [Cached(typeof(IBeatSyncProvider))] + [Cached] public interface IBeatSyncProvider { ControlPointInfo? ControlPoints { get; } From 9c3d0dafbdba852857197bf13dbc2879328dceb3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 May 2022 02:00:20 +0900 Subject: [PATCH 46/50] Remove implementation at `Player` level Turns out this isn't required in the end due to implementation at `MasterGameplayClockContainer`. --- osu.Game/Screens/Play/Player.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cbd9b03c32..51c1e6b43b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; @@ -17,10 +16,8 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; -using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.IO.Archives; @@ -41,7 +38,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play { [Cached] - public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler, ILocalUserPlayInfo, IBeatSyncProvider + public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler, ILocalUserPlayInfo { /// /// The delay upon completion of the beatmap before displaying the results screen. @@ -1111,9 +1108,5 @@ namespace osu.Game.Screens.Play IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; IBindable ILocalUserPlayInfo.IsPlaying => LocalUserPlaying; - - ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.Beatmap.ControlPointInfo; - IClock IBeatSyncProvider.Clock => GameplayClockContainer.GameplayClock; - ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; } } From 0bef2ca7526295767db063b0b9e92035411cda83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 May 2022 19:27:20 +0200 Subject: [PATCH 47/50] Adjust test slightly * Import `osuTK.Input` instead of using full qualified name * Use some more straightforward assertions --- .../TestSceneMultiplayerMatchSubScreen.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index dd281e7738..7ae81c9800 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Extensions; +using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -28,6 +29,7 @@ using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; +using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { @@ -194,10 +196,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => RoomJoined); - AddStep("press toggle mod select key", () => InputManager.Key(osuTK.Input.Key.F1)); + AddStep("press toggle mod select key", () => InputManager.Key(Key.F1)); - AddUntilStep("mod select contents loaded", - () => this.ChildrenOfType().Any() && this.ChildrenOfType().All(col => col.IsLoaded && col.ItemsLoaded)); + AddUntilStep("mod select shown", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); } [Test] @@ -214,10 +215,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => RoomJoined); - AddStep("press toggle mod select key", () => InputManager.Key(osuTK.Input.Key.F1)); + AddStep("press toggle mod select key", () => InputManager.Key(Key.F1)); - AddAssert("mod select contents not loaded", - () => !(this.ChildrenOfType().Any() && this.ChildrenOfType().All(col => col.IsLoaded && col.ItemsLoaded))); + AddWaitStep("wait some", 3); + AddAssert("mod select not shown", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); } [Test] From afbb1fa750e8560481fbf43509ae166e822d87ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 May 2022 14:06:00 +0900 Subject: [PATCH 48/50] Fix tournament user stat population potentially using wrong ruleset `OsuGameBase.Ruleset` is bound [late](https://github.com/ppy/osu/blob/7d0470794bcd454d3a93954ec4fe3415933627ef/osu.Game.Tournament/TournamentGameBase.cs#L169) so we must use the ladder one during user retrieval. Closes https://github.com/ppy/osu/issues/18363. --- osu.Game.Tournament/TournamentGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 363baccb37..6ae0312cce 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -259,7 +259,7 @@ namespace osu.Game.Tournament public void PopulateUser(APIUser user, Action success = null, Action failure = null, bool immediate = false) { - var req = new GetUserRequest(user.Id, Ruleset.Value); + var req = new GetUserRequest(user.Id, ladder.Ruleset.Value); if (immediate) { From 63a3829768be608c125076767538910104b4fe89 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 23 May 2022 14:19:25 +0900 Subject: [PATCH 49/50] Split out SingleKeyStamina class --- .../Difficulty/Skills/SingleKeyStamina.cs | 42 +++++++++++++++++++ .../Difficulty/Skills/Stamina.cs | 34 --------------- 2 files changed, 42 insertions(+), 34 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Skills/SingleKeyStamina.cs diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SingleKeyStamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SingleKeyStamina.cs new file mode 100644 index 0000000000..cabfd231d8 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SingleKeyStamina.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; + +namespace osu.Game.Rulesets.Taiko.Difficulty.Skills +{ + /// + /// Stamina of a single key, calculated based on repetition speed. + /// + public class SingleKeyStamina + { + private double? previousHitTime; + + /// + /// Similar to + /// + public double StrainValueOf(DifficultyHitObject current) + { + if (previousHitTime == null) + { + previousHitTime = current.StartTime; + return 0; + } + + double objectStrain = 0.5; + objectStrain += speedBonus(current.StartTime - previousHitTime.Value); + previousHitTime = current.StartTime; + return objectStrain; + } + + /// + /// Applies a speed bonus dependent on the time since the last hit performed using this key. + /// + /// The duration between the current and previous note hit using the same key. + private double speedBonus(double notePairDuration) + { + return 175 / (notePairDuration + 100); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 744dafd57e..32d8b70485 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -9,40 +9,6 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { - /// - /// Stamina of a single key, calculated based on repetition speed. - /// - public class SingleKeyStamina - { - private double? previousHitTime; - - /// - /// Similar to - /// - public double StrainValueOf(DifficultyHitObject current) - { - if (previousHitTime == null) - { - previousHitTime = current.StartTime; - return 0; - } - - double objectStrain = 0.5; - objectStrain += speedBonus(current.StartTime - previousHitTime.Value); - previousHitTime = current.StartTime; - return objectStrain; - } - - /// - /// Applies a speed bonus dependent on the time since the last hit performed using this key. - /// - /// The duration between the current and previous note hit using the same key. - private double speedBonus(double notePairDuration) - { - return 175 / (notePairDuration + 100); - } - } - /// /// Calculates the stamina coefficient of taiko difficulty. /// From 6cfe35360a6ed2e666047dce18a45570b098041a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 23 May 2022 14:36:06 +0900 Subject: [PATCH 50/50] Refactor key indexing --- .../Difficulty/Skills/Stamina.cs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 32d8b70485..61bcbfa59d 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -20,26 +20,27 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0.4; - /// - /// Stamina of each individual keys, calculated based on repetition speed. - /// - private readonly SingleKeyStamina[] keyStamina = + private readonly SingleKeyStamina[] centreKeyStamina = { new SingleKeyStamina(), - new SingleKeyStamina(), + new SingleKeyStamina() + }; + + private readonly SingleKeyStamina[] rimKeyStamina = + { new SingleKeyStamina(), new SingleKeyStamina() }; /// - /// Current index to for a don hit. + /// Current index into for a centre hit. /// - private int donIndex = 1; + private int centreKeyIndex; /// - /// Current index to for a kat hit. + /// Current index into for a rim hit. /// - private int katIndex = 3; + private int rimKeyIndex; /// /// Creates a skill. @@ -59,12 +60,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills // Alternate key for the same color. if (current.HitType == HitType.Centre) { - donIndex = donIndex == 0 ? 1 : 0; - return keyStamina[donIndex]; + centreKeyIndex = (centreKeyIndex + 1) % 2; + return centreKeyStamina[centreKeyIndex]; } - katIndex = katIndex == 2 ? 3 : 2; - return keyStamina[katIndex]; + rimKeyIndex = (rimKeyIndex + 1) % 2; + return rimKeyStamina[rimKeyIndex]; } protected override double StrainValueOf(DifficultyHitObject current)