From 2f33aeac9ff14d075957d7c053dd7b384d2caa37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Nov 2020 14:31:07 +0100 Subject: [PATCH 001/100] Move drawable instatiation to [SetUp] --- .../Skinning/ManiaHitObjectTestScene.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs index d24c81dac6..96444fd316 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs @@ -1,7 +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 osu.Framework.Allocation; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Objects.Drawables; @@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning /// public abstract class ManiaHitObjectTestScene : ManiaSkinnableTestScene { - [BackgroundDependencyLoader] - private void load() + [SetUp] + public void SetUp() => Schedule(() => { SetContents(() => new FillFlowContainer { @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning }, } }); - } + }); protected abstract DrawableManiaHitObject CreateHitObject(); } From 2ccc81ccc0ca7406d21e6283c60a77f5849bf681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Nov 2020 14:31:24 +0100 Subject: [PATCH 002/100] Add test case for fading hold note --- .../Skinning/TestSceneHoldNote.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs index 9c4c2b3d5b..e88ff8e2ac 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.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.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; @@ -26,6 +27,18 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning }); } + [Test] + public void TestFadeOnMiss() + { + AddStep("miss tick", () => + { + foreach (var holdNote in holdNotes) + holdNote.ChildrenOfType().First().MissForcefully(); + }); + } + + private IEnumerable holdNotes => CreatedDrawables.SelectMany(d => d.ChildrenOfType()); + protected override DrawableManiaHitObject CreateHitObject() { var note = new HoldNote { Duration = 1000 }; From 55a91dbbe031df2bb677a6518cf035961d4faf1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Nov 2020 15:16:33 +0100 Subject: [PATCH 003/100] Add fading on hit state change --- .../Skinning/LegacyBodyPiece.cs | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index c0f0fcb4af..a9cd7b592f 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.Skinning { public class LegacyBodyPiece : LegacyManiaColumnElement { + private DrawableHoldNote holdNote; + private readonly IBindable direction = new Bindable(); private readonly IBindable isHitting = new Bindable(); @@ -38,6 +40,8 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo, DrawableHitObject drawableObject) { + holdNote = (DrawableHoldNote)drawableObject; + string imageName = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value ?? $"mania-note{FallbackColumnIndex}L"; @@ -92,11 +96,44 @@ namespace osu.Game.Rulesets.Mania.Skinning InternalChild = bodySprite; direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(onDirectionChanged, true); - - var holdNote = (DrawableHoldNote)drawableObject; isHitting.BindTo(holdNote.IsHitting); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + direction.BindValueChanged(onDirectionChanged, true); isHitting.BindValueChanged(onIsHittingChanged, true); + + holdNote.ApplyCustomUpdateState += applyCustomUpdateState; + applyCustomUpdateState(holdNote, holdNote.State.Value); + } + + /// + /// Stores the start time of the fade animation that plays when any of the nested + /// hitobjects of the hold note are missed. + /// + private double? missFadeTime; + + private void applyCustomUpdateState(DrawableHitObject hitObject, ArmedState state) + { + switch (state) + { + case ArmedState.Miss: + missFadeTime ??= hitObject.StateUpdateTime; + + using (BeginAbsoluteSequence(missFadeTime.Value)) + { + // colour and duration matches stable + // transforms not applied to entire hold note in order to not affect hit lighting + holdNote.Head.FadeColour(Colour4.DarkGray, 60); + bodySprite?.FadeColour(Colour4.DarkGray, 60); + holdNote.Tail.FadeColour(Colour4.DarkGray, 60); + } + + break; + } } private void onIsHittingChanged(ValueChangedEvent isHitting) @@ -162,6 +199,9 @@ namespace osu.Game.Rulesets.Mania.Skinning { base.Dispose(isDisposing); + if (holdNote != null) + holdNote.ApplyCustomUpdateState -= applyCustomUpdateState; + lightContainer?.Expire(); } } From 4777b1be8108462cad86c5b81e2865888a1972e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Nov 2020 15:43:10 +0100 Subject: [PATCH 004/100] Fix fade not applying to tails sometimes --- .../Skinning/LegacyBodyPiece.cs | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index a9cd7b592f..a0cbc47df1 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -118,21 +118,22 @@ namespace osu.Game.Rulesets.Mania.Skinning private void applyCustomUpdateState(DrawableHitObject hitObject, ArmedState state) { - switch (state) + if (state == ArmedState.Miss) + missFadeTime = hitObject.StateUpdateTime; + + if (missFadeTime == null) + return; + + // this state update could come from any nested object of the hold note. + // make sure the transforms are consistent across all affected parts + // even if they're idle. + using (BeginAbsoluteSequence(missFadeTime.Value)) { - case ArmedState.Miss: - missFadeTime ??= hitObject.StateUpdateTime; - - using (BeginAbsoluteSequence(missFadeTime.Value)) - { - // colour and duration matches stable - // transforms not applied to entire hold note in order to not affect hit lighting - holdNote.Head.FadeColour(Colour4.DarkGray, 60); - bodySprite?.FadeColour(Colour4.DarkGray, 60); - holdNote.Tail.FadeColour(Colour4.DarkGray, 60); - } - - break; + // colour and duration matches stable + // transforms not applied to entire hold note in order to not affect hit lighting + holdNote.Head.FadeColour(Colour4.DarkGray, 60); + bodySprite?.FadeColour(Colour4.DarkGray, 60); + holdNote.Tail.FadeColour(Colour4.DarkGray, 60); } } From b62bf5798d40d0df7cd0e8ed63e59215dae6a088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Nov 2020 21:14:34 +0100 Subject: [PATCH 005/100] Store time of hold note break --- .../Objects/Drawables/DrawableHoldNote.cs | 8 ++++---- .../Objects/Drawables/DrawableHoldNoteTail.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 59899637f9..a64cc6dc67 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -51,9 +51,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public double? HoldStartTime { get; private set; } /// - /// Whether the hold note has been released too early and shouldn't give full score for the release. + /// Time at which the hold note has been broken, i.e. released too early, resulting in a reduced score. /// - public bool HasBroken { get; private set; } + public double? HoldBrokenTime { get; private set; } /// /// Whether the hold note has been released potentially without having caused a break. @@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } if (Tail.Judged && !Tail.IsHit) - HasBroken = true; + HoldBrokenTime = Time.Current; } public bool OnPressed(ManiaAction action) @@ -298,7 +298,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // If the key has been released too early, the user should not receive full score for the release if (!Tail.IsHit) - HasBroken = true; + HoldBrokenTime = Time.Current; releaseTime = Time.Current; } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index c780c0836e..a4029e7893 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables ApplyResult(r => { // If the head wasn't hit or the hold note was broken, cap the max score to Meh. - if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HasBroken)) + if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HoldBrokenTime != null)) result = HitResult.Meh; r.Type = result; From a199a957cca03746a93b2ebf1e5ffa0a90b43bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Nov 2020 21:47:55 +0100 Subject: [PATCH 006/100] Use stored hold note break time to fade upon it --- .../Skinning/LegacyBodyPiece.cs | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index a0cbc47df1..9ed25115d8 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -23,6 +23,12 @@ namespace osu.Game.Rulesets.Mania.Skinning private readonly IBindable direction = new Bindable(); private readonly IBindable isHitting = new Bindable(); + /// + /// Stores the start time of the fade animation that plays when any of the nested + /// hitobjects of the hold note are missed. + /// + private readonly Bindable missFadeTime = new Bindable(); + [CanBeNull] private Drawable bodySprite; @@ -105,36 +111,16 @@ namespace osu.Game.Rulesets.Mania.Skinning direction.BindValueChanged(onDirectionChanged, true); isHitting.BindValueChanged(onIsHittingChanged, true); + missFadeTime.BindValueChanged(onMissFadeTimeChanged, true); holdNote.ApplyCustomUpdateState += applyCustomUpdateState; applyCustomUpdateState(holdNote, holdNote.State.Value); } - /// - /// Stores the start time of the fade animation that plays when any of the nested - /// hitobjects of the hold note are missed. - /// - private double? missFadeTime; - private void applyCustomUpdateState(DrawableHitObject hitObject, ArmedState state) { if (state == ArmedState.Miss) - missFadeTime = hitObject.StateUpdateTime; - - if (missFadeTime == null) - return; - - // this state update could come from any nested object of the hold note. - // make sure the transforms are consistent across all affected parts - // even if they're idle. - using (BeginAbsoluteSequence(missFadeTime.Value)) - { - // colour and duration matches stable - // transforms not applied to entire hold note in order to not affect hit lighting - holdNote.Head.FadeColour(Colour4.DarkGray, 60); - bodySprite?.FadeColour(Colour4.DarkGray, 60); - holdNote.Tail.FadeColour(Colour4.DarkGray, 60); - } + missFadeTime.Value ??= hitObject.StateUpdateTime; } private void onIsHittingChanged(ValueChangedEvent isHitting) @@ -196,6 +182,29 @@ namespace osu.Game.Rulesets.Mania.Skinning } } + private void onMissFadeTimeChanged(ValueChangedEvent missFadeTimeChange) + { + if (missFadeTimeChange.NewValue == null) + return; + + // this update could come from any nested object of the hold note (or even from an input). + // make sure the transforms are consistent across all affected parts. + using (BeginAbsoluteSequence(missFadeTimeChange.NewValue.Value)) + { + // colour and duration matches stable + // transforms not applied to entire hold note in order to not affect hit lighting + holdNote.Head.FadeColour(Colour4.DarkGray, 60); + holdNote.Tail.FadeColour(Colour4.DarkGray, 60); + bodySprite?.FadeColour(Colour4.DarkGray, 60); + } + } + + protected override void Update() + { + base.Update(); + missFadeTime.Value ??= holdNote.HoldBrokenTime; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From ba30800bf4d242b30f0909bf97a146cced4e6ccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Nov 2020 22:21:22 +0100 Subject: [PATCH 007/100] Extract constant --- osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index 9ed25115d8..f1f72c8618 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -193,9 +193,11 @@ namespace osu.Game.Rulesets.Mania.Skinning { // colour and duration matches stable // transforms not applied to entire hold note in order to not affect hit lighting - holdNote.Head.FadeColour(Colour4.DarkGray, 60); - holdNote.Tail.FadeColour(Colour4.DarkGray, 60); - bodySprite?.FadeColour(Colour4.DarkGray, 60); + const double fade_duration = 60; + + holdNote.Head.FadeColour(Colour4.DarkGray, fade_duration); + holdNote.Tail.FadeColour(Colour4.DarkGray, fade_duration); + bodySprite?.FadeColour(Colour4.DarkGray, fade_duration); } } From dc38aeac4392d44b1b99aa3b4a18e47aa5373f21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Nov 2020 17:23:02 +0900 Subject: [PATCH 008/100] Remove unnecessary local definition of colour logic from taiko judgement --- .../UI/DrawableTaikoJudgement.cs | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index cbfc5a8628..b5e35f88b5 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -1,12 +1,9 @@ // 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.Objects.Drawables; -using osu.Framework.Allocation; -using osu.Game.Graphics; -using osu.Game.Rulesets.Judgements; using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.UI { @@ -25,21 +22,6 @@ namespace osu.Game.Rulesets.Taiko.UI { } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - switch (Result.Type) - { - case HitResult.Ok: - JudgementBody.Colour = colours.GreenLight; - break; - - case HitResult.Great: - JudgementBody.Colour = colours.BlueLight; - break; - } - } - protected override void ApplyHitAnimations() { this.MoveToY(-100, 500); From 1b1f4c9c09cf8669b957b07a4888cf47dcd67199 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Nov 2020 20:35:22 +0900 Subject: [PATCH 009/100] Refactor user request to fix threadsafety issues --- osu.Game/Database/UserLookupCache.cs | 123 ++++++++++----------------- 1 file changed, 47 insertions(+), 76 deletions(-) diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index c85ad6d651..05ba9c882b 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.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 System.Collections.Generic; using System.Linq; using System.Threading; @@ -15,103 +14,75 @@ namespace osu.Game.Database { public class UserLookupCache : MemoryCachingComponent { - private readonly HashSet nextTaskIDs = new HashSet(); - [Resolved] private IAPIProvider api { get; set; } - private readonly object taskAssignmentLock = new object(); - - private Task> pendingRequest; - - /// - /// Whether has already grabbed its IDs. - /// - private bool pendingRequestConsumedIDs; - public Task GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token); protected override async Task ComputeValueAsync(int lookup, CancellationToken token = default) - { - var users = await getQueryTaskForUser(lookup); - return users.FirstOrDefault(u => u.Id == lookup); - } + => await queryUser(lookup); - /// - /// Return the task responsible for fetching the provided user. - /// This may be part of a larger batch lookup to reduce web requests. - /// - /// The user to lookup. - /// The task responsible for the lookup. - private Task> getQueryTaskForUser(int userId) + private readonly List<(int id, TaskCompletionSource)> pendingUserTasks = new List<(int, TaskCompletionSource)>(); + private Task pendingRequestTask; + private readonly object taskAssignmentLock = new object(); + + private Task queryUser(int userId) { lock (taskAssignmentLock) { - nextTaskIDs.Add(userId); + var tcs = new TaskCompletionSource(); - // if there's a pending request which hasn't been started yet (and is not yet full), we can wait on it. - if (pendingRequest != null && !pendingRequestConsumedIDs && nextTaskIDs.Count < 50) - return pendingRequest; + // Add to the queue. + pendingUserTasks.Add((userId, tcs)); - return queueNextTask(nextLookup); - } + // Create a request task if there's not already one. + if (pendingRequestTask == null) + createNewTask(); - List nextLookup() - { - int[] lookupItems; - - lock (taskAssignmentLock) - { - pendingRequestConsumedIDs = true; - lookupItems = nextTaskIDs.ToArray(); - nextTaskIDs.Clear(); - - if (lookupItems.Length == 0) - { - queueNextTask(null); - return new List(); - } - } - - var request = new GetUsersRequest(lookupItems); - - // rather than queueing, we maintain our own single-threaded request stream. - api.Perform(request); - - return request.Result?.Users; + return tcs.Task; } } - /// - /// Queues new work at the end of the current work tasks. - /// Ensures the provided work is eventually run. - /// - /// The work to run. Can be null to signify the end of available work. - /// The task tracking this work. - private Task> queueNextTask(Func> work) + private void performLookup() { + var userTasks = new List<(int id, TaskCompletionSource task)>(); + + // Grab at most 50 users from the queue. lock (taskAssignmentLock) { - if (work == null) + while (pendingUserTasks.Count > 0 && userTasks.Count < 50) { - pendingRequest = null; - pendingRequestConsumedIDs = false; - } - else if (pendingRequest == null) - { - // special case for the first request ever. - pendingRequest = Task.Run(work); - pendingRequestConsumedIDs = false; - } - else - { - // append the new request on to the last to be executed. - pendingRequest = pendingRequest.ContinueWith(_ => work()); - pendingRequestConsumedIDs = false; - } + (int id, TaskCompletionSource task) next = pendingUserTasks[^1]; - return pendingRequest; + pendingUserTasks.RemoveAt(pendingUserTasks.Count - 1); + + // Perform a secondary check for existence, in case the user was queried in a previous batch. + if (CheckExists(next.id, out var existing)) + next.task.SetResult(existing); + else + userTasks.Add(next); + } } + + // Query the users. + var request = new GetUsersRequest(userTasks.Select(t => t.id).ToArray()); + + // rather than queueing, we maintain our own single-threaded request stream. + api.Perform(request); + + // Create a new request task if there's still more users to query. + lock (taskAssignmentLock) + { + pendingRequestTask = null; + if (pendingUserTasks.Count > 0) + createNewTask(); + } + + // Notify of completion. + foreach (var (id, task) in userTasks) + task.SetResult(request.Result?.Users?.FirstOrDefault(u => u.Id == id)); } + + private void createNewTask() => pendingRequestTask = Task.Run(performLookup); } } From 87bf168718bd918fb90cb04306216ceae8d0a688 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Nov 2020 20:52:51 +0900 Subject: [PATCH 010/100] Use queue instead of list --- osu.Game/Database/UserLookupCache.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index 05ba9c882b..71413d076e 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.cs @@ -22,7 +22,7 @@ namespace osu.Game.Database protected override async Task ComputeValueAsync(int lookup, CancellationToken token = default) => await queryUser(lookup); - private readonly List<(int id, TaskCompletionSource)> pendingUserTasks = new List<(int, TaskCompletionSource)>(); + private readonly Queue<(int id, TaskCompletionSource)> pendingUserTasks = new Queue<(int, TaskCompletionSource)>(); private Task pendingRequestTask; private readonly object taskAssignmentLock = new object(); @@ -33,7 +33,7 @@ namespace osu.Game.Database var tcs = new TaskCompletionSource(); // Add to the queue. - pendingUserTasks.Add((userId, tcs)); + pendingUserTasks.Enqueue((userId, tcs)); // Create a request task if there's not already one. if (pendingRequestTask == null) @@ -52,9 +52,7 @@ namespace osu.Game.Database { while (pendingUserTasks.Count > 0 && userTasks.Count < 50) { - (int id, TaskCompletionSource task) next = pendingUserTasks[^1]; - - pendingUserTasks.RemoveAt(pendingUserTasks.Count - 1); + (int id, TaskCompletionSource task) next = pendingUserTasks.Dequeue(); // Perform a secondary check for existence, in case the user was queried in a previous batch. if (CheckExists(next.id, out var existing)) From 85b0f714670215fc85eec8215f393674ccfd062a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Nov 2020 21:17:43 +0900 Subject: [PATCH 011/100] Handle duplicate user IDs within the same batch --- osu.Game/Database/UserLookupCache.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index 71413d076e..ba7f2ad98e 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.cs @@ -45,12 +45,15 @@ namespace osu.Game.Database private void performLookup() { + // userTasks may exceed 50 elements, indicating the existence of duplicate user IDs. All duplicated user IDs must be fulfilled. + // userIds contains at most 50 unique user IDs from userTasks, which is used to perform the lookup. var userTasks = new List<(int id, TaskCompletionSource task)>(); + var userIds = new HashSet(); - // Grab at most 50 users from the queue. + // Grab at most 50 unique user IDs from the queue. lock (taskAssignmentLock) { - while (pendingUserTasks.Count > 0 && userTasks.Count < 50) + while (pendingUserTasks.Count > 0 && userIds.Count < 50) { (int id, TaskCompletionSource task) next = pendingUserTasks.Dequeue(); @@ -58,12 +61,15 @@ namespace osu.Game.Database if (CheckExists(next.id, out var existing)) next.task.SetResult(existing); else + { userTasks.Add(next); + userIds.Add(next.id); + } } } // Query the users. - var request = new GetUsersRequest(userTasks.Select(t => t.id).ToArray()); + var request = new GetUsersRequest(userIds.ToArray()); // rather than queueing, we maintain our own single-threaded request stream. api.Perform(request); From 84b42f207dc8beefd61122dad234674a196b20a0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Nov 2020 23:14:20 +0900 Subject: [PATCH 012/100] Fix triangles disappearing after a while --- .../Objects/Drawables/Pieces/TrianglesPiece.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs index 6cdb0d3df3..add62192f0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs @@ -7,8 +7,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class TrianglesPiece : Triangles { - protected override bool ExpireOffScreenTriangles => false; - protected override bool CreateNewTriangles => false; protected override float SpawnRatio => 0.5f; public TrianglesPiece(int? seed = null) From e88920442c7f3a2ce09b17d346737018c54bbb8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 16 Nov 2020 20:01:10 +0100 Subject: [PATCH 013/100] Use HitStateUpdateTime instead --- osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index f1f72c8618..328c4e547b 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Mania.Skinning private void applyCustomUpdateState(DrawableHitObject hitObject, ArmedState state) { if (state == ArmedState.Miss) - missFadeTime.Value ??= hitObject.StateUpdateTime; + missFadeTime.Value ??= hitObject.HitStateUpdateTime; } private void onIsHittingChanged(ValueChangedEvent isHitting) From 21f29e28e2a11f33b702c3dfbe92d5da32a92b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 16 Nov 2020 20:36:56 +0100 Subject: [PATCH 014/100] Add clarification comment --- osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index 328c4e547b..8902d82f33 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -119,6 +119,7 @@ namespace osu.Game.Rulesets.Mania.Skinning private void applyCustomUpdateState(DrawableHitObject hitObject, ArmedState state) { + // ensure that the hold note is also faded out when the head/tail/any tick is missed. if (state == ArmedState.Miss) missFadeTime.Value ??= hitObject.HitStateUpdateTime; } From c6618f08aad3b47cc69ef73153966a5f384b580c Mon Sep 17 00:00:00 2001 From: kamp Date: Mon, 16 Nov 2020 21:26:08 +0100 Subject: [PATCH 015/100] Fix slider control point connections not being updated --- .../PathControlPointConnectionPiece.cs | 12 +++++++++- .../Components/PathControlPointVisualiser.cs | 24 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index ba1d35c35c..45c4a61ce1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -20,7 +20,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private readonly Path path; private readonly Slider slider; - private readonly int controlPointIndex; + private int controlPointIndex; + + public int ControlPointIndex + { + get => controlPointIndex; + set + { + controlPointIndex = value; + updateConnectingPath(); + } + } private IBindable sliderPosition; private IBindable pathVersion; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 17541866ec..14ce0e065b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -66,6 +66,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components switch (e.Action) { case NotifyCollectionChangedAction.Add: + // If inserting in the the path (not appending), + // update indices of existing connections after insert location + if (e.NewStartingIndex < Pieces.Count) + { + foreach (var connection in Connections) + { + if (connection.ControlPointIndex >= e.NewStartingIndex) + connection.ControlPointIndex += e.NewItems.Count; + } + } + for (int i = 0; i < e.NewItems.Count; i++) { var point = (PathControlPoint)e.NewItems[i]; @@ -82,12 +93,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components break; case NotifyCollectionChangedAction.Remove: + int oldSize = Pieces.Count; + foreach (var point in e.OldItems.Cast()) { Pieces.RemoveAll(p => p.ControlPoint == point); Connections.RemoveAll(c => c.ControlPoint == point); } + // If removing before the end of the path, + // update indices of connections after remove location + if (e.OldStartingIndex + e.OldItems.Count < oldSize) + { + foreach (var connection in Connections) + { + if (connection.ControlPointIndex >= e.OldStartingIndex) + connection.ControlPointIndex -= e.OldItems.Count; + } + } + break; } } From 009d666241fb7adfd1c1f11d7603478dbd9166b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 10:57:11 +0900 Subject: [PATCH 016/100] Use dictionary to avoid linq overhead --- osu.Game/Database/UserLookupCache.cs | 36 +++++++++++++++++++--------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index ba7f2ad98e..05d6930992 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.cs @@ -45,15 +45,13 @@ namespace osu.Game.Database private void performLookup() { - // userTasks may exceed 50 elements, indicating the existence of duplicate user IDs. All duplicated user IDs must be fulfilled. - // userIds contains at most 50 unique user IDs from userTasks, which is used to perform the lookup. - var userTasks = new List<(int id, TaskCompletionSource task)>(); - var userIds = new HashSet(); + // contains at most 50 unique user IDs from userTasks, which is used to perform the lookup. + var userTasks = new Dictionary>>(); // Grab at most 50 unique user IDs from the queue. lock (taskAssignmentLock) { - while (pendingUserTasks.Count > 0 && userIds.Count < 50) + while (pendingUserTasks.Count > 0 && userTasks.Count < 50) { (int id, TaskCompletionSource task) next = pendingUserTasks.Dequeue(); @@ -62,14 +60,16 @@ namespace osu.Game.Database next.task.SetResult(existing); else { - userTasks.Add(next); - userIds.Add(next.id); + if (userTasks.TryGetValue(next.id, out var tasks)) + tasks.Add(next.task); + else + userTasks[next.id] = new List> { next.task }; } } } // Query the users. - var request = new GetUsersRequest(userIds.ToArray()); + var request = new GetUsersRequest(userTasks.Keys.ToArray()); // rather than queueing, we maintain our own single-threaded request stream. api.Perform(request); @@ -82,9 +82,23 @@ namespace osu.Game.Database createNewTask(); } - // Notify of completion. - foreach (var (id, task) in userTasks) - task.SetResult(request.Result?.Users?.FirstOrDefault(u => u.Id == id)); + foreach (var user in request.Result.Users) + { + if (userTasks.TryGetValue(user.Id, out var tasks)) + { + foreach (var task in tasks) + task.SetResult(user); + + userTasks.Remove(user.Id); + } + } + + // if any tasks remain which were not satisfied, return null. + foreach (var tasks in userTasks.Values) + { + foreach (var task in tasks) + task.SetResult(null); + } } private void createNewTask() => pendingRequestTask = Task.Run(performLookup); From 33c643e36907d0c5830b1a13118c58d0b816ff11 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Nov 2020 12:23:34 +0900 Subject: [PATCH 017/100] Add obsoletion for unused property --- osu.Game/Graphics/Backgrounds/Triangles.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 5b0fa44444..a81dbbb64a 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -60,6 +60,7 @@ namespace osu.Game.Graphics.Backgrounds /// /// Whether we want to expire triangles as they exit our draw area completely. /// + [Obsolete("Unused.")] // Can be removed 20210518 protected virtual bool ExpireOffScreenTriangles => true; /// From 3bcf9c255a483a9936671b5c4acadc0e7b475d5a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Nov 2020 13:06:30 +0900 Subject: [PATCH 018/100] Add Triangles.Reset() --- osu.Game/Graphics/Backgrounds/Triangles.cs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index a81dbbb64a..0e9382279a 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -87,12 +87,9 @@ namespace osu.Game.Graphics.Backgrounds /// public float Velocity = 1; - private readonly Random stableRandom; - - private float nextRandom() => (float)(stableRandom?.NextDouble() ?? RNG.NextSingle()); - private readonly SortedList parts = new SortedList(Comparer.Default); + private Random stableRandom; private IShader shader; private readonly Texture texture; @@ -173,7 +170,20 @@ namespace osu.Game.Graphics.Backgrounds } } - protected int AimCount; + /// + /// Clears and re-initialises triangles according to a given seed. + /// + /// An optional seed to stabilise random positions / attributes. Note that this does not guarantee stable playback when seeking in time. + public void Reset(int? seed = null) + { + if (seed != null) + stableRandom = new Random(seed.Value); + + parts.Clear(); + addTriangles(true); + } + + protected int AimCount { get; private set; } private void addTriangles(bool randomY) { @@ -227,6 +237,8 @@ namespace osu.Game.Graphics.Backgrounds } } + private float nextRandom() => (float)(stableRandom?.NextDouble() ?? RNG.NextSingle()); + protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(this); private class TrianglesDrawNode : DrawNode From c101f32db8a796f08f21d317ffb5aef8310f8d14 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Nov 2020 13:06:52 +0900 Subject: [PATCH 019/100] Reset osu! triangle pieces on hitobject application --- .../Objects/Drawables/Pieces/CirclePiece.cs | 28 ++++++++++++++-- .../Objects/Drawables/Pieces/ExplodePiece.cs | 32 ++++++++++++++++++- .../Drawables/Pieces/TrianglesPiece.cs | 1 + 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index c455c66e8d..d0e1055dce 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -13,6 +13,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class CirclePiece : CompositeDrawable { + [Resolved] + private DrawableHitObject drawableObject { get; set; } + + private TrianglesPiece triangles; + public CirclePiece() { Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); @@ -26,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } [BackgroundDependencyLoader] - private void load(TextureStore textures, DrawableHitObject drawableHitObject) + private void load(TextureStore textures) { InternalChildren = new Drawable[] { @@ -36,13 +41,32 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Origin = Anchor.Centre, Texture = textures.Get(@"Gameplay/osu/disc"), }, - new TrianglesPiece(drawableHitObject.GetHashCode()) + triangles = new TrianglesPiece { RelativeSizeAxes = Axes.Both, Blending = BlendingParameters.Additive, Alpha = 0.5f, } }; + + drawableObject.HitObjectApplied += onHitObjectApplied; + onHitObjectApplied(drawableObject); + } + + private void onHitObjectApplied(DrawableHitObject obj) + { + if (obj.HitObject == null) + return; + + triangles.Reset((int)obj.HitObject.StartTime); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (drawableObject != null) + drawableObject.HitObjectApplied -= onHitObjectApplied; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs index 6381ddca69..09299a3622 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs @@ -1,14 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class ExplodePiece : Container { + [Resolved] + private DrawableHitObject drawableObject { get; set; } + + private TrianglesPiece triangles; + public ExplodePiece() { Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); @@ -18,13 +25,36 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Blending = BlendingParameters.Additive; Alpha = 0; + } - Child = new TrianglesPiece + [BackgroundDependencyLoader] + private void load() + { + Child = triangles = new TrianglesPiece { Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, Alpha = 0.2f, }; + + drawableObject.HitObjectApplied += onHitObjectApplied; + onHitObjectApplied(drawableObject); + } + + private void onHitObjectApplied(DrawableHitObject obj) + { + if (obj.HitObject == null) + return; + + triangles.Reset((int)obj.HitObject.StartTime); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (drawableObject != null) + drawableObject.HitObjectApplied -= onHitObjectApplied; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs index add62192f0..53dc7ecea3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs @@ -7,6 +7,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class TrianglesPiece : Triangles { + protected override bool CreateNewTriangles => false; protected override float SpawnRatio => 0.5f; public TrianglesPiece(int? seed = null) From 77942af3a653aa3f8672d44f5370e56236c03df6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Nov 2020 13:37:58 +0900 Subject: [PATCH 020/100] Fix hold note judgements displaying incorrectly --- .../Objects/Drawables/DrawableHoldNoteTick.cs | 2 -- osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- osu.Game.Rulesets.Mania/UI/Stage.cs | 4 ++++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index f265419aa0..98931dceed 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -16,8 +16,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableHoldNoteTick : DrawableManiaHitObject { - public override bool DisplayResult => false; - /// /// References the time at which the user started holding the hold note. /// diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index c28a1c13d8..9aabcc6699 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI if (result.IsHit) hitPolicy.HandleHit(judgedObject); - if (!result.IsHit || !DisplayJudgements.Value) + if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) return; HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result))); diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index e7a2de266d..3d7960ffe3 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -167,6 +167,10 @@ namespace osu.Game.Rulesets.Mania.UI if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; + // Tick judgements should not display text. + if (judgedObject is DrawableHoldNoteTick) + return; + judgements.Clear(false); judgements.Add(judgementPool.Get(j => { From 9dfa4249e059f68a54677625fb05227a401de745 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 14:05:13 +0900 Subject: [PATCH 021/100] Make Apply non-virtual --- .../Objects/Drawables/DrawableOsuJudgement.cs | 17 ++++++----------- .../Rulesets/Judgements/DrawableJudgement.cs | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 98898ce1b4..5bf5f89b26 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -39,23 +39,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }); } - public override void Apply(JudgementResult result, DrawableHitObject judgedObject) - { - base.Apply(result, judgedObject); - - if (judgedObject?.HitObject is OsuHitObject osuObject) - { - Position = osuObject.StackedPosition; - Scale = new Vector2(osuObject.Scale); - } - } - protected override void PrepareForUse() { base.PrepareForUse(); Lighting.ResetAnimation(); Lighting.SetColourFrom(JudgedObject, Result); + + if (JudgedObject?.HitObject is OsuHitObject osuObject) + { + Position = osuObject.StackedPosition; + Scale = new Vector2(osuObject.Scale); + } } private double fadeOutDelay; diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index d24c81536e..5c617aaa98 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Judgements this.Delay(FadeOutDelay).FadeOut(400); } - public virtual void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject) + public void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject) { Result = result; JudgedObject = judgedObject; From 82c3925a3795345d74d95f48fa4ebd2dd6e23f4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 14:13:50 +0900 Subject: [PATCH 022/100] Remove unused DrawableOsuJudgement constructors --- .../Objects/Drawables/DrawableOsuJudgement.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 5bf5f89b26..d89a613e0f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -17,15 +17,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [Resolved] private OsuConfigManager config { get; set; } - public DrawableOsuJudgement(JudgementResult result, DrawableHitObject judgedObject) - : base(result, judgedObject) - { - } - - public DrawableOsuJudgement() - { - } - [BackgroundDependencyLoader] private void load() { From f465dd5a5e6f840169c15129ce9698441c0bd82b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 14:59:34 +0900 Subject: [PATCH 023/100] Move all extended animation logic out of DrawableJudgement --- .../UI/DrawableManiaJudgement.cs | 21 +++--- .../Objects/Drawables/DrawableOsuJudgement.cs | 18 ++++- .../Judgements/DefaultJudgementPiece.cs | 65 +++++++++++++++++ .../Rulesets/Judgements/DrawableJudgement.cs | 69 ++++++++++--------- .../Judgements/IAnimatableJudgement.cs | 15 ++++ 5 files changed, 146 insertions(+), 42 deletions(-) create mode 100644 osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs create mode 100644 osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index d99f6cb8d3..c53ab4a717 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.UI { @@ -19,13 +19,6 @@ namespace osu.Game.Rulesets.Mania.UI { } - [BackgroundDependencyLoader] - private void load() - { - if (JudgementText != null) - JudgementText.Font = JudgementText.Font.With(size: 25); - } - protected override double FadeInDuration => 50; protected override void ApplyHitAnimations() @@ -36,5 +29,17 @@ namespace osu.Game.Rulesets.Mania.UI JudgementBody.Delay(FadeInDuration).ScaleTo(0.75f, 250); this.Delay(FadeInDuration).FadeOut(200); } + + protected override Drawable CreateDefaultJudgement(HitResult type) + => new ManiaJudgementPiece(); + + private class ManiaJudgementPiece : DefaultJudgementPiece + { + protected override void LoadComplete() + { + base.LoadComplete(); + JudgementText.Font = JudgementText.Font.With(size: 25); + } + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index d89a613e0f..a96ec53e28 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -4,9 +4,9 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; -using osuTK; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -65,8 +65,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables fadeOutDelay = hitLightingEnabled ? 1400 : base.FadeOutDelay; - JudgementText?.TransformSpacingTo(Vector2.Zero).Then().TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); base.ApplyHitAnimations(); } + + protected override Drawable CreateDefaultJudgement(HitResult type) => new OsuJudgementPiece(); + + private class OsuJudgementPiece : DefaultJudgementPiece + { + public override void PlayAnimation(HitResult resultType) + { + base.PlayAnimation(resultType); + + if (resultType != HitResult.Miss) + JudgementText.TransformSpacingTo(Vector2.Zero).Then().TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); + } + } } } diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs new file mode 100644 index 0000000000..051cd755d6 --- /dev/null +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; +using osuTK; + +namespace osu.Game.Rulesets.Judgements +{ + public class DefaultJudgementPiece : CompositeDrawable, IAnimatableJudgement + { + protected SpriteText JudgementText { get; } + + [Resolved] + private OsuColour colours { get; set; } + + public DefaultJudgementPiece() + { + Origin = Anchor.Centre; + + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + JudgementText = new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 20), + Scale = new Vector2(0.85f, 1), + } + }; + } + + public virtual void PlayAnimation(HitResult result) + { + JudgementText.Text = result.GetDescription().ToUpperInvariant(); + JudgementText.Colour = colours.ForHitResult(result); + + this.RotateTo(0); + this.MoveTo(Vector2.Zero); + + switch (result) + { + case HitResult.Miss: + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); + + this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + + this.RotateTo(40, 800, Easing.InQuint); + break; + + default: + this.ScaleTo(0.9f); + this.ScaleTo(1, 500, Easing.OutElastic); + break; + } + } + } +} diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 5c617aaa98..a73b422ccf 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -3,18 +3,14 @@ using System.Diagnostics; using JetBrains.Annotations; -using osuTK; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Judgements { @@ -25,16 +21,13 @@ namespace osu.Game.Rulesets.Judgements { private const float judgement_size = 128; - [Resolved] - private OsuColour colours { get; set; } - public JudgementResult Result { get; private set; } + public DrawableHitObject JudgedObject { get; private set; } protected Container JudgementBody { get; private set; } - protected SpriteText JudgementText { get; private set; } - private SkinnableDrawable bodyDrawable; + private SkinnableDrawable skinnableJudgement; /// /// Duration of initial fade in. @@ -69,14 +62,34 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); } + /// + /// Apply top-level animations to the current judgement when successfully hit. + /// Generally used for fading, defaulting to a simple fade out based on . + /// This will be used to calculate the lifetime of the judgement. + /// + /// + /// For animating the actual "default skin" judgement itself, it is recommended to use . + /// This allows applying animations which don't affect custom skins. + /// protected virtual void ApplyHitAnimations() { - JudgementBody.ScaleTo(0.9f); - JudgementBody.ScaleTo(1, 500, Easing.OutElastic); - this.Delay(FadeOutDelay).FadeOut(400); } + /// + /// Apply top-level animations to the current judgement when missed. + /// Generally used for fading, defaulting to a simple fade out based on . + /// This will be used to calculate the lifetime of the judgement. + /// + /// + /// For animating the actual "default skin" judgement itself, it is recommended to use . + /// This allows applying animations which don't affect custom skins. + /// + protected virtual void ApplyMissAnimations() + { + this.Delay(600).FadeOut(200); + } + public void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject) { Result = result; @@ -91,12 +104,9 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); - bodyDrawable.ResetAnimation(); + skinnableJudgement.ResetAnimation(); this.FadeInFromZero(FadeInDuration, Easing.OutQuint); - JudgementBody.ScaleTo(1); - JudgementBody.RotateTo(0); - JudgementBody.MoveTo(Vector2.Zero); switch (Result.Type) { @@ -104,13 +114,7 @@ namespace osu.Game.Rulesets.Judgements break; case HitResult.Miss: - JudgementBody.ScaleTo(1.6f); - JudgementBody.ScaleTo(1, 100, Easing.In); - - JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); - JudgementBody.RotateTo(40, 800, Easing.InQuint); - - this.Delay(600).FadeOut(200); + ApplyMissAnimations(); break; default: @@ -118,6 +122,12 @@ namespace osu.Game.Rulesets.Judgements break; } + if (skinnableJudgement.Drawable is IAnimatableJudgement animatable) + { + using (BeginAbsoluteSequence(Result.TimeAbsolute)) + animatable.PlayAnimation(Result.Type); + } + Expire(true); } @@ -139,16 +149,13 @@ namespace osu.Game.Rulesets.Judgements Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Child = bodyDrawable = new SkinnableDrawable(new GameplaySkinComponent(type), _ => JudgementText = new OsuSpriteText - { - Text = type.GetDescription().ToUpperInvariant(), - Font = OsuFont.Numeric.With(size: 20), - Colour = colours.ForHitResult(type), - Scale = new Vector2(0.85f, 1), - }, confineMode: ConfineMode.NoScaling) + Child = skinnableJudgement = new SkinnableDrawable(new GameplaySkinComponent(type), _ => + CreateDefaultJudgement(type), confineMode: ConfineMode.NoScaling) }); currentDrawableType = type; } + + protected virtual Drawable CreateDefaultJudgement(HitResult type) => new DefaultJudgementPiece(); } } diff --git a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs new file mode 100644 index 0000000000..3f84e6f83c --- /dev/null +++ b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs @@ -0,0 +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 osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Judgements +{ + /// + /// A skinnable judgement element which supports playing an animation from the current point in time. + /// + public interface IAnimatableJudgement + { + void PlayAnimation(HitResult result); + } +} From e4f1e52422931ec11939a497b1db2e425be1ebce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 15:03:26 +0900 Subject: [PATCH 024/100] Add xmldoc coverage of Apply() --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index a73b422ccf..724b4a4d4e 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -90,6 +90,11 @@ namespace osu.Game.Rulesets.Judgements this.Delay(600).FadeOut(200); } + /// + /// Associate a new result / object with this judgement. Should be called when retrieving a judgement from a pool. + /// + /// The applicable judgement. + /// The drawable object. public void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject) { Result = result; @@ -104,6 +109,7 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); + // not sure if this should remain going forward. skinnableJudgement.ResetAnimation(); this.FadeInFromZero(FadeInDuration, Easing.OutQuint); From 8247e6ce917053c1e881ef6ce27ce2fcdf3fc456 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 15:43:54 +0900 Subject: [PATCH 025/100] Move result type to ctor --- .../UI/DrawableManiaJudgement.cs | 9 +++++++-- .../Objects/Drawables/DrawableOsuJudgement.cs | 13 ++++++++---- .../Judgements/DefaultJudgementPiece.cs | 20 ++++++++++++------- .../Rulesets/Judgements/DrawableJudgement.cs | 5 +++-- .../Judgements/IAnimatableJudgement.cs | 4 +--- 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index c53ab4a717..ebce40a785 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -30,14 +30,19 @@ namespace osu.Game.Rulesets.Mania.UI this.Delay(FadeInDuration).FadeOut(200); } - protected override Drawable CreateDefaultJudgement(HitResult type) - => new ManiaJudgementPiece(); + protected override Drawable CreateDefaultJudgement(HitResult result) => new ManiaJudgementPiece(result); private class ManiaJudgementPiece : DefaultJudgementPiece { + public ManiaJudgementPiece(HitResult result) + : base(result) + { + } + protected override void LoadComplete() { base.LoadComplete(); + JudgementText.Font = JudgementText.Font.With(size: 25); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index a96ec53e28..47fb53379f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -68,15 +68,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.ApplyHitAnimations(); } - protected override Drawable CreateDefaultJudgement(HitResult type) => new OsuJudgementPiece(); + protected override Drawable CreateDefaultJudgement(HitResult result) => new OsuJudgementPiece(result); private class OsuJudgementPiece : DefaultJudgementPiece { - public override void PlayAnimation(HitResult resultType) + public OsuJudgementPiece(HitResult result) + : base(result) { - base.PlayAnimation(resultType); + } - if (resultType != HitResult.Miss) + public override void PlayAnimation() + { + base.PlayAnimation(); + + if (Result != HitResult.Miss) JudgementText.TransformSpacingTo(Vector2.Zero).Then().TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); } } diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 051cd755d6..3b9e5e948a 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -15,36 +15,42 @@ namespace osu.Game.Rulesets.Judgements { public class DefaultJudgementPiece : CompositeDrawable, IAnimatableJudgement { - protected SpriteText JudgementText { get; } + protected readonly HitResult Result; + + protected SpriteText JudgementText { get; private set; } [Resolved] private OsuColour colours { get; set; } - public DefaultJudgementPiece() + public DefaultJudgementPiece(HitResult result) { + this.Result = result; Origin = Anchor.Centre; + } + [BackgroundDependencyLoader] + private void load() + { AutoSizeAxes = Axes.Both; InternalChildren = new Drawable[] { JudgementText = new OsuSpriteText { + Text = Result.GetDescription().ToUpperInvariant(), + Colour = colours.ForHitResult(Result), Font = OsuFont.Numeric.With(size: 20), Scale = new Vector2(0.85f, 1), } }; } - public virtual void PlayAnimation(HitResult result) + public virtual void PlayAnimation() { - JudgementText.Text = result.GetDescription().ToUpperInvariant(); - JudgementText.Colour = colours.ForHitResult(result); - this.RotateTo(0); this.MoveTo(Vector2.Zero); - switch (result) + switch (Result) { case HitResult.Miss: this.ScaleTo(1.6f); diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 724b4a4d4e..468b3190b0 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Judgements if (skinnableJudgement.Drawable is IAnimatableJudgement animatable) { using (BeginAbsoluteSequence(Result.TimeAbsolute)) - animatable.PlayAnimation(Result.Type); + animatable.PlayAnimation(); } Expire(true); @@ -143,6 +143,7 @@ namespace osu.Game.Rulesets.Judgements { var type = Result?.Type ?? HitResult.Perfect; //TODO: better default type from ruleset + // todo: this should be removed once judgements are always pooled. if (type == currentDrawableType) return; @@ -162,6 +163,6 @@ namespace osu.Game.Rulesets.Judgements currentDrawableType = type; } - protected virtual Drawable CreateDefaultJudgement(HitResult type) => new DefaultJudgementPiece(); + protected virtual Drawable CreateDefaultJudgement(HitResult result) => new DefaultJudgementPiece(result); } } diff --git a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs index 3f84e6f83c..3d5bbe6dad 100644 --- a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs @@ -1,8 +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 osu.Game.Rulesets.Scoring; - namespace osu.Game.Rulesets.Judgements { /// @@ -10,6 +8,6 @@ namespace osu.Game.Rulesets.Judgements /// public interface IAnimatableJudgement { - void PlayAnimation(HitResult result); + void PlayAnimation(); } } From dd4b69feab226c09c7334f407431add26828832f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 15:44:15 +0900 Subject: [PATCH 026/100] Add legacy judgement implementation which doesn't transform on animations --- osu.Game/Skinning/LegacyJudgementPiece.cs | 58 +++++++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 37 +++++++++------ 2 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 osu.Game/Skinning/LegacyJudgementPiece.cs diff --git a/osu.Game/Skinning/LegacyJudgementPiece.cs b/osu.Game/Skinning/LegacyJudgementPiece.cs new file mode 100644 index 0000000000..6c606c638d --- /dev/null +++ b/osu.Game/Skinning/LegacyJudgementPiece.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osuTK; + +namespace osu.Game.Skinning +{ + public class LegacyJudgementPiece : CompositeDrawable, IAnimatableJudgement + { + private readonly HitResult result; + + public LegacyJudgementPiece(HitResult result, Drawable texture) + { + this.result = result; + + AutoSizeAxes = Axes.Both; + Origin = Anchor.Centre; + + InternalChild = texture; + } + + public virtual void PlayAnimation() + { + var animation = InternalChild as IFramedAnimation; + + animation?.GotoFrame(0); + + this.RotateTo(0); + this.MoveTo(Vector2.Zero); + + // legacy judgements don't play any transforms if they are an animation. + if (animation?.FrameCount > 1) + return; + + switch (result) + { + case HitResult.Miss: + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); + + this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + + this.RotateTo(40, 800, Easing.InQuint); + break; + + default: + this.ScaleTo(0.9f); + this.ScaleTo(1, 500, Easing.OutElastic); + break; + } + } + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index fb020f4e39..ca8bb58023 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -371,20 +371,9 @@ namespace osu.Game.Skinning } case GameplaySkinComponent resultComponent: - switch (resultComponent.Component) - { - case HitResult.Miss: - return this.GetAnimation("hit0", true, false); - - case HitResult.Meh: - return this.GetAnimation("hit50", true, false); - - case HitResult.Ok: - return this.GetAnimation("hit100", true, false); - - case HitResult.Great: - return this.GetAnimation("hit300", true, false); - } + var drawable = getJudgementAnimation(resultComponent.Component); + if (drawable != null) + return new LegacyJudgementPiece(resultComponent.Component, drawable); break; } @@ -392,6 +381,26 @@ namespace osu.Game.Skinning return this.GetAnimation(component.LookupName, false, false); } + private Drawable getJudgementAnimation(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return this.GetAnimation("hit0", true, false); + + case HitResult.Meh: + return this.GetAnimation("hit50", true, false); + + case HitResult.Ok: + return this.GetAnimation("hit100", true, false); + + case HitResult.Great: + return this.GetAnimation("hit300", true, false); + } + + return null; + } + public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) { foreach (var name in getFallbackNames(componentName)) From eebce1f9145813b525400517e170d3931a1f8de9 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 17 Nov 2020 18:13:32 +0900 Subject: [PATCH 027/100] Fix TestSceneFruitObjects --- .../TestSceneFruitObjects.cs | 61 +++++++++++++++---- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index 385d8ed7fa..e9dabd30ed 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Objects; using osuTK; namespace osu.Game.Rulesets.Catch.Tests @@ -36,17 +37,29 @@ namespace osu.Game.Rulesets.Catch.Tests Scale = 1.5f, }; - return new DrawableTinyDroplet(droplet) + return new TestDrawableTinyDroplet(droplet) { Anchor = Anchor.Centre, RelativePositionAxes = Axes.None, Position = Vector2.Zero, - Alpha = 1, - LifetimeStart = double.NegativeInfinity, - LifetimeEnd = double.PositiveInfinity, }; } + private class TestDrawableTinyDroplet : DrawableTinyDroplet + { + public TestDrawableTinyDroplet(TinyDroplet tinyDroplet) + : base(tinyDroplet) + { + } + + protected override void OnApply(HitObject hitObject) + { + base.OnApply(hitObject); + LifetimeStart = double.NegativeInfinity; + LifetimeEnd = double.PositiveInfinity; + } + } + private Drawable createDrawableDroplet(bool hyperdash = false) { var droplet = new TestCatchDroplet @@ -55,17 +68,29 @@ namespace osu.Game.Rulesets.Catch.Tests HyperDashTarget = hyperdash ? new Banana() : null }; - return new DrawableDroplet(droplet) + return new TestDrawableDroplet(droplet) { Anchor = Anchor.Centre, RelativePositionAxes = Axes.None, Position = Vector2.Zero, - Alpha = 1, - LifetimeStart = double.NegativeInfinity, - LifetimeEnd = double.PositiveInfinity, }; } + private class TestDrawableDroplet : DrawableDroplet + { + public TestDrawableDroplet(Droplet droplet) + : base(droplet) + { + } + + protected override void OnApply(HitObject hitObject) + { + base.OnApply(hitObject); + LifetimeStart = double.NegativeInfinity; + LifetimeEnd = double.PositiveInfinity; + } + } + private Drawable createDrawable(FruitVisualRepresentation rep, bool hyperdash = false) { Fruit fruit = new TestCatchFruit(rep) @@ -74,17 +99,29 @@ namespace osu.Game.Rulesets.Catch.Tests HyperDashTarget = hyperdash ? new Banana() : null }; - return new DrawableFruit(fruit) + return new TestDrawableFruit(fruit) { Anchor = Anchor.Centre, RelativePositionAxes = Axes.None, Position = Vector2.Zero, - Alpha = 1, - LifetimeStart = double.NegativeInfinity, - LifetimeEnd = double.PositiveInfinity, }; } + private class TestDrawableFruit : DrawableFruit + { + public TestDrawableFruit(Fruit fruit) + : base(fruit) + { + } + + protected override void OnApply(HitObject hitObject) + { + base.OnApply(hitObject); + LifetimeStart = double.NegativeInfinity; + LifetimeEnd = double.PositiveInfinity; + } + } + public class TestCatchFruit : Fruit { public TestCatchFruit(FruitVisualRepresentation rep) From 58c8184ad7f7b008d001a048cfcbf998f14e1423 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Nov 2020 22:56:21 +0900 Subject: [PATCH 028/100] Define blueprint order similarly to hitobjects --- .../Compose/Components/BlueprintContainer.cs | 6 +- .../Components/SelectionBlueprintContainer.cs | 76 +++++++++++++++++++ .../Timeline/TimelineBlueprintContainer.cs | 6 +- 3 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/SelectionBlueprintContainer.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 53b6e14940..3aaa0c7d89 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -118,8 +118,8 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - protected virtual Container CreateSelectionBlueprintContainer() => - new Container { RelativeSizeAxes = Axes.Both }; + protected virtual SelectionBlueprintContainer CreateSelectionBlueprintContainer() => + new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; /// /// Creates a which outlines s and handles movement of selections. @@ -338,7 +338,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether a selection was performed. private bool beginClickSelection(MouseButtonEvent e) { - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren) + foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse()) { if (!blueprint.IsHovered) continue; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBlueprintContainer.cs new file mode 100644 index 0000000000..54932f6252 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBlueprintContainer.cs @@ -0,0 +1,76 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Edit; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class SelectionBlueprintContainer : Container + { + public override void Add(SelectionBlueprint drawable) + { + base.Add(drawable); + + if (Content == this) + bindStartTime(drawable); + } + + public override bool Remove(SelectionBlueprint drawable) + { + if (!base.Remove(drawable)) + return false; + + if (Content == this) + unbindStartTime(drawable); + return true; + } + + public override void Clear(bool disposeChildren) + { + base.Clear(disposeChildren); + unbindAllStartTimes(); + } + + private readonly Dictionary startTimeMap = new Dictionary(); + + private void bindStartTime(SelectionBlueprint blueprint) + { + var bindable = blueprint.HitObject.StartTimeBindable.GetBoundCopy(); + + bindable.BindValueChanged(_ => + { + if (LoadState >= LoadState.Ready) + SortInternal(); + }); + + startTimeMap[blueprint] = bindable; + } + + private void unbindStartTime(SelectionBlueprint blueprint) + { + startTimeMap[blueprint].UnbindAll(); + startTimeMap.Remove(blueprint); + } + + private void unbindAllStartTimes() + { + foreach (var kvp in startTimeMap) + kvp.Value.UnbindAll(); + startTimeMap.Clear(); + } + + protected override int Compare(Drawable x, Drawable y) + { + if (!(x is SelectionBlueprint xObj) || !(y is SelectionBlueprint yObj)) + return base.Compare(x, y); + + // Put earlier blueprints towards the end of the list, so they handle input first + int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime); + return i == 0 ? CompareReverseChildID(x, y) : i; + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index eef02e61a6..2bd4ac2f91 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected override Container CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected override SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; protected override void OnDrag(DragEvent e) { @@ -195,13 +195,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected class TimelineSelectionBlueprintContainer : Container + protected class TimelineSelectionBlueprintContainer : SelectionBlueprintContainer { protected override Container Content { get; } public TimelineSelectionBlueprintContainer() { - AddInternal(new TimelinePart(Content = new Container { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); + AddInternal(new TimelinePart(Content = new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); } } } From 04805b78c31718a451737bd78b090e96e39a3859 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Nov 2020 23:19:59 +0900 Subject: [PATCH 029/100] Tighten osu! ruleset lifetime expiry for past hitobjects --- .../Objects/Drawables/DrawableHitCircle.cs | 2 ++ .../Objects/Drawables/DrawableOsuHitObject.cs | 5 +++++ .../Objects/Drawables/DrawableSlider.cs | 16 ++++++++-------- .../Objects/Drawables/DrawableSpinner.cs | 2 +- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 2e63160d36..d1ceca6d8f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -180,6 +180,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables this.Delay(800).FadeOut(); break; } + + Expire(); } public Drawable ProxiedLayer => ApproachCircle; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index bcaf73d34f..c962d191a6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -91,6 +92,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // Manually set to reduce the number of future alive objects to a bare minimum. LifetimeStart = HitObject.StartTime - HitObject.TimePreempt; + + // Arbitrary lifetime end to prevent past objects in idle states remaining alive in non-frame-stable contexts. + // An extra 1000ms is added to always overestimate the true lifetime, and a more exact value is set by hit transforms and the following expiry. + LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss) + 1000; } /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index f7b1894058..14c494d909 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -193,13 +193,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return base.CreateNestedHitObject(hitObject); } - protected override void UpdateInitialTransforms() - { - base.UpdateInitialTransforms(); - - Body.FadeInFromZero(HitObject.TimeFadeIn); - } - public readonly Bindable Tracking = new Bindable(); protected override void Update() @@ -273,6 +266,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.PlaySamples(); } + protected override void UpdateInitialTransforms() + { + base.UpdateInitialTransforms(); + + Body.FadeInFromZero(HitObject.TimeFadeIn); + } + protected override void UpdateStartTimeStateTransforms() { base.UpdateStartTimeStateTransforms(); @@ -297,7 +297,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables break; } - this.FadeOut(fade_out_time, Easing.OutQuint); + this.FadeOut(fade_out_time, Easing.OutQuint).Expire(); } public Drawable ProxiedLayer => HeadCircle.ProxiedLayer; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 87c7146a64..2a14a7c975 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateHitStateTransforms(state); - this.FadeOut(160); + this.FadeOut(160).Expire(); // skin change does a rewind of transforms, which will stop the spinning sound from playing if it's currently in playback. isSpinning?.TriggerChange(); From ce4baf328dba51f3f5006bbc69f0ad58b9d91dd6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Nov 2020 23:35:36 +0900 Subject: [PATCH 030/100] Move into OnApply() to resolve one-frame issues --- .../Objects/Drawables/DrawableOsuHitObject.cs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index c962d191a6..a26db06ede 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -61,6 +61,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables PositionBindable.BindTo(HitObject.PositionBindable); StackHeightBindable.BindTo(HitObject.StackHeightBindable); ScaleBindable.BindTo(HitObject.ScaleBindable); + + // Manually set to reduce the number of future alive objects to a bare minimum. + LifetimeStart = HitObject.StartTime - HitObject.TimePreempt; + + // Arbitrary lifetime end to prevent past objects in idle states remaining alive in non-frame-stable contexts. + // An extra 1000ms is added to always overestimate the true lifetime, and a more exact value is set by hit transforms and the following expiry. + LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss) + 1000; } protected override void OnFree(HitObject hitObject) @@ -86,18 +93,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength); - protected override void UpdateInitialTransforms() - { - base.UpdateInitialTransforms(); - - // Manually set to reduce the number of future alive objects to a bare minimum. - LifetimeStart = HitObject.StartTime - HitObject.TimePreempt; - - // Arbitrary lifetime end to prevent past objects in idle states remaining alive in non-frame-stable contexts. - // An extra 1000ms is added to always overestimate the true lifetime, and a more exact value is set by hit transforms and the following expiry. - LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss) + 1000; - } - /// /// Causes this to get missed, disregarding all conditions in implementations of . /// From c360533e4cce0875dbb5175ef6a27bac869759f1 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 17 Nov 2020 23:40:30 +0900 Subject: [PATCH 031/100] Simplify code of TestSceneFruitObjects --- .../TestSceneFruitObjects.cs | 131 ++++-------------- 1 file changed, 24 insertions(+), 107 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index e9dabd30ed..89063319d6 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; -using osu.Game.Rulesets.Objects; using osuTK; namespace osu.Game.Rulesets.Catch.Tests @@ -19,107 +18,42 @@ namespace osu.Game.Rulesets.Catch.Tests base.LoadComplete(); foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation))) - AddStep($"show {rep}", () => SetContents(() => createDrawable(rep))); + AddStep($"show {rep}", () => SetContents(() => createDrawableFruit(rep))); AddStep("show droplet", () => SetContents(() => createDrawableDroplet())); AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet)); foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation))) - AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawable(rep, true))); + AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawableFruit(rep, true))); AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true))); } - private Drawable createDrawableTinyDroplet() + private Drawable createDrawableFruit(FruitVisualRepresentation rep, bool hyperdash = false) => + setProperties(new DrawableFruit(new TestCatchFruit(rep)), hyperdash); + + private Drawable createDrawableDroplet(bool hyperdash = false) => setProperties(new DrawableDroplet(new Droplet()), hyperdash); + + private Drawable createDrawableTinyDroplet() => setProperties(new DrawableTinyDroplet(new TinyDroplet())); + + private DrawableCatchHitObject setProperties(DrawableCatchHitObject d, bool hyperdash = false) { - var droplet = new TestCatchTinyDroplet + var hitObject = d.HitObject; + hitObject.StartTime = 1000000000000; + hitObject.Scale = 1.5f; + + if (hyperdash) + hitObject.HyperDashTarget = new Banana(); + + d.Anchor = Anchor.Centre; + d.RelativePositionAxes = Axes.None; + d.Position = Vector2.Zero; + d.HitObjectApplied += _ => { - Scale = 1.5f, + d.LifetimeStart = double.NegativeInfinity; + d.LifetimeEnd = double.PositiveInfinity; }; - - return new TestDrawableTinyDroplet(droplet) - { - Anchor = Anchor.Centre, - RelativePositionAxes = Axes.None, - Position = Vector2.Zero, - }; - } - - private class TestDrawableTinyDroplet : DrawableTinyDroplet - { - public TestDrawableTinyDroplet(TinyDroplet tinyDroplet) - : base(tinyDroplet) - { - } - - protected override void OnApply(HitObject hitObject) - { - base.OnApply(hitObject); - LifetimeStart = double.NegativeInfinity; - LifetimeEnd = double.PositiveInfinity; - } - } - - private Drawable createDrawableDroplet(bool hyperdash = false) - { - var droplet = new TestCatchDroplet - { - Scale = 1.5f, - HyperDashTarget = hyperdash ? new Banana() : null - }; - - return new TestDrawableDroplet(droplet) - { - Anchor = Anchor.Centre, - RelativePositionAxes = Axes.None, - Position = Vector2.Zero, - }; - } - - private class TestDrawableDroplet : DrawableDroplet - { - public TestDrawableDroplet(Droplet droplet) - : base(droplet) - { - } - - protected override void OnApply(HitObject hitObject) - { - base.OnApply(hitObject); - LifetimeStart = double.NegativeInfinity; - LifetimeEnd = double.PositiveInfinity; - } - } - - private Drawable createDrawable(FruitVisualRepresentation rep, bool hyperdash = false) - { - Fruit fruit = new TestCatchFruit(rep) - { - Scale = 1.5f, - HyperDashTarget = hyperdash ? new Banana() : null - }; - - return new TestDrawableFruit(fruit) - { - Anchor = Anchor.Centre, - RelativePositionAxes = Axes.None, - Position = Vector2.Zero, - }; - } - - private class TestDrawableFruit : DrawableFruit - { - public TestDrawableFruit(Fruit fruit) - : base(fruit) - { - } - - protected override void OnApply(HitObject hitObject) - { - base.OnApply(hitObject); - LifetimeStart = double.NegativeInfinity; - LifetimeEnd = double.PositiveInfinity; - } + return d; } public class TestCatchFruit : Fruit @@ -127,26 +61,9 @@ namespace osu.Game.Rulesets.Catch.Tests public TestCatchFruit(FruitVisualRepresentation rep) { VisualRepresentation = rep; - StartTime = 1000000000000; } public override FruitVisualRepresentation VisualRepresentation { get; } } - - public class TestCatchDroplet : Droplet - { - public TestCatchDroplet() - { - StartTime = 1000000000000; - } - } - - public class TestCatchTinyDroplet : TinyDroplet - { - public TestCatchTinyDroplet() - { - StartTime = 1000000000000; - } - } } } From 875d7dec7999f96e03c9454ce256d283ff299286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 17 Nov 2020 20:11:13 +0100 Subject: [PATCH 032/100] Remove redundant `this.` qualifier --- osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 3b9e5e948a..7fe3917893 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Judgements public DefaultJudgementPiece(HitResult result) { - this.Result = result; + Result = result; Origin = Anchor.Centre; } From 57eaee27aec1110f869c9ca5f25262bede0e0188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 17 Nov 2020 20:12:04 +0100 Subject: [PATCH 033/100] Rename param to match type better --- osu.Game/Skinning/LegacyJudgementPiece.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPiece.cs b/osu.Game/Skinning/LegacyJudgementPiece.cs index 6c606c638d..3def37e22c 100644 --- a/osu.Game/Skinning/LegacyJudgementPiece.cs +++ b/osu.Game/Skinning/LegacyJudgementPiece.cs @@ -14,14 +14,14 @@ namespace osu.Game.Skinning { private readonly HitResult result; - public LegacyJudgementPiece(HitResult result, Drawable texture) + public LegacyJudgementPiece(HitResult result, Drawable drawable) { this.result = result; AutoSizeAxes = Axes.Both; Origin = Anchor.Centre; - InternalChild = texture; + InternalChild = drawable; } public virtual void PlayAnimation() From c8fb49d540d4876fb3b7800c4159da6f5239fe24 Mon Sep 17 00:00:00 2001 From: kamp Date: Tue, 17 Nov 2020 22:23:46 +0100 Subject: [PATCH 034/100] Apply suggestions and remove redundant updateConnectingPath call --- .../PathControlPointConnectionPiece.cs | 16 +++------------- .../Components/PathControlPointVisualiser.cs | 6 ++---- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index 45c4a61ce1..eb7011e8b0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -20,17 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private readonly Path path; private readonly Slider slider; - private int controlPointIndex; - - public int ControlPointIndex - { - get => controlPointIndex; - set - { - controlPointIndex = value; - updateConnectingPath(); - } - } + public int ControlPointIndex { get; set; } private IBindable sliderPosition; private IBindable pathVersion; @@ -38,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public PathControlPointConnectionPiece(Slider slider, int controlPointIndex) { this.slider = slider; - this.controlPointIndex = controlPointIndex; + ControlPointIndex = controlPointIndex; Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; @@ -74,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components path.ClearVertices(); - int nextIndex = controlPointIndex + 1; + int nextIndex = ControlPointIndex + 1; if (nextIndex == 0 || nextIndex >= slider.Path.ControlPoints.Count) return; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 14ce0e065b..e551be4af3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components switch (e.Action) { case NotifyCollectionChangedAction.Add: - // If inserting in the the path (not appending), + // If inserting in the path (not appending), // update indices of existing connections after insert location if (e.NewStartingIndex < Pieces.Count) { @@ -93,8 +93,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components break; case NotifyCollectionChangedAction.Remove: - int oldSize = Pieces.Count; - foreach (var point in e.OldItems.Cast()) { Pieces.RemoveAll(p => p.ControlPoint == point); @@ -103,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // If removing before the end of the path, // update indices of connections after remove location - if (e.OldStartingIndex + e.OldItems.Count < oldSize) + if (e.OldStartingIndex + e.OldItems.Count < Pieces.Count + e.OldItems.Count) { foreach (var connection in Connections) { From 2d66423fbdc37cba19f675897297a8efa2131784 Mon Sep 17 00:00:00 2001 From: kamp Date: Tue, 17 Nov 2020 23:04:38 +0100 Subject: [PATCH 035/100] Simplify inequality --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index e551be4af3..7375c0e981 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // If removing before the end of the path, // update indices of connections after remove location - if (e.OldStartingIndex + e.OldItems.Count < Pieces.Count + e.OldItems.Count) + if (e.OldStartingIndex < Pieces.Count) { foreach (var connection in Connections) { From 783c172b5de26e53b1ff0982f8f49a9bcd7bbad1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 18 Nov 2020 13:33:22 +0900 Subject: [PATCH 036/100] Make sealed and cleanup comparator --- .../Edit/Compose/Components/BlueprintContainer.cs | 3 +-- ...ner.cs => HitObjectOrderedSelectionContainer.cs} | 13 +++++-------- .../Timeline/TimelineBlueprintContainer.cs | 6 +++--- 3 files changed, 9 insertions(+), 13 deletions(-) rename osu.Game/Screens/Edit/Compose/Components/{SelectionBlueprintContainer.cs => HitObjectOrderedSelectionContainer.cs} (85%) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 3aaa0c7d89..4b98d42c7c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -118,8 +118,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - protected virtual SelectionBlueprintContainer CreateSelectionBlueprintContainer() => - new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected virtual Container CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }; /// /// Creates a which outlines s and handles movement of selections. diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs similarity index 85% rename from osu.Game/Screens/Edit/Compose/Components/SelectionBlueprintContainer.cs rename to osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index 54932f6252..ae50b0fd61 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -9,14 +9,12 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Screens.Edit.Compose.Components { - public class SelectionBlueprintContainer : Container + public sealed class HitObjectOrderedSelectionContainer : Container { public override void Add(SelectionBlueprint drawable) { base.Add(drawable); - - if (Content == this) - bindStartTime(drawable); + bindStartTime(drawable); } public override bool Remove(SelectionBlueprint drawable) @@ -24,8 +22,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (!base.Remove(drawable)) return false; - if (Content == this) - unbindStartTime(drawable); + unbindStartTime(drawable); return true; } @@ -65,8 +62,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override int Compare(Drawable x, Drawable y) { - if (!(x is SelectionBlueprint xObj) || !(y is SelectionBlueprint yObj)) - return base.Compare(x, y); + var xObj = (SelectionBlueprint)x; + var yObj = (SelectionBlueprint)y; // Put earlier blueprints towards the end of the list, so they handle input first int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 2bd4ac2f91..2f14c607c2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected override SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected override Container CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; protected override void OnDrag(DragEvent e) { @@ -195,13 +195,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected class TimelineSelectionBlueprintContainer : SelectionBlueprintContainer + protected class TimelineSelectionBlueprintContainer : Container { protected override Container Content { get; } public TimelineSelectionBlueprintContainer() { - AddInternal(new TimelinePart(Content = new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); + AddInternal(new TimelinePart(Content = new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); } } } From f00c23b4a07367179d67c7291e1305f1ca01ab3d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 18 Nov 2020 13:37:15 +0900 Subject: [PATCH 037/100] Add comment + xmldoc --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 1 + .../Compose/Components/HitObjectOrderedSelectionContainer.cs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 4b98d42c7c..df9cadebfc 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -337,6 +337,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether a selection was performed. private bool beginClickSelection(MouseButtonEvent e) { + // Iterate from the top of the input stack (blueprints closest to the front of the screen first). foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse()) { if (!blueprint.IsHovered) continue; diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index ae50b0fd61..9e95fe4fa1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -6,9 +6,13 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit.Compose.Components { + /// + /// A container for ordered by their start times. + /// public sealed class HitObjectOrderedSelectionContainer : Container { public override void Add(SelectionBlueprint drawable) From efc18887c8182d6414ff2b8efe47c1b4f709525e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 15:51:09 +0900 Subject: [PATCH 038/100] Update framework --- osu.Android.props | 2 +- osu.Desktop/OsuGameDesktop.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 6e3d5eec1f..4657896fac 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index b17611f23f..0feab9a717 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -139,7 +139,7 @@ namespace osu.Desktop // SDL2 DesktopWindow case DesktopWindow desktopWindow: - desktopWindow.CursorState.Value |= CursorState.Hidden; + desktopWindow.CursorState |= CursorState.Hidden; desktopWindow.SetIconFromStream(iconStream); desktopWindow.Title = Name; desktopWindow.DragDrop += f => fileDrop(new[] { f }); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1850ee3488..704ac5a611 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 2ac23f1503..346bd892b0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - + From bb1aacb360758c2c5e21e172bcb9f1985933a2b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 16:18:27 +0900 Subject: [PATCH 039/100] Fix SkinnableSprite initialising a drawable even when the texture is not available --- osu.Game/Skinning/SkinnableSprite.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 5352928ec6..1340d1474c 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -24,7 +24,15 @@ namespace osu.Game.Skinning { } - protected override Drawable CreateDefault(ISkinComponent component) => new Sprite { Texture = textures.Get(component.LookupName) }; + protected override Drawable CreateDefault(ISkinComponent component) + { + var texture = textures.Get(component.LookupName); + + if (texture == null) + return null; + + return new Sprite { Texture = texture }; + } private class SpriteComponent : ISkinComponent { From 8be31f4805385708909b4405dd3c2aeb427bda15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Nov 2020 23:55:21 +0900 Subject: [PATCH 040/100] Adjust legacy skin judgement transforms to match stable --- osu.Game/Skinning/LegacyJudgementPiece.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPiece.cs b/osu.Game/Skinning/LegacyJudgementPiece.cs index 3def37e22c..44aa5106e2 100644 --- a/osu.Game/Skinning/LegacyJudgementPiece.cs +++ b/osu.Game/Skinning/LegacyJudgementPiece.cs @@ -14,6 +14,8 @@ namespace osu.Game.Skinning { private readonly HitResult result; + private bool hasParticle; + public LegacyJudgementPiece(HitResult result, Drawable drawable) { this.result = result; @@ -37,6 +39,8 @@ namespace osu.Game.Skinning if (animation?.FrameCount > 1) return; + const double animation_length = 500; + switch (result) { case HitResult.Miss: @@ -49,8 +53,19 @@ namespace osu.Game.Skinning break; default: - this.ScaleTo(0.9f); - this.ScaleTo(1, 500, Easing.OutElastic); + if (!hasParticle) + { + this.ScaleTo(0.6f).Then() + .ScaleTo(1.1f, animation_length * 0.8f).Then() + .ScaleTo(0.9f, animation_length * 0.4f).Then() + .ScaleTo(1f, animation_length * 0.2f); + } + else + { + this.ScaleTo(0.9f); + this.ScaleTo(1.05f, animation_length); + } + break; } } From 5bd4ace37f73cffd0550812e455bb7c3335cf623 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 15:38:26 +0900 Subject: [PATCH 041/100] Split out new and old style legacy judgement pieces --- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 93 +++++++++++++++++++ ...entPiece.cs => LegacyJudgementPieceOld.cs} | 31 +++---- osu.Game/Skinning/LegacySkin.cs | 30 +++++- 3 files changed, 133 insertions(+), 21 deletions(-) create mode 100644 osu.Game/Skinning/LegacyJudgementPieceNew.cs rename osu.Game/Skinning/{LegacyJudgementPiece.cs => LegacyJudgementPieceOld.cs} (61%) diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs new file mode 100644 index 0000000000..de77df9e10 --- /dev/null +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -0,0 +1,93 @@ +// 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.Graphics; +using osu.Framework.Graphics.Animations; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osuTK; + +namespace osu.Game.Skinning +{ + public class LegacyJudgementPieceNew : CompositeDrawable, IAnimatableJudgement + { + private readonly HitResult result; + + private readonly LegacyJudgementPieceOld temporaryOldStyle; + + private readonly Drawable mainPiece; + + public LegacyJudgementPieceNew(HitResult result, Func createMainDrawable, Drawable particleDrawable) + { + this.result = result; + + AutoSizeAxes = Axes.Both; + Origin = Anchor.Centre; + + InternalChildren = new[] + { + mainPiece = createMainDrawable().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + }) + }; + + if (result != HitResult.Miss) + { + //new judgement shows old as a temporary effect + AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, createMainDrawable, 1.05f) + { + Blending = BlendingParameters.Additive, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + } + + public void PlayAnimation() + { + var animation = mainPiece as IFramedAnimation; + + animation?.GotoFrame(0); + + this.RotateTo(0); + this.MoveTo(Vector2.Zero); + + if (temporaryOldStyle != null) + { + temporaryOldStyle.PlayAnimation(); + + temporaryOldStyle.Hide(); + temporaryOldStyle.Delay(-16) + .FadeTo(0.5f, 56, Easing.Out).Then() + .FadeOut(300); + } + + // legacy judgements don't play any transforms if they are an animation. + if (animation?.FrameCount > 1) + return; + + switch (result) + { + case HitResult.Miss: + mainPiece.ScaleTo(1.6f); + mainPiece.ScaleTo(1, 100, Easing.In); + + mainPiece.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + + mainPiece.RotateTo(40, 800, Easing.InQuint); + break; + + default: + const double animation_length = 1100; + + mainPiece.ScaleTo(0.9f); + mainPiece.ScaleTo(1.05f, animation_length); + break; + } + } + } +} diff --git a/osu.Game/Skinning/LegacyJudgementPiece.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs similarity index 61% rename from osu.Game/Skinning/LegacyJudgementPiece.cs rename to osu.Game/Skinning/LegacyJudgementPieceOld.cs index 44aa5106e2..63d2c44dd9 100644 --- a/osu.Game/Skinning/LegacyJudgementPiece.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.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 osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; @@ -10,20 +11,21 @@ using osuTK; namespace osu.Game.Skinning { - public class LegacyJudgementPiece : CompositeDrawable, IAnimatableJudgement + public class LegacyJudgementPieceOld : CompositeDrawable, IAnimatableJudgement { private readonly HitResult result; - private bool hasParticle; + private readonly float finalScale; - public LegacyJudgementPiece(HitResult result, Drawable drawable) + public LegacyJudgementPieceOld(HitResult result, Func createMainDrawable, float finalScale = 1f) { this.result = result; + this.finalScale = finalScale; AutoSizeAxes = Axes.Both; Origin = Anchor.Centre; - InternalChild = drawable; + InternalChild = createMainDrawable(); } public virtual void PlayAnimation() @@ -39,8 +41,6 @@ namespace osu.Game.Skinning if (animation?.FrameCount > 1) return; - const double animation_length = 500; - switch (result) { case HitResult.Miss: @@ -53,19 +53,14 @@ namespace osu.Game.Skinning break; default: - if (!hasParticle) - { - this.ScaleTo(0.6f).Then() - .ScaleTo(1.1f, animation_length * 0.8f).Then() - .ScaleTo(0.9f, animation_length * 0.4f).Then() - .ScaleTo(1f, animation_length * 0.2f); - } - else - { - this.ScaleTo(0.9f); - this.ScaleTo(1.05f, animation_length); - } + const double animation_length = 120; + this.ScaleTo(0.6f).Then() + .ScaleTo(1.1f, animation_length * 0.8f).Then() + // this is actually correct to match stable; there were overlapping transforms. + .ScaleTo(0.9f).Delay(animation_length * 0.2f) + .ScaleTo(1.1f).ScaleTo(0.9f, animation_length * 0.2f).Then() + .ScaleTo(0.95f).ScaleTo(finalScale, animation_length * 0.2f); break; } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index ca8bb58023..ce1a736f0a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -371,9 +371,16 @@ namespace osu.Game.Skinning } case GameplaySkinComponent resultComponent: - var drawable = getJudgementAnimation(resultComponent.Component); - if (drawable != null) - return new LegacyJudgementPiece(resultComponent.Component, drawable); + Func createDrawable = () => getJudgementAnimation(resultComponent.Component); + + if (createDrawable() != null) + { + var particles = getParticleTexture(resultComponent.Component); + if (particles != null) + return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, getParticleTexture(resultComponent.Component)); + else + return new LegacyJudgementPieceOld(resultComponent.Component, createDrawable); + } break; } @@ -381,6 +388,23 @@ namespace osu.Game.Skinning return this.GetAnimation(component.LookupName, false, false); } + private Drawable getParticleTexture(HitResult result) + { + switch (result) + { + case HitResult.Meh: + return this.GetAnimation("particle50", false, false); + + case HitResult.Ok: + return this.GetAnimation("particle100", false, false); + + case HitResult.Great: + return this.GetAnimation("particle300", false, false); + } + + return null; + } + private Drawable getJudgementAnimation(HitResult result) { switch (result) From 94886a09b23b2c7d850781eaa20c07b0697a2d3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 15:39:02 +0900 Subject: [PATCH 042/100] Remove fades from DrawableJudgement itself --- .../UI/DrawableManiaJudgement.cs | 7 +++---- .../Objects/Drawables/DrawableOsuJudgement.cs | 14 +++----------- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 11 +++++++---- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index ebce40a785..a341cdd8ec 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -19,15 +19,14 @@ namespace osu.Game.Rulesets.Mania.UI { } - protected override double FadeInDuration => 50; - protected override void ApplyHitAnimations() { JudgementBody.ScaleTo(0.8f); JudgementBody.ScaleTo(1, 250, Easing.OutElastic); - JudgementBody.Delay(FadeInDuration).ScaleTo(0.75f, 250); - this.Delay(FadeInDuration).FadeOut(200); + JudgementBody.Delay(50) + .ScaleTo(0.75f, 250) + .FadeOut(200); } protected override Drawable CreateDefaultJudgement(HitResult result) => new ManiaJudgementPiece(result); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 47fb53379f..90133fb01c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -44,26 +44,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - private double fadeOutDelay; - protected override double FadeOutDelay => fadeOutDelay; - protected override void ApplyHitAnimations() { bool hitLightingEnabled = config.Get(OsuSetting.HitLighting); if (hitLightingEnabled) { - JudgementBody.FadeIn().Delay(FadeInDuration).FadeOut(400); - Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out); Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000); - } - else - { - JudgementBody.Alpha = 1; - } - fadeOutDelay = hitLightingEnabled ? 1400 : base.FadeOutDelay; + // extend the lifetime to cover lighting fade + LifetimeEnd = 1400; + } base.ApplyHitAnimations(); } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 468b3190b0..09b8353c63 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.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.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -32,11 +33,13 @@ namespace osu.Game.Rulesets.Judgements /// /// Duration of initial fade in. /// + [Obsolete("Apply any animations manually via ApplyHitAnimations / ApplyMissAnimations. Defaults were moved inside skinned components.")] protected virtual double FadeInDuration => 100; /// /// Duration to wait until fade out begins. Defaults to . /// + [Obsolete("Apply any animations manually via ApplyHitAnimations / ApplyMissAnimations. Defaults were moved inside skinned components.")] protected virtual double FadeOutDelay => FadeInDuration; /// @@ -73,7 +76,6 @@ namespace osu.Game.Rulesets.Judgements /// protected virtual void ApplyHitAnimations() { - this.Delay(FadeOutDelay).FadeOut(400); } /// @@ -112,8 +114,6 @@ namespace osu.Game.Rulesets.Judgements // not sure if this should remain going forward. skinnableJudgement.ResetAnimation(); - this.FadeInFromZero(FadeInDuration, Easing.OutQuint); - switch (Result.Type) { case HitResult.None: @@ -134,7 +134,10 @@ namespace osu.Game.Rulesets.Judgements animatable.PlayAnimation(); } - Expire(true); + JudgementBody.Expire(true); + + LifetimeStart = JudgementBody.LifetimeStart; + LifetimeEnd = JudgementBody.LifetimeEnd; } private HitResult? currentDrawableType; From 25d4511e49d6c7db38eb8e6ab349bbfd2434a56b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 16:09:51 +0900 Subject: [PATCH 043/100] Fix judgement test scene always using hitobjects at t=0 --- osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 646f12f710..7ea2ef3a78 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -89,7 +89,13 @@ namespace osu.Game.Rulesets.Osu.Tests Children = new Drawable[] { pool, - pool.Get(j => j.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)).With(j => + pool.Get(j => j.Apply(new JudgementResult(new HitObject + { + StartTime = Time.Current + }, new Judgement()) + { + Type = result, + }, null)).With(j => { j.Anchor = Anchor.Centre; j.Origin = Anchor.Centre; From 72a15ef2dc51f877dd1416128a43af38f2b718fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 15:51:09 +0900 Subject: [PATCH 044/100] Handle DrawableJudgement lifetime more flexibly --- .../Objects/Drawables/DrawableOsuJudgement.cs | 2 +- .../Rulesets/Judgements/DrawableJudgement.cs | 51 +++++++++++-------- .../Judgements/IAnimatableJudgement.cs | 4 +- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 90133fb01c..b06c55102c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000); // extend the lifetime to cover lighting fade - LifetimeEnd = 1400; + LifetimeEnd = Lighting.LatestTransformEndTime; } base.ApplyHitAnimations(); diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 09b8353c63..45b3e229f3 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -89,7 +89,6 @@ namespace osu.Game.Rulesets.Judgements /// protected virtual void ApplyMissAnimations() { - this.Delay(600).FadeOut(200); } /// @@ -111,33 +110,43 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); - // not sure if this should remain going forward. - skinnableJudgement.ResetAnimation(); + LifetimeStart = Result.TimeAbsolute; - switch (Result.Type) + using (BeginAbsoluteSequence(Result.TimeAbsolute, true)) { - case HitResult.None: - break; + // not sure if this should remain going forward. + skinnableJudgement.ResetAnimation(); - case HitResult.Miss: - ApplyMissAnimations(); - break; + switch (Result.Type) + { + case HitResult.None: + break; - default: - ApplyHitAnimations(); - break; - } + case HitResult.Miss: + ApplyMissAnimations(); + break; + + default: + ApplyHitAnimations(); + break; + } + + if (skinnableJudgement.Drawable is IAnimatableJudgement animatable) + { + var drawableAnimation = (Drawable)animatable; + + drawableAnimation.ClearTransforms(); - if (skinnableJudgement.Drawable is IAnimatableJudgement animatable) - { - using (BeginAbsoluteSequence(Result.TimeAbsolute)) animatable.PlayAnimation(); + + drawableAnimation.Expire(true); + + // a derived version of DrawableJudgement may be adjusting lifetime. + // if not adjusted (or the skinned portion requires greater bounds than calculated) use the skinned source's lifetime. + if (LifetimeEnd == double.MaxValue || drawableAnimation.LifetimeEnd > LifetimeEnd) + LifetimeEnd = drawableAnimation.LifetimeEnd; + } } - - JudgementBody.Expire(true); - - LifetimeStart = JudgementBody.LifetimeStart; - LifetimeEnd = JudgementBody.LifetimeEnd; } private HitResult? currentDrawableType; diff --git a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs index 3d5bbe6dad..32312f1115 100644 --- a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs @@ -1,12 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; + namespace osu.Game.Rulesets.Judgements { /// /// A skinnable judgement element which supports playing an animation from the current point in time. /// - public interface IAnimatableJudgement + public interface IAnimatableJudgement : IDrawable { void PlayAnimation(); } From 9d0a6de26e6039cf87dfd8c64f1f0ffb7f3643d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 16:18:27 +0900 Subject: [PATCH 045/100] Fix SkinnableSprite initialising a drawable even when the texture is not available --- osu.Game/Skinning/SkinnableSprite.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 5352928ec6..1340d1474c 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -24,7 +24,15 @@ namespace osu.Game.Skinning { } - protected override Drawable CreateDefault(ISkinComponent component) => new Sprite { Texture = textures.Get(component.LookupName) }; + protected override Drawable CreateDefault(ISkinComponent component) + { + var texture = textures.Get(component.LookupName); + + if (texture == null) + return null; + + return new Sprite { Texture = texture }; + } private class SpriteComponent : ISkinComponent { From 9d3de5bca0441ea4b04cab337c9ccdc074b170ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 16:18:58 +0900 Subject: [PATCH 046/100] Fix hit lighting dictating lifetime even when not present in skin --- .../Objects/Drawables/DrawableOsuJudgement.cs | 2 +- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index b06c55102c..c81cfd97dd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { bool hitLightingEnabled = config.Get(OsuSetting.HitLighting); - if (hitLightingEnabled) + if (hitLightingEnabled && Lighting.Drawable != null) { Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out); Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000); diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 45b3e229f3..c0fcb1eb3c 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -139,12 +139,14 @@ namespace osu.Game.Rulesets.Judgements animatable.PlayAnimation(); - drawableAnimation.Expire(true); - // a derived version of DrawableJudgement may be adjusting lifetime. // if not adjusted (or the skinned portion requires greater bounds than calculated) use the skinned source's lifetime. - if (LifetimeEnd == double.MaxValue || drawableAnimation.LifetimeEnd > LifetimeEnd) - LifetimeEnd = drawableAnimation.LifetimeEnd; + double lastTransformTime = drawableAnimation.LatestTransformEndTime; + + if (LifetimeEnd == double.MaxValue || lastTransformTime > LifetimeEnd) + { + LifetimeEnd = lastTransformTime; + } } } } From c47e70da9b4c6ac4ac337221b747f09ce3f5ab9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 17:15:45 +0900 Subject: [PATCH 047/100] Update animations for new/old miss style and add fades --- .../Judgements/DefaultJudgementPiece.cs | 8 ++++-- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 28 +++++++++++++------ osu.Game/Skinning/LegacyJudgementPieceOld.cs | 25 ++++++++++------- osu.Game/Skinning/LegacySkin.cs | 8 +++--- 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 7fe3917893..b89c1f4e4f 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -47,18 +47,18 @@ namespace osu.Game.Rulesets.Judgements public virtual void PlayAnimation() { - this.RotateTo(0); - this.MoveTo(Vector2.Zero); - switch (Result) { case HitResult.Miss: this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); + this.MoveTo(Vector2.Zero); this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + this.RotateTo(0); this.RotateTo(40, 800, Easing.InQuint); + break; default: @@ -66,6 +66,8 @@ namespace osu.Game.Rulesets.Judgements this.ScaleTo(1, 500, Easing.OutElastic); break; } + + this.FadeOutFromOne(800); } } } diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index de77df9e10..d0a2f1d4eb 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osuTK; @@ -53,9 +54,14 @@ namespace osu.Game.Skinning animation?.GotoFrame(0); - this.RotateTo(0); - this.MoveTo(Vector2.Zero); + const double fade_in_length = 120; + const double fade_out_delay = 500; + const double fade_out_length = 600; + this.FadeInFromZero(fade_in_length); + this.Delay(fade_out_delay).FadeOut(fade_out_length); + + // new style non-miss judgements show the original style temporarily, with additive colour. if (temporaryOldStyle != null) { temporaryOldStyle.PlayAnimation(); @@ -73,19 +79,23 @@ namespace osu.Game.Skinning switch (result) { case HitResult.Miss: - mainPiece.ScaleTo(1.6f); - mainPiece.ScaleTo(1, 100, Easing.In); + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); - mainPiece.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + //todo: this only applies to osu! ruleset apparently. + this.MoveTo(new Vector2(0, -2)); + this.MoveToOffset(new Vector2(0, 20), fade_out_delay + fade_out_length, Easing.In); - mainPiece.RotateTo(40, 800, Easing.InQuint); + float rotation = RNG.NextSingle(-8.6f, 8.6f); + + this.RotateTo(0); + this.RotateTo(rotation, fade_in_length) + .Then().RotateTo(rotation * 2, fade_out_delay + fade_out_length - fade_in_length, Easing.In); break; default: - const double animation_length = 1100; - mainPiece.ScaleTo(0.9f); - mainPiece.ScaleTo(1.05f, animation_length); + mainPiece.ScaleTo(1.05f, fade_out_delay + fade_out_length); break; } } diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 63d2c44dd9..3486dce081 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -5,9 +5,9 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osuTK; namespace osu.Game.Skinning { @@ -34,8 +34,12 @@ namespace osu.Game.Skinning animation?.GotoFrame(0); - this.RotateTo(0); - this.MoveTo(Vector2.Zero); + const double fade_in_length = 120; + const double fade_out_delay = 500; + const double fade_out_length = 600; + + this.FadeInFromZero(fade_in_length); + this.Delay(fade_out_delay).FadeOut(fade_out_length); // legacy judgements don't play any transforms if they are an animation. if (animation?.FrameCount > 1) @@ -47,20 +51,21 @@ namespace osu.Game.Skinning this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); - this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + float rotation = RNG.NextSingle(-8.6f, 8.6f); - this.RotateTo(40, 800, Easing.InQuint); + this.RotateTo(0); + this.RotateTo(rotation, fade_in_length) + .Then().RotateTo(rotation * 2, fade_out_delay + fade_out_length - fade_in_length, Easing.In); break; default: - const double animation_length = 120; this.ScaleTo(0.6f).Then() - .ScaleTo(1.1f, animation_length * 0.8f).Then() + .ScaleTo(1.1f, fade_in_length * 0.8f).Then() // this is actually correct to match stable; there were overlapping transforms. - .ScaleTo(0.9f).Delay(animation_length * 0.2f) - .ScaleTo(1.1f).ScaleTo(0.9f, animation_length * 0.2f).Then() - .ScaleTo(0.95f).ScaleTo(finalScale, animation_length * 0.2f); + .ScaleTo(0.9f).Delay(fade_in_length * 0.2f) + .ScaleTo(1.1f).ScaleTo(0.9f, fade_in_length * 0.2f).Then() + .ScaleTo(0.95f).ScaleTo(finalScale, fade_in_length * 0.2f); break; } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index ce1a736f0a..9fbf3de043 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -373,11 +373,11 @@ namespace osu.Game.Skinning case GameplaySkinComponent resultComponent: Func createDrawable = () => getJudgementAnimation(resultComponent.Component); + // kind of wasteful that we throw this away, but should do for now. if (createDrawable() != null) { - var particles = getParticleTexture(resultComponent.Component); - if (particles != null) - return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, getParticleTexture(resultComponent.Component)); + if (Configuration.LegacyVersion > 1) + return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, getParticleDrawable(resultComponent.Component)); else return new LegacyJudgementPieceOld(resultComponent.Component, createDrawable); } @@ -388,7 +388,7 @@ namespace osu.Game.Skinning return this.GetAnimation(component.LookupName, false, false); } - private Drawable getParticleTexture(HitResult result) + private Drawable getParticleDrawable(HitResult result) { switch (result) { From d017e725fb87a1c3b22dfe17809defc002925db7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 17:15:53 +0900 Subject: [PATCH 048/100] Add comment for future todo task --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index c81cfd97dd..9699fb72a4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -50,6 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (hitLightingEnabled && Lighting.Drawable != null) { + // todo: this animation changes slightly based on new/old legacy skin versions. Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out); Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000); From ee8804b50b86e6d2bb85c18908f715fec0b599f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 17:53:27 +0900 Subject: [PATCH 049/100] Fix animation playback not running on skin change --- .../Rulesets/Judgements/DrawableJudgement.cs | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index c0fcb1eb3c..85875e2f2a 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -28,8 +28,13 @@ namespace osu.Game.Rulesets.Judgements protected Container JudgementBody { get; private set; } + public override bool RemoveCompletedTransforms => false; + private SkinnableDrawable skinnableJudgement; + [Resolved] + private ISkinSource skinSource { get; set; } + /// /// Duration of initial fade in. /// @@ -65,6 +70,27 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); } + protected override void LoadComplete() + { + base.LoadComplete(); + skinSource.SourceChanged += onSkinChanged; + } + + private void onSkinChanged() + { + // on a skin change, the child component will update but not get correctly triggered to play its animation. + // we need to trigger a reinitialisation to make things right. + currentDrawableType = null; + + PrepareForUse(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + skinSource.SourceChanged -= onSkinChanged; + } + /// /// Apply top-level animations to the current judgement when successfully hit. /// Generally used for fading, defaulting to a simple fade out based on . @@ -110,6 +136,12 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); + runAnimation(); + } + + private void runAnimation() + { + ClearTransforms(true); LifetimeStart = Result.TimeAbsolute; using (BeginAbsoluteSequence(Result.TimeAbsolute, true)) @@ -135,18 +167,13 @@ namespace osu.Game.Rulesets.Judgements { var drawableAnimation = (Drawable)animatable; - drawableAnimation.ClearTransforms(); - animatable.PlayAnimation(); - // a derived version of DrawableJudgement may be adjusting lifetime. + // a derived version of DrawableJudgement may be proposing a lifetime. // if not adjusted (or the skinned portion requires greater bounds than calculated) use the skinned source's lifetime. double lastTransformTime = drawableAnimation.LatestTransformEndTime; - if (LifetimeEnd == double.MaxValue || lastTransformTime > LifetimeEnd) - { LifetimeEnd = lastTransformTime; - } } } } From 1fd582d33341c396919d7f23920a18e55a00b982 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 19:12:29 +0900 Subject: [PATCH 050/100] Update lighting tests to not require lighting (some test skins are missing it) --- osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 7ea2ef3a78..b0119edc14 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -43,10 +43,8 @@ namespace osu.Game.Rulesets.Osu.Tests showResult(HitResult.Great); AddUntilStep("judgements shown", () => this.ChildrenOfType().Any()); - AddAssert("judgement body immediately visible", - () => this.ChildrenOfType().All(judgement => judgement.JudgementBody.Alpha == 1)); AddAssert("hit lighting hidden", - () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha == 0)); + () => this.ChildrenOfType().All(judgement => !judgement.Lighting.Transforms.Any())); } [Test] @@ -57,10 +55,8 @@ namespace osu.Game.Rulesets.Osu.Tests showResult(HitResult.Great); AddUntilStep("judgements shown", () => this.ChildrenOfType().Any()); - AddAssert("judgement body not immediately visible", - () => this.ChildrenOfType().All(judgement => judgement.JudgementBody.Alpha > 0 && judgement.JudgementBody.Alpha < 1)); AddAssert("hit lighting shown", - () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha > 0)); + () => this.ChildrenOfType().Any(judgement => judgement.Lighting.Transforms.Any())); } private void showResult(HitResult result) From 20bb64c627edd4043f6ff72886a2e91de506536c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 19:34:00 +0900 Subject: [PATCH 051/100] Fix mania misses not correctly animating (temporary solution) --- .../Skinning/TestSceneDrawableJudgement.cs | 5 +++- .../UI/DrawableManiaJudgement.cs | 28 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs index a4d4ec50f8..11536ac8c3 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs @@ -24,7 +24,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning if (hitWindows.IsHitResultAllowed(result)) { AddStep("Show " + result.GetDescription(), () => SetContents(() => - new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null) + new DrawableManiaJudgement(new JudgementResult(new HitObject + { + StartTime = Time.Current + }, new Judgement()) { Type = result }, null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index a341cdd8ec..a3dcd0e57f 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Mania.UI { @@ -19,6 +20,27 @@ namespace osu.Game.Rulesets.Mania.UI { } + protected override void ApplyMissAnimations() + { + if (!(JudgementBody.Drawable is DefaultManiaJudgementPiece)) + { + // this is temporary logic until mania's skin transformer returns IAnimatableJudgements + JudgementBody.ScaleTo(1.6f); + JudgementBody.ScaleTo(1, 100, Easing.In); + + JudgementBody.MoveTo(Vector2.Zero); + JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); + + JudgementBody.RotateTo(0); + JudgementBody.RotateTo(40, 800, Easing.InQuint); + JudgementBody.FadeOutFromOne(800); + + LifetimeEnd = JudgementBody.LatestTransformEndTime; + } + + base.ApplyMissAnimations(); + } + protected override void ApplyHitAnimations() { JudgementBody.ScaleTo(0.8f); @@ -29,11 +51,11 @@ namespace osu.Game.Rulesets.Mania.UI .FadeOut(200); } - protected override Drawable CreateDefaultJudgement(HitResult result) => new ManiaJudgementPiece(result); + protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result); - private class ManiaJudgementPiece : DefaultJudgementPiece + private class DefaultManiaJudgementPiece : DefaultJudgementPiece { - public ManiaJudgementPiece(HitResult result) + public DefaultManiaJudgementPiece(HitResult result) : base(result) { } From 8522ddc61e4d9a795feb290a66f1f3be6a948dfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 19:34:27 +0900 Subject: [PATCH 052/100] Reduce nesting of skinned component to reduce exposed surface --- .../TestSceneDrawableJudgement.cs | 2 +- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index b0119edc14..1339d11ab6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests private class TestDrawableOsuJudgement : DrawableOsuJudgement { public new SkinnableSprite Lighting => base.Lighting; - public new Container JudgementBody => base.JudgementBody; + public new SkinnableDrawable JudgementBody => base.JudgementBody; } } } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 85875e2f2a..72ffa3a34b 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; @@ -26,11 +25,9 @@ namespace osu.Game.Rulesets.Judgements public DrawableHitObject JudgedObject { get; private set; } - protected Container JudgementBody { get; private set; } - public override bool RemoveCompletedTransforms => false; - private SkinnableDrawable skinnableJudgement; + protected SkinnableDrawable JudgementBody { get; private set; } [Resolved] private ISkinSource skinSource { get; set; } @@ -147,7 +144,7 @@ namespace osu.Game.Rulesets.Judgements using (BeginAbsoluteSequence(Result.TimeAbsolute, true)) { // not sure if this should remain going forward. - skinnableJudgement.ResetAnimation(); + JudgementBody.ResetAnimation(); switch (Result.Type) { @@ -163,7 +160,7 @@ namespace osu.Game.Rulesets.Judgements break; } - if (skinnableJudgement.Drawable is IAnimatableJudgement animatable) + if (JudgementBody.Drawable is IAnimatableJudgement animatable) { var drawableAnimation = (Drawable)animatable; @@ -192,13 +189,11 @@ namespace osu.Game.Rulesets.Judgements if (JudgementBody != null) RemoveInternal(JudgementBody); - AddInternal(JudgementBody = new Container + AddInternal(JudgementBody = new SkinnableDrawable(new GameplaySkinComponent(type), _ => + CreateDefaultJudgement(type), confineMode: ConfineMode.NoScaling) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Child = skinnableJudgement = new SkinnableDrawable(new GameplaySkinComponent(type), _ => - CreateDefaultJudgement(type), confineMode: ConfineMode.NoScaling) }); currentDrawableType = type; From b3bec81b79b5fa88edbce4ab7b322b10a9fb0db0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 19:38:38 +0900 Subject: [PATCH 053/100] Update xmldoc to match new behaviour --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 72ffa3a34b..6407cb40aa 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -90,8 +90,7 @@ namespace osu.Game.Rulesets.Judgements /// /// Apply top-level animations to the current judgement when successfully hit. - /// Generally used for fading, defaulting to a simple fade out based on . - /// This will be used to calculate the lifetime of the judgement. + /// If displaying components which require lifetime extensions, manually adjusting is required. /// /// /// For animating the actual "default skin" judgement itself, it is recommended to use . @@ -103,8 +102,7 @@ namespace osu.Game.Rulesets.Judgements /// /// Apply top-level animations to the current judgement when missed. - /// Generally used for fading, defaulting to a simple fade out based on . - /// This will be used to calculate the lifetime of the judgement. + /// If displaying components which require lifetime extensions, manually adjusting is required. /// /// /// For animating the actual "default skin" judgement itself, it is recommended to use . From 191b95810ce519e6b9b3990ae105867b113f753a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Nov 2020 19:47:35 +0900 Subject: [PATCH 054/100] Fix whitespace issues --- .../Skinning/TestSceneDrawableJudgement.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs index 11536ac8c3..dcb25f21ba 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs @@ -24,10 +24,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning if (hitWindows.IsHitResultAllowed(result)) { AddStep("Show " + result.GetDescription(), () => SetContents(() => - new DrawableManiaJudgement(new JudgementResult(new HitObject + new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement()) { - StartTime = Time.Current - }, new Judgement()) { Type = result }, null) + Type = result + }, null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From cb5d1d0d77f1dbf87a01d113074dcdb1f31e7b57 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 18 Nov 2020 21:26:35 +0900 Subject: [PATCH 055/100] Remove obsolete method --- osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index d8c6da86f9..ba38c7f77d 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -10,14 +10,6 @@ namespace osu.Game.Rulesets.Objects { public static class SliderEventGenerator { - [Obsolete("Use the overload with cancellation support instead.")] // can be removed 20201115 - // ReSharper disable once RedundantOverload.Global - public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, - double? legacyLastTickOffset) - { - return Generate(startTime, spanDuration, velocity, tickDistance, totalDistance, spanCount, legacyLastTickOffset, default); - } - // ReSharper disable once MethodOverloadWithOptionalParameter public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, double? legacyLastTickOffset, CancellationToken cancellationToken = default) From 85c5c68dfac097bc15c9218853de000816b74f58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 13:20:21 +0900 Subject: [PATCH 056/100] Provide particle drawable as a function (for future use) --- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index d0a2f1d4eb..b5e1de337a 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -20,7 +20,7 @@ namespace osu.Game.Skinning private readonly Drawable mainPiece; - public LegacyJudgementPieceNew(HitResult result, Func createMainDrawable, Drawable particleDrawable) + public LegacyJudgementPieceNew(HitResult result, Func createMainDrawable, Func createParticleDrawable) { this.result = result; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 9fbf3de043..6faee8c2e7 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -377,7 +377,7 @@ namespace osu.Game.Skinning if (createDrawable() != null) { if (Configuration.LegacyVersion > 1) - return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, getParticleDrawable(resultComponent.Component)); + return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, () => getParticleDrawable(resultComponent.Component)); else return new LegacyJudgementPieceOld(resultComponent.Component, createDrawable); } From ba735584faac6f0c5ab081cbd1fed06b3b0ec7ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 14:04:43 +0900 Subject: [PATCH 057/100] Add null check for disposal safety --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 6407cb40aa..4aed38e689 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -85,7 +85,9 @@ namespace osu.Game.Rulesets.Judgements protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - skinSource.SourceChanged -= onSkinChanged; + + if (skinSource != null) + skinSource.SourceChanged -= onSkinChanged; } /// From 3024ae6d867b4eae0014aaba8d2ed7dbf0470bc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 14:10:07 +0900 Subject: [PATCH 058/100] Add better test coverage of hit lighting (and ensure reset after animation reapplication) --- osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs | 7 +++---- .../Objects/Drawables/DrawableOsuJudgement.cs | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 1339d11ab6..e4158d8f07 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -43,8 +43,8 @@ namespace osu.Game.Rulesets.Osu.Tests showResult(HitResult.Great); AddUntilStep("judgements shown", () => this.ChildrenOfType().Any()); - AddAssert("hit lighting hidden", - () => this.ChildrenOfType().All(judgement => !judgement.Lighting.Transforms.Any())); + AddAssert("hit lighting has no transforms", () => this.ChildrenOfType().All(judgement => !judgement.Lighting.Transforms.Any())); + AddAssert("hit lighting hidden", () => this.ChildrenOfType().All(judgement => judgement.Lighting.Alpha == 0)); } [Test] @@ -55,8 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests showResult(HitResult.Great); AddUntilStep("judgements shown", () => this.ChildrenOfType().Any()); - AddAssert("hit lighting shown", - () => this.ChildrenOfType().Any(judgement => judgement.Lighting.Transforms.Any())); + AddUntilStep("hit lighting shown", () => this.ChildrenOfType().Any(judgement => judgement.Lighting.Alpha > 0)); } private void showResult(HitResult result) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 9699fb72a4..13f5960bd4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -48,6 +48,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { bool hitLightingEnabled = config.Get(OsuSetting.HitLighting); + Lighting.Alpha = 0; + if (hitLightingEnabled && Lighting.Drawable != null) { // todo: this animation changes slightly based on new/old legacy skin versions. From 9df93e1f186d4fa592b549ba6b1a15a4112847c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 14:54:31 +0900 Subject: [PATCH 059/100] Add basic implementation of particle explosion Using drawables still, just to get things in place and setup the structure --- .../Gameplay/TestSceneParticleExplosion.cs | 30 ++++++++++ osu.Game/Graphics/ParticleExplosion.cs | 59 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs create mode 100644 osu.Game/Graphics/ParticleExplosion.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs new file mode 100644 index 0000000000..6df4842608 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [TestFixture] + public class TestSceneParticleExplosion : OsuTestScene + { + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + AddStep(@"display", () => + { + Child = new ParticleExplosion(textures.Get("Cursor/cursortrail"), 150, 1200) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200) + }; + }); + } + } +} diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs new file mode 100644 index 0000000000..6a6f947dd5 --- /dev/null +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -0,0 +1,59 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Utils; +using osuTK; + +namespace osu.Game.Graphics +{ + public class ParticleExplosion : CompositeDrawable + { + public ParticleExplosion(Texture texture, int particleCount, double duration) + { + for (int i = 0; i < particleCount; i++) + { + double rDuration = RNG.NextDouble(duration / 3, duration); + + AddInternal(new Particle(rDuration, RNG.NextSingle(0, MathF.PI * 2)) + { + Texture = texture + }); + } + } + + private class Particle : Sprite + { + private readonly double duration; + private readonly float direction; + + private Vector2 positionForOffset(float offset) => new Vector2( + (float)(offset * Math.Sin(direction)), + (float)(offset * Math.Cos(direction)) + ); + + public Particle(double duration, float direction) + { + this.duration = duration; + this.direction = direction; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + RelativePositionAxes = Axes.Both; + Position = positionForOffset(0); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + this.MoveTo(positionForOffset(1), duration); + this.FadeOut(duration); + Expire(); + } + } + } +} From 9d04ce75ccba4d263a40e961552eed793a8348ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 15:47:02 +0900 Subject: [PATCH 060/100] Make particles additive and consume in judgement explosions --- osu.Game/Graphics/ParticleExplosion.cs | 24 ++++++++++++++++---- osu.Game/Skinning/LegacyJudgementPieceNew.cs | 24 +++++++++++++++++++- osu.Game/Skinning/LegacySkin.cs | 10 ++++---- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index 6a6f947dd5..4daae34d62 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -26,11 +27,19 @@ namespace osu.Game.Graphics } } + public void Restart() + { + foreach (var p in InternalChildren.OfType()) + p.Play(); + } + private class Particle : Sprite { private readonly double duration; private readonly float direction; + public override bool RemoveWhenNotAlive => false; + private Vector2 positionForOffset(float offset) => new Vector2( (float)(offset * Math.Sin(direction)), (float)(offset * Math.Cos(direction)) @@ -40,18 +49,25 @@ namespace osu.Game.Graphics { this.duration = duration; this.direction = direction; - Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Blending = BlendingParameters.Additive; + RelativePositionAxes = Axes.Both; - Position = positionForOffset(0); } protected override void LoadComplete() { base.LoadComplete(); + Play(); + } - this.MoveTo(positionForOffset(1), duration); - this.FadeOut(duration); + public void Play() + { + this.MoveTo(new Vector2(0.5f)); + this.MoveTo(new Vector2(0.5f) + positionForOffset(0.5f), duration); + + this.FadeOutFromOne(duration); Expire(); } } diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index b5e1de337a..2a53820872 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -5,7 +5,9 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Textures; using osu.Framework.Utils; +using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osuTK; @@ -20,7 +22,9 @@ namespace osu.Game.Skinning private readonly Drawable mainPiece; - public LegacyJudgementPieceNew(HitResult result, Func createMainDrawable, Func createParticleDrawable) + private readonly ParticleExplosion particles; + + public LegacyJudgementPieceNew(HitResult result, Func createMainDrawable, Texture particleTexture) { this.result = result; @@ -36,6 +40,17 @@ namespace osu.Game.Skinning }) }; + if (particleTexture != null) + { + AddInternal(particles = new ParticleExplosion(particleTexture, 150, 1600) + { + Size = new Vector2(140), + Depth = float.MaxValue, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + if (result != HitResult.Miss) { //new judgement shows old as a temporary effect @@ -54,6 +69,13 @@ namespace osu.Game.Skinning animation?.GotoFrame(0); + if (particles != null) + { + // start the particles already some way into their animation to break cluster away from centre. + using (particles.BeginDelayedSequence(-100, true)) + particles.Restart(); + } + const double fade_in_length = 120; const double fade_out_delay = 500; const double fade_out_length = 600; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 6faee8c2e7..63a22eba62 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -377,7 +377,7 @@ namespace osu.Game.Skinning if (createDrawable() != null) { if (Configuration.LegacyVersion > 1) - return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, () => getParticleDrawable(resultComponent.Component)); + return new LegacyJudgementPieceNew(resultComponent.Component, createDrawable, getParticleTexture(resultComponent.Component)); else return new LegacyJudgementPieceOld(resultComponent.Component, createDrawable); } @@ -388,18 +388,18 @@ namespace osu.Game.Skinning return this.GetAnimation(component.LookupName, false, false); } - private Drawable getParticleDrawable(HitResult result) + private Texture getParticleTexture(HitResult result) { switch (result) { case HitResult.Meh: - return this.GetAnimation("particle50", false, false); + return GetTexture("particle50"); case HitResult.Ok: - return this.GetAnimation("particle100", false, false); + return GetTexture("particle100"); case HitResult.Great: - return this.GetAnimation("particle300", false, false); + return GetTexture("particle300"); } return null; From efd5acb8abb0e73a9e5ba632d592d485dc1f2443 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 15:55:11 +0900 Subject: [PATCH 061/100] Randomise direction every animation playback --- osu.Game/Graphics/ParticleExplosion.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index 4daae34d62..200bcc062a 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -20,7 +20,7 @@ namespace osu.Game.Graphics { double rDuration = RNG.NextDouble(duration / 3, duration); - AddInternal(new Particle(rDuration, RNG.NextSingle(0, MathF.PI * 2)) + AddInternal(new Particle(rDuration) { Texture = texture }); @@ -36,19 +36,12 @@ namespace osu.Game.Graphics private class Particle : Sprite { private readonly double duration; - private readonly float direction; public override bool RemoveWhenNotAlive => false; - private Vector2 positionForOffset(float offset) => new Vector2( - (float)(offset * Math.Sin(direction)), - (float)(offset * Math.Cos(direction)) - ); - - public Particle(double duration, float direction) + public Particle(double duration) { this.duration = duration; - this.direction = direction; Origin = Anchor.Centre; Blending = BlendingParameters.Additive; @@ -64,11 +57,18 @@ namespace osu.Game.Graphics public void Play() { + double direction = RNG.NextSingle(0, MathF.PI * 2); + this.MoveTo(new Vector2(0.5f)); this.MoveTo(new Vector2(0.5f) + positionForOffset(0.5f), duration); this.FadeOutFromOne(duration); Expire(); + + Vector2 positionForOffset(float offset) => new Vector2( + (float)(offset * Math.Sin(direction)), + (float)(offset * Math.Cos(direction)) + ); } } } From 83024f1ec524db962d88645c1e6fe5be9623a594 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 16:00:20 +0900 Subject: [PATCH 062/100] Add back positional randomness from stable --- osu.Game/Graphics/ParticleExplosion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index 200bcc062a..8a3be513be 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -60,7 +60,7 @@ namespace osu.Game.Graphics double direction = RNG.NextSingle(0, MathF.PI * 2); this.MoveTo(new Vector2(0.5f)); - this.MoveTo(new Vector2(0.5f) + positionForOffset(0.5f), duration); + this.MoveTo(new Vector2(0.5f) + positionForOffset(RNG.NextSingle(0.5f)), duration); this.FadeOutFromOne(duration); Expire(); From fe025043bd59d8164710cce507c16589ad439c98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 17:16:29 +0900 Subject: [PATCH 063/100] Make test run multiple times --- .../Visual/Gameplay/TestSceneParticleExplosion.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs index 6df4842608..63c8757afd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs @@ -16,15 +16,15 @@ namespace osu.Game.Tests.Visual.Gameplay [BackgroundDependencyLoader] private void load(TextureStore textures) { - AddStep(@"display", () => + AddRepeatStep(@"display", () => { Child = new ParticleExplosion(textures.Get("Cursor/cursortrail"), 150, 1200) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(200) + Size = new Vector2(400) }; - }); + }, 10); } } } From 476d0256ccc0d7620d84316fc7f3bb2657e4b2d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 17:22:37 +0900 Subject: [PATCH 064/100] Replace particle explosion implementation with DrawNode version --- osu.Game/Graphics/ParticleExplosion.cs | 128 ++++++++++++++++++------- 1 file changed, 96 insertions(+), 32 deletions(-) diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index 8a3be513be..ba6d26ec22 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -2,9 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; +using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; @@ -12,64 +13,127 @@ using osuTK; namespace osu.Game.Graphics { - public class ParticleExplosion : CompositeDrawable + public class ParticleExplosion : Sprite { + private readonly double duration; + private double startTime; + + private readonly List parts = new List(); + public ParticleExplosion(Texture texture, int particleCount, double duration) { - for (int i = 0; i < particleCount; i++) - { - double rDuration = RNG.NextDouble(duration / 3, duration); + Texture = texture; + this.duration = duration; + Blending = BlendingParameters.Additive; - AddInternal(new Particle(rDuration) - { - Texture = texture - }); - } + for (int i = 0; i < particleCount; i++) + parts.Add(new ParticlePart(duration)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Restart(); } public void Restart() { - foreach (var p in InternalChildren.OfType()) - p.Play(); + startTime = TransformStartTime; + + this.FadeOutFromOne(duration); + + foreach (var p in parts) + p.Randomise(); } - private class Particle : Sprite + protected override void Update() { - private readonly double duration; + base.Update(); - public override bool RemoveWhenNotAlive => false; + Invalidate(Invalidation.DrawNode); + } - public Particle(double duration) + protected override DrawNode CreateDrawNode() => new ParticleExplosionDrawNode(this); + + private class ParticleExplosionDrawNode : SpriteDrawNode + { + private List parts = new List(); + + private ParticleExplosion source => (ParticleExplosion)Source; + + private double startTime; + private double currentTime; + private Vector2 sourceSize; + + public ParticleExplosionDrawNode(Sprite source) + : base(source) { - this.duration = duration; - - Origin = Anchor.Centre; - Blending = BlendingParameters.Additive; - - RelativePositionAxes = Axes.Both; } - protected override void LoadComplete() + public override void ApplyState() { - base.LoadComplete(); - Play(); + base.ApplyState(); + + parts = source.parts; + sourceSize = source.Size; + startTime = source.startTime; + currentTime = source.Time.Current; } - public void Play() + protected override void Blit(Action vertexAction) { - double direction = RNG.NextSingle(0, MathF.PI * 2); + foreach (var p in parts) + { + var pos = p.PositionAtTime(currentTime - startTime); - this.MoveTo(new Vector2(0.5f)); - this.MoveTo(new Vector2(0.5f) + positionForOffset(RNG.NextSingle(0.5f)), duration); + // todo: implement per particle. + var rect = new RectangleF(pos.X * sourceSize.X, pos.Y * sourceSize.Y, Texture.DisplayWidth, Texture.DisplayHeight); - this.FadeOutFromOne(duration); - Expire(); + var quad = new Quad( + Vector2Extensions.Transform(rect.TopLeft, DrawInfo.Matrix), + Vector2Extensions.Transform(rect.TopRight, DrawInfo.Matrix), + Vector2Extensions.Transform(rect.BottomLeft, DrawInfo.Matrix), + Vector2Extensions.Transform(rect.BottomRight, DrawInfo.Matrix) + ); + + DrawQuad(Texture, quad, DrawColourInfo.Colour, null, vertexAction, + new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height), + null, TextureCoords); + } + } + } + + private class ParticlePart + { + private readonly double totalDuration; + + private double duration; + private double direction; + private float distance; + + public ParticlePart(double totalDuration) + { + this.totalDuration = totalDuration; + + Randomise(); + } + + public Vector2 PositionAtTime(double time) + { + return new Vector2(0.5f) + positionForOffset(distance * (float)(time / duration)); Vector2 positionForOffset(float offset) => new Vector2( (float)(offset * Math.Sin(direction)), (float)(offset * Math.Cos(direction)) ); } + + public void Randomise() + { + distance = RNG.NextSingle(0.5f); + duration = RNG.NextDouble(totalDuration / 3, totalDuration); + direction = RNG.NextSingle(0, MathF.PI * 2); + } } } } From 3a7291c5cf699bfc4e83653a44ced17aa86a3f53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 17:56:11 +0900 Subject: [PATCH 065/100] Fix some behavioural regressions --- osu.Game/Graphics/ParticleExplosion.cs | 42 +++++++++++++++++--------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index ba6d26ec22..68dedf70a0 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -39,7 +39,6 @@ namespace osu.Game.Graphics public void Restart() { startTime = TransformStartTime; - this.FadeOutFromOne(duration); foreach (var p in parts) @@ -74,6 +73,8 @@ namespace osu.Game.Graphics { base.ApplyState(); + // this is mostly safe as the parts are immutable. + // the most that can go wrong is the random state be incorrect parts = source.parts; sourceSize = source.Size; startTime = source.startTime; @@ -82,13 +83,20 @@ namespace osu.Game.Graphics protected override void Blit(Action vertexAction) { + var time = currentTime - startTime; + foreach (var p in parts) { - var pos = p.PositionAtTime(currentTime - startTime); + Vector2 pos = p.PositionAtTime(time); + float alpha = p.AlphaAtTime(time); - // todo: implement per particle. - var rect = new RectangleF(pos.X * sourceSize.X, pos.Y * sourceSize.Y, Texture.DisplayWidth, Texture.DisplayHeight); + var rect = new RectangleF( + pos.X * sourceSize.X - Texture.DisplayWidth / 2, + pos.Y * sourceSize.Y - Texture.DisplayHeight / 2, + Texture.DisplayWidth, + Texture.DisplayHeight); + // convert to screen space. var quad = new Quad( Vector2Extensions.Transform(rect.TopLeft, DrawInfo.Matrix), Vector2Extensions.Transform(rect.TopRight, DrawInfo.Matrix), @@ -96,7 +104,7 @@ namespace osu.Game.Graphics Vector2Extensions.Transform(rect.BottomRight, DrawInfo.Matrix) ); - DrawQuad(Texture, quad, DrawColourInfo.Colour, null, vertexAction, + DrawQuad(Texture, quad, DrawColourInfo.Colour.MultiplyAlpha(alpha), null, vertexAction, new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height), null, TextureCoords); } @@ -105,22 +113,31 @@ namespace osu.Game.Graphics private class ParticlePart { - private readonly double totalDuration; + private readonly double availableDuration; private double duration; private double direction; private float distance; - public ParticlePart(double totalDuration) + public ParticlePart(double availableDuration) { - this.totalDuration = totalDuration; + this.availableDuration = availableDuration; Randomise(); } + public void Randomise() + { + distance = RNG.NextSingle(0.5f); + duration = RNG.NextDouble(availableDuration / 3, availableDuration); + direction = RNG.NextSingle(0, MathF.PI * 2); + } + + public float AlphaAtTime(double time) => 1 - progressAtTime(time); + public Vector2 PositionAtTime(double time) { - return new Vector2(0.5f) + positionForOffset(distance * (float)(time / duration)); + return new Vector2(0.5f) + positionForOffset(distance * progressAtTime(time)); Vector2 positionForOffset(float offset) => new Vector2( (float)(offset * Math.Sin(direction)), @@ -128,12 +145,7 @@ namespace osu.Game.Graphics ); } - public void Randomise() - { - distance = RNG.NextSingle(0.5f); - duration = RNG.NextDouble(totalDuration / 3, totalDuration); - direction = RNG.NextSingle(0, MathF.PI * 2); - } + private float progressAtTime(double time) => (float)Math.Clamp(time / duration, 0, 1); } } } From 84e73e88d57c3359be76e905860e20f31122291d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 18:05:41 +0900 Subject: [PATCH 066/100] Use structs for parts for added safety --- osu.Game/Graphics/ParticleExplosion.cs | 51 +++++++++++--------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index 68dedf70a0..fa55260f53 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -13,8 +13,12 @@ using osuTK; namespace osu.Game.Graphics { + /// + /// An explosion of textured particles based on how osu-stable randomises the explosion pattern. + /// public class ParticleExplosion : Sprite { + private readonly int particleCount; private readonly double duration; private double startTime; @@ -23,11 +27,9 @@ namespace osu.Game.Graphics public ParticleExplosion(Texture texture, int particleCount, double duration) { Texture = texture; + this.particleCount = particleCount; this.duration = duration; Blending = BlendingParameters.Additive; - - for (int i = 0; i < particleCount; i++) - parts.Add(new ParticlePart(duration)); } protected override void LoadComplete() @@ -36,19 +38,23 @@ namespace osu.Game.Graphics Restart(); } + /// + /// Restart the animation from the current point in time. + /// Supports transform time offset chaining. + /// public void Restart() { startTime = TransformStartTime; this.FadeOutFromOne(duration); - foreach (var p in parts) - p.Randomise(); + parts.Clear(); + for (int i = 0; i < particleCount; i++) + parts.Add(new ParticlePart(duration)); } protected override void Update() { base.Update(); - Invalidate(Invalidation.DrawNode); } @@ -56,7 +62,7 @@ namespace osu.Game.Graphics private class ParticleExplosionDrawNode : SpriteDrawNode { - private List parts = new List(); + private readonly List parts = new List(); private ParticleExplosion source => (ParticleExplosion)Source; @@ -73,9 +79,9 @@ namespace osu.Game.Graphics { base.ApplyState(); - // this is mostly safe as the parts are immutable. - // the most that can go wrong is the random state be incorrect - parts = source.parts; + parts.Clear(); + parts.AddRange(source.parts); + sourceSize = source.Size; startTime = source.startTime; currentTime = source.Time.Current; @@ -111,22 +117,13 @@ namespace osu.Game.Graphics } } - private class ParticlePart + private readonly struct ParticlePart { - private readonly double availableDuration; - - private double duration; - private double direction; - private float distance; + private readonly double duration; + private readonly double direction; + private readonly float distance; public ParticlePart(double availableDuration) - { - this.availableDuration = availableDuration; - - Randomise(); - } - - public void Randomise() { distance = RNG.NextSingle(0.5f); duration = RNG.NextDouble(availableDuration / 3, availableDuration); @@ -137,12 +134,8 @@ namespace osu.Game.Graphics public Vector2 PositionAtTime(double time) { - return new Vector2(0.5f) + positionForOffset(distance * progressAtTime(time)); - - Vector2 positionForOffset(float offset) => new Vector2( - (float)(offset * Math.Sin(direction)), - (float)(offset * Math.Cos(direction)) - ); + var travelledDistance = distance * progressAtTime(time); + return new Vector2(0.5f) + travelledDistance * new Vector2((float)Math.Sin(direction), (float)Math.Cos(direction)); } private float progressAtTime(double time) => (float)Math.Clamp(time / duration, 0, 1); From dd5b90cf6cde83e0b59f282f0bdb1aa75e30a6d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 18:07:41 +0900 Subject: [PATCH 067/100] Add test coverage of animation restarting --- .../Visual/Gameplay/TestSceneParticleExplosion.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs index 63c8757afd..82095cb809 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleExplosion.cs @@ -13,17 +13,26 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneParticleExplosion : OsuTestScene { + private ParticleExplosion explosion; + [BackgroundDependencyLoader] private void load(TextureStore textures) { - AddRepeatStep(@"display", () => + AddStep("create initial", () => { - Child = new ParticleExplosion(textures.Get("Cursor/cursortrail"), 150, 1200) + Child = explosion = new ParticleExplosion(textures.Get("Cursor/cursortrail"), 150, 1200) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(400) }; + }); + + AddWaitStep("wait for playback", 5); + + AddRepeatStep(@"restart animation", () => + { + explosion.Restart(); }, 10); } } From 1c7ee2ca5fdaa87f2ec487bbe92466dce16560b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Nov 2020 18:46:19 +0900 Subject: [PATCH 068/100] Simplify math by making direction a float --- osu.Game/Graphics/ParticleExplosion.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs index fa55260f53..e0d2b50c55 100644 --- a/osu.Game/Graphics/ParticleExplosion.cs +++ b/osu.Game/Graphics/ParticleExplosion.cs @@ -120,7 +120,7 @@ namespace osu.Game.Graphics private readonly struct ParticlePart { private readonly double duration; - private readonly double direction; + private readonly float direction; private readonly float distance; public ParticlePart(double availableDuration) @@ -135,7 +135,7 @@ namespace osu.Game.Graphics public Vector2 PositionAtTime(double time) { var travelledDistance = distance * progressAtTime(time); - return new Vector2(0.5f) + travelledDistance * new Vector2((float)Math.Sin(direction), (float)Math.Cos(direction)); + return new Vector2(0.5f) + travelledDistance * new Vector2(MathF.Sin(direction), MathF.Cos(direction)); } private float progressAtTime(double time) => (float)Math.Clamp(time / duration, 0, 1); From d467a00eeac6f04e6b192c7513538b22e3d1f0d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 00:11:31 +0900 Subject: [PATCH 069/100] Add initial followpoint pooling implementation --- .../TestSceneFollowPoints.cs | 552 +++++++++--------- .../Drawables/Connections/FollowPoint.cs | 5 +- .../Connections/FollowPointConnection.cs | 134 +---- .../Connections/FollowPointRenderer.cs | 167 ++++-- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 +- 5 files changed, 423 insertions(+), 439 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 6c077eb214..3ebb747a21 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -1,276 +1,276 @@ -// 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.Linq; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; -using osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; -using osu.Game.Tests.Visual; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Tests -{ - public class TestSceneFollowPoints : OsuTestScene - { - private Container hitObjectContainer; - private FollowPointRenderer followPointRenderer; - - [SetUp] - public void Setup() => Schedule(() => - { - Children = new Drawable[] - { - hitObjectContainer = new TestHitObjectContainer { RelativeSizeAxes = Axes.Both }, - followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } - }; - }); - - [Test] - public void TestAddObject() - { - addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); - - assertGroups(); - } - - [Test] - public void TestRemoveObject() - { - addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); - - removeObjectStep(() => getObject(0)); - - assertGroups(); - } - - [Test] - public void TestAddMultipleObjects() - { - addMultipleObjectsStep(); - - assertGroups(); - } - - [Test] - public void TestRemoveEndObject() - { - addMultipleObjectsStep(); - - removeObjectStep(() => getObject(4)); - - assertGroups(); - } - - [Test] - public void TestRemoveStartObject() - { - addMultipleObjectsStep(); - - removeObjectStep(() => getObject(0)); - - assertGroups(); - } - - [Test] - public void TestRemoveMiddleObject() - { - addMultipleObjectsStep(); - - removeObjectStep(() => getObject(2)); - - assertGroups(); - } - - [Test] - public void TestMoveObject() - { - addMultipleObjectsStep(); - - AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100)); - - assertGroups(); - } - - [TestCase(0, 0)] // Start -> Start - [TestCase(0, 2)] // Start -> Middle - [TestCase(0, 5)] // Start -> End - [TestCase(2, 0)] // Middle -> Start - [TestCase(1, 3)] // Middle -> Middle (forwards) - [TestCase(3, 1)] // Middle -> Middle (backwards) - [TestCase(4, 0)] // End -> Start - [TestCase(4, 2)] // End -> Middle - [TestCase(4, 4)] // End -> End - public void TestReorderObjects(int startIndex, int endIndex) - { - addMultipleObjectsStep(); - - reorderObjectStep(startIndex, endIndex); - - assertGroups(); - } - - [Test] - public void TestStackedObjects() - { - addObjectsStep(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(300, 100) }, - new HitCircle - { - Position = new Vector2(300, 300), - StackHeight = 20 - }, - }); - - assertDirections(); - } - - private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }); - - private void addObjectsStep(Func ctorFunc) - { - AddStep("add hitobjects", () => - { - var objects = ctorFunc(); - - for (int i = 0; i < objects.Length; i++) - { - objects[i].StartTime = Time.Current + 1000 + 500 * (i + 1); - objects[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - DrawableOsuHitObject drawableObject = null; - - switch (objects[i]) - { - case HitCircle circle: - drawableObject = new DrawableHitCircle(circle); - break; - - case Slider slider: - drawableObject = new DrawableSlider(slider); - break; - - case Spinner spinner: - drawableObject = new DrawableSpinner(spinner); - break; - } - - hitObjectContainer.Add(drawableObject); - followPointRenderer.AddFollowPoints(objects[i]); - } - }); - } - - private void removeObjectStep(Func getFunc) - { - AddStep("remove hitobject", () => - { - var drawableObject = getFunc.Invoke(); - - hitObjectContainer.Remove(drawableObject); - followPointRenderer.RemoveFollowPoints(drawableObject.HitObject); - }); - } - - private void reorderObjectStep(int startIndex, int endIndex) - { - AddStep($"move object {startIndex} to {endIndex}", () => - { - DrawableOsuHitObject toReorder = getObject(startIndex); - - double targetTime; - if (endIndex < hitObjectContainer.Count) - targetTime = getObject(endIndex).HitObject.StartTime - 1; - else - targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1; - - hitObjectContainer.Remove(toReorder); - toReorder.HitObject.StartTime = targetTime; - hitObjectContainer.Add(toReorder); - }); - } - - private void assertGroups() - { - AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count); - AddAssert("group endpoints are correct", () => - { - for (int i = 0; i < hitObjectContainer.Count; i++) - { - DrawableOsuHitObject expectedStart = getObject(i); - DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; - - if (getGroup(i).Start != expectedStart.HitObject) - throw new AssertionException($"Object {i} expected to be the start of group {i}."); - - if (getGroup(i).End != expectedEnd?.HitObject) - throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}."); - } - - return true; - }); - } - - private void assertDirections() - { - AddAssert("group directions are correct", () => - { - for (int i = 0; i < hitObjectContainer.Count; i++) - { - DrawableOsuHitObject expectedStart = getObject(i); - DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; - - if (expectedEnd == null) - continue; - - var points = getGroup(i).ChildrenOfType().ToArray(); - if (points.Length == 0) - continue; - - float expectedDirection = MathF.Atan2(expectedStart.Position.Y - expectedEnd.Position.Y, expectedStart.Position.X - expectedEnd.Position.X); - float realDirection = MathF.Atan2(expectedStart.Position.Y - points[^1].Position.Y, expectedStart.Position.X - points[^1].Position.X); - - if (!Precision.AlmostEquals(expectedDirection, realDirection)) - throw new AssertionException($"Expected group {i} in direction {expectedDirection}, but was {realDirection}."); - } - - return true; - }); - } - - private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; - - private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index]; - - private class TestHitObjectContainer : Container - { - protected override int Compare(Drawable x, Drawable y) - { - var osuX = (DrawableOsuHitObject)x; - var osuY = (DrawableOsuHitObject)y; - - int compare = osuX.HitObject.StartTime.CompareTo(osuY.HitObject.StartTime); - - if (compare == 0) - return base.Compare(x, y); - - return compare; - } - } - } -} +// // 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.Linq; +// using NUnit.Framework; +// using osu.Framework.Graphics; +// using osu.Framework.Graphics.Containers; +// using osu.Framework.Testing; +// using osu.Framework.Utils; +// using osu.Game.Beatmaps; +// using osu.Game.Beatmaps.ControlPoints; +// using osu.Game.Rulesets.Osu.Objects; +// using osu.Game.Rulesets.Osu.Objects.Drawables; +// using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; +// using osu.Game.Tests.Visual; +// using osuTK; +// +// namespace osu.Game.Rulesets.Osu.Tests +// { +// public class TestSceneFollowPoints : OsuTestScene +// { +// private Container hitObjectContainer; +// private FollowPointRenderer followPointRenderer; +// +// [SetUp] +// public void Setup() => Schedule(() => +// { +// Children = new Drawable[] +// { +// hitObjectContainer = new TestHitObjectContainer { RelativeSizeAxes = Axes.Both }, +// followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } +// }; +// }); +// +// [Test] +// public void TestAddObject() +// { +// addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); +// +// assertGroups(); +// } +// +// [Test] +// public void TestRemoveObject() +// { +// addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); +// +// removeObjectStep(() => getObject(0)); +// +// assertGroups(); +// } +// +// [Test] +// public void TestAddMultipleObjects() +// { +// addMultipleObjectsStep(); +// +// assertGroups(); +// } +// +// [Test] +// public void TestRemoveEndObject() +// { +// addMultipleObjectsStep(); +// +// removeObjectStep(() => getObject(4)); +// +// assertGroups(); +// } +// +// [Test] +// public void TestRemoveStartObject() +// { +// addMultipleObjectsStep(); +// +// removeObjectStep(() => getObject(0)); +// +// assertGroups(); +// } +// +// [Test] +// public void TestRemoveMiddleObject() +// { +// addMultipleObjectsStep(); +// +// removeObjectStep(() => getObject(2)); +// +// assertGroups(); +// } +// +// [Test] +// public void TestMoveObject() +// { +// addMultipleObjectsStep(); +// +// AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100)); +// +// assertGroups(); +// } +// +// [TestCase(0, 0)] // Start -> Start +// [TestCase(0, 2)] // Start -> Middle +// [TestCase(0, 5)] // Start -> End +// [TestCase(2, 0)] // Middle -> Start +// [TestCase(1, 3)] // Middle -> Middle (forwards) +// [TestCase(3, 1)] // Middle -> Middle (backwards) +// [TestCase(4, 0)] // End -> Start +// [TestCase(4, 2)] // End -> Middle +// [TestCase(4, 4)] // End -> End +// public void TestReorderObjects(int startIndex, int endIndex) +// { +// addMultipleObjectsStep(); +// +// reorderObjectStep(startIndex, endIndex); +// +// assertGroups(); +// } +// +// [Test] +// public void TestStackedObjects() +// { +// addObjectsStep(() => new OsuHitObject[] +// { +// new HitCircle { Position = new Vector2(300, 100) }, +// new HitCircle +// { +// Position = new Vector2(300, 300), +// StackHeight = 20 +// }, +// }); +// +// assertDirections(); +// } +// +// private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[] +// { +// new HitCircle { Position = new Vector2(100, 100) }, +// new HitCircle { Position = new Vector2(200, 200) }, +// new HitCircle { Position = new Vector2(300, 300) }, +// new HitCircle { Position = new Vector2(400, 400) }, +// new HitCircle { Position = new Vector2(500, 500) }, +// }); +// +// private void addObjectsStep(Func ctorFunc) +// { +// AddStep("add hitobjects", () => +// { +// var objects = ctorFunc(); +// +// for (int i = 0; i < objects.Length; i++) +// { +// objects[i].StartTime = Time.Current + 1000 + 500 * (i + 1); +// objects[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); +// +// DrawableOsuHitObject drawableObject = null; +// +// switch (objects[i]) +// { +// case HitCircle circle: +// drawableObject = new DrawableHitCircle(circle); +// break; +// +// case Slider slider: +// drawableObject = new DrawableSlider(slider); +// break; +// +// case Spinner spinner: +// drawableObject = new DrawableSpinner(spinner); +// break; +// } +// +// hitObjectContainer.Add(drawableObject); +// followPointRenderer.AddFollowPoints(objects[i]); +// } +// }); +// } +// +// private void removeObjectStep(Func getFunc) +// { +// AddStep("remove hitobject", () => +// { +// var drawableObject = getFunc.Invoke(); +// +// hitObjectContainer.Remove(drawableObject); +// followPointRenderer.RemoveFollowPoints(drawableObject.HitObject); +// }); +// } +// +// private void reorderObjectStep(int startIndex, int endIndex) +// { +// AddStep($"move object {startIndex} to {endIndex}", () => +// { +// DrawableOsuHitObject toReorder = getObject(startIndex); +// +// double targetTime; +// if (endIndex < hitObjectContainer.Count) +// targetTime = getObject(endIndex).HitObject.StartTime - 1; +// else +// targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1; +// +// hitObjectContainer.Remove(toReorder); +// toReorder.HitObject.StartTime = targetTime; +// hitObjectContainer.Add(toReorder); +// }); +// } +// +// private void assertGroups() +// { +// AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count); +// AddAssert("group endpoints are correct", () => +// { +// for (int i = 0; i < hitObjectContainer.Count; i++) +// { +// DrawableOsuHitObject expectedStart = getObject(i); +// DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; +// +// if (getGroup(i).Start != expectedStart.HitObject) +// throw new AssertionException($"Object {i} expected to be the start of group {i}."); +// +// if (getGroup(i).End != expectedEnd?.HitObject) +// throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}."); +// } +// +// return true; +// }); +// } +// +// private void assertDirections() +// { +// AddAssert("group directions are correct", () => +// { +// for (int i = 0; i < hitObjectContainer.Count; i++) +// { +// DrawableOsuHitObject expectedStart = getObject(i); +// DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; +// +// if (expectedEnd == null) +// continue; +// +// var points = getGroup(i).ChildrenOfType().ToArray(); +// if (points.Length == 0) +// continue; +// +// float expectedDirection = MathF.Atan2(expectedStart.Position.Y - expectedEnd.Position.Y, expectedStart.Position.X - expectedEnd.Position.X); +// float realDirection = MathF.Atan2(expectedStart.Position.Y - points[^1].Position.Y, expectedStart.Position.X - points[^1].Position.X); +// +// if (!Precision.AlmostEquals(expectedDirection, realDirection)) +// throw new AssertionException($"Expected group {i} in direction {expectedDirection}, but was {realDirection}."); +// } +// +// return true; +// }); +// } +// +// private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; +// +// private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index]; +// +// private class TestHitObjectContainer : Container +// { +// protected override int Compare(Drawable x, Drawable y) +// { +// var osuX = (DrawableOsuHitObject)x; +// var osuY = (DrawableOsuHitObject)y; +// +// int compare = osuX.HitObject.StartTime.CompareTo(osuY.HitObject.StartTime); +// +// if (compare == 0) +// return base.Compare(x, y); +// +// return compare; +// } +// } +// } +// } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index a981648444..3e2ab65bb2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Game.Skinning; @@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// A single follow point positioned between two adjacent s. /// - public class FollowPoint : Container, IAnimationTimeReference + public class FollowPoint : PoolableDrawable, IAnimationTimeReference { private const float width = 8; @@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { Origin = Anchor.Centre; - Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new CircularContainer + InternalChild = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new CircularContainer { Masking = true, AutoSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 3a9e19b361..1d82e91c0e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -2,11 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; -using JetBrains.Annotations; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects; using osuTK; @@ -15,150 +12,77 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// Visualises the s between two s. /// - public class FollowPointConnection : CompositeDrawable + public class FollowPointConnection : PoolableDrawable { // Todo: These shouldn't be constants - private const int spacing = 32; - private const double preempt = 800; + public const int SPACING = 32; + public const double PREEMPT = 800; - public override bool RemoveWhenNotAlive => false; + public FollowPointRenderer.FollowPointLifetimeEntry Entry; + public DrawablePool Pool; - /// - /// The start time of . - /// - public readonly Bindable StartTime = new BindableDouble(); - - /// - /// The which s will exit from. - /// - [NotNull] - public readonly OsuHitObject Start; - - /// - /// Creates a new . - /// - /// The which s will exit from. - public FollowPointConnection([NotNull] OsuHitObject start) + protected override void FreeAfterUse() { - Start = start; - - RelativeSizeAxes = Axes.Both; - - StartTime.BindTo(start.StartTimeBindable); + base.FreeAfterUse(); + ClearInternal(false); } - protected override void LoadComplete() + protected override void PrepareForUse() { - base.LoadComplete(); - bindEvents(Start); - } + base.PrepareForUse(); - private OsuHitObject end; + OsuHitObject start = Entry.Start; + OsuHitObject end = Entry.End; - /// - /// The which s will enter. - /// - [CanBeNull] - public OsuHitObject End - { - get => end; - set - { - end = value; + double startTime = start.GetEndTime(); - if (end != null) - bindEvents(end); - - if (IsLoaded) - scheduleRefresh(); - else - refresh(); - } - } - - private void bindEvents(OsuHitObject obj) - { - obj.PositionBindable.BindValueChanged(_ => scheduleRefresh()); - obj.DefaultsApplied += _ => scheduleRefresh(); - } - - private void scheduleRefresh() - { - Scheduler.AddOnce(refresh); - } - - private void refresh() - { - double startTime = Start.GetEndTime(); - - LifetimeStart = startTime; - - if (End == null || End.NewCombo || Start is Spinner || End is Spinner) - { - // ensure we always set a lifetime for full LifetimeManagementContainer benefits - LifetimeEnd = LifetimeStart; + if (end == null || end.NewCombo || start is Spinner || end is Spinner) return; - } - Vector2 startPosition = Start.StackedEndPosition; - Vector2 endPosition = End.StackedPosition; - double endTime = End.StartTime; + Vector2 startPosition = start.StackedEndPosition; + Vector2 endPosition = end.StackedPosition; + double endTime = end.StartTime; Vector2 distanceVector = endPosition - startPosition; int distance = (int)distanceVector.Length; float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); double duration = endTime - startTime; - double? firstTransformStartTime = null; double finalTransformEndTime = startTime; - int point = 0; - - ClearInternal(); - - for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) + for (int d = (int)(SPACING * 1.5); d < distance - SPACING; d += SPACING) { float fraction = (float)d / distance; Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; Vector2 pointEndPosition = startPosition + fraction * distanceVector; double fadeOutTime = startTime + fraction * duration; - double fadeInTime = fadeOutTime - preempt; + double fadeInTime = fadeOutTime - PREEMPT; FollowPoint fp; - AddInternal(fp = new FollowPoint()); - - Debug.Assert(End != null); + AddInternal(fp = Pool.Get()); + fp.ClearTransforms(); fp.Position = pointStartPosition; fp.Rotation = rotation; fp.Alpha = 0; - fp.Scale = new Vector2(1.5f * End.Scale); - - firstTransformStartTime ??= fadeInTime; + fp.Scale = new Vector2(1.5f * end.Scale); fp.AnimationStartTime = fadeInTime; using (fp.BeginAbsoluteSequence(fadeInTime)) { - fp.FadeIn(End.TimeFadeIn); - fp.ScaleTo(End.Scale, End.TimeFadeIn, Easing.Out); - fp.MoveTo(pointEndPosition, End.TimeFadeIn, Easing.Out); - fp.Delay(fadeOutTime - fadeInTime).FadeOut(End.TimeFadeIn); + fp.FadeIn(end.TimeFadeIn); + fp.ScaleTo(end.Scale, end.TimeFadeIn, Easing.Out); + fp.MoveTo(pointEndPosition, end.TimeFadeIn, Easing.Out); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn); - finalTransformEndTime = fadeOutTime + End.TimeFadeIn; + finalTransformEndTime = fadeOutTime + end.TimeFadeIn; } - - point++; } - int excessPoints = InternalChildren.Count - point; - for (int i = 0; i < excessPoints; i++) - RemoveInternal(InternalChildren[^1]); - // todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed. - LifetimeStart = firstTransformStartTime ?? startTime; - LifetimeEnd = finalTransformEndTime; + Entry.LifetimeEnd = finalTransformEndTime; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index be1392d7c3..ac7b78a25b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -2,53 +2,57 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; +using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Objects; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { /// /// Visualises connections between s. /// - public class FollowPointRenderer : LifetimeManagementContainer + public class FollowPointRenderer : CompositeDrawable { - /// - /// All the s contained by this . - /// - internal IReadOnlyList Connections => connections; - - private readonly List connections = new List(); - public override bool RemoveCompletedTransforms => false; - /// - /// Adds the s around an . - /// This includes s leading into , and s exiting . - /// - /// The to add s for. - public void AddFollowPoints(OsuHitObject hitObject) - => addConnection(new FollowPointConnection(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g)))); + private DrawablePool connectionPool; + private DrawablePool pointPool; - /// - /// Removes the s around an . - /// This includes s leading into , and s exiting . - /// - /// The to remove s for. - public void RemoveFollowPoints(OsuHitObject hitObject) => removeGroup(connections.Single(g => g.Start == hitObject)); + private readonly List lifetimeEntries = new List(); + private readonly Dictionary connectionsInUse = new Dictionary(); + private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); - /// - /// Adds a to this . - /// - /// The to add. - /// The index of in . - private void addConnection(FollowPointConnection connection) + public FollowPointRenderer() { - // Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections - int index = connections.AddInPlace(connection, Comparer.Create((g1, g2) => + lifetimeManager.EntryBecameAlive += onEntryBecameAlive; + lifetimeManager.EntryBecameDead += onEntryBecameDead; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] { - int comp = g1.StartTime.Value.CompareTo(g2.StartTime.Value); + connectionPool = new DrawablePool(1, 200), + pointPool = new DrawablePool(50, 1000) + }; + + MakeChildAlive(connectionPool); + MakeChildAlive(pointPool); + } + + public void AddFollowPoints2(OsuHitObject hitObject) + { + var newEntry = new FollowPointLifetimeEntry(hitObject); + + var index = lifetimeEntries.AddInPlace(newEntry, Comparer.Create((e1, e2) => + { + int comp = e1.Start.StartTime.CompareTo(e2.Start.StartTime); if (comp != 0) return comp; @@ -61,19 +65,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections return -1; })); - if (index < connections.Count - 1) + if (index < lifetimeEntries.Count - 1) { // Update the connection's end point to the next connection's start point // h1 -> -> -> h2 // connection nextGroup - FollowPointConnection nextConnection = connections[index + 1]; - connection.End = nextConnection.Start; + FollowPointLifetimeEntry nextEntry = lifetimeEntries[index + 1]; + newEntry.End = nextEntry.Start; } else { // The end point may be non-null during re-ordering - connection.End = null; + newEntry.End = null; } if (index > 0) @@ -82,23 +86,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections // h1 -> -> -> h2 // prevGroup connection - FollowPointConnection previousConnection = connections[index - 1]; - previousConnection.End = connection.Start; + FollowPointLifetimeEntry previousEntry = lifetimeEntries[index - 1]; + previousEntry.End = newEntry.Start; } - AddInternal(connection); + lifetimeManager.AddEntry(newEntry); } - /// - /// Removes a from this . - /// - /// The to remove. - /// Whether was removed. - private void removeGroup(FollowPointConnection connection) + public void RemoveFollowPoints2(OsuHitObject hitObject) { - RemoveInternal(connection); + int index = lifetimeEntries.FindIndex(e => e.Start == hitObject); + var entry = lifetimeEntries[index]; - int index = connections.IndexOf(connection); + lifetimeEntries.RemoveAt(index); + lifetimeManager.RemoveEntry(entry); if (index > 0) { @@ -106,18 +107,76 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections // h1 -> -> -> h2 -> -> -> h3 // prevGroup connection nextGroup // The current connection's end point is used since there may not be a next connection - FollowPointConnection previousConnection = connections[index - 1]; - previousConnection.End = connection.End; + FollowPointLifetimeEntry previousEntry = lifetimeEntries[index - 1]; + previousEntry.End = entry.End; } - - connections.Remove(connection); } - private void onStartTimeChanged(FollowPointConnection connection) + protected override bool CheckChildrenLife() => lifetimeManager.Update(Time.Current); + + private void onEntryBecameAlive(LifetimeEntry entry) { - // Naive but can be improved if performance becomes an issue - removeGroup(connection); - addConnection(connection); + var connection = connectionPool.Get(c => + { + c.Entry = (FollowPointLifetimeEntry)entry; + c.Pool = pointPool; + }); + + connectionsInUse[entry] = connection; + + AddInternal(connection); + MakeChildAlive(connection); + } + + private void onEntryBecameDead(LifetimeEntry entry) + { + RemoveInternal(connectionsInUse[entry]); + connectionsInUse.Remove(entry); + } + + public class FollowPointLifetimeEntry : LifetimeEntry + { + public readonly OsuHitObject Start; + + public FollowPointLifetimeEntry(OsuHitObject start) + { + Start = start; + + LifetimeStart = LifetimeEnd = Start.StartTime; + } + + private OsuHitObject end; + + public OsuHitObject End + { + get => end; + set + { + end = value; + computeLifetimes(); + } + } + + private void computeLifetimes() + { + if (end == null) + { + LifetimeEnd = LifetimeStart; + return; + } + + Vector2 startPosition = Start.StackedEndPosition; + Vector2 endPosition = End.StackedPosition; + Vector2 distanceVector = endPosition - startPosition; + float fraction = (int)(FollowPointConnection.SPACING * 1.5) / distanceVector.Length; + + double duration = End.StartTime - Start.GetEndTime(); + + double fadeOutTime = Start.StartTime + fraction * duration; + double fadeInTime = fadeOutTime - FollowPointConnection.PREEMPT; + + LifetimeStart = fadeInTime; + } } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index c816502d61..a8d9423bf6 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -125,13 +125,13 @@ namespace osu.Game.Rulesets.Osu.UI protected override void OnHitObjectAdded(HitObject hitObject) { base.OnHitObjectAdded(hitObject); - followPoints.AddFollowPoints((OsuHitObject)hitObject); + followPoints.AddFollowPoints2((OsuHitObject)hitObject); } protected override void OnHitObjectRemoved(HitObject hitObject) { base.OnHitObjectRemoved(hitObject); - followPoints.RemoveFollowPoints((OsuHitObject)hitObject); + followPoints.RemoveFollowPoints2((OsuHitObject)hitObject); } public void OnHitObjectLoaded(Drawable drawable) From 6356b2dde93c72b8fe4ee891b7cc649f3cca96ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 19 Nov 2020 22:11:52 +0100 Subject: [PATCH 070/100] Prevent editor from crashing for rulesets with no compose screen implementation --- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index d9948aa23c..46d5eb40b4 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -32,7 +32,8 @@ namespace osu.Game.Screens.Edit.Compose composer = ruleset?.CreateHitObjectComposer(); // make the composer available to the timeline and other components in this screen. - dependencies.CacheAs(composer); + if (composer != null) + dependencies.CacheAs(composer); return dependencies; } From 1df3f88fc44275ee1ae1bfe3404259557c5d6b60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 12:32:17 +0900 Subject: [PATCH 071/100] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 4657896fac..6dab6edc5e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 704ac5a611..54f3fcede6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 346bd892b0..692dac909a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - + From b547abafb2f4bd702b7448d7556485ba76e536b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 13:46:21 +0900 Subject: [PATCH 072/100] Fix slider right click context menus not being shown outside the valid playfield area Closes #10816. --- .../Sliders/Components/PathControlPointVisualiser.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 7375c0e981..ce5dc4855e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -20,12 +20,15 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; +using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield. + internal readonly Container Pieces; internal readonly Container Connections; From a3145ed96dfbd45ff58fdd744e3490528ce00946 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 13:54:41 +0900 Subject: [PATCH 073/100] Fix test compile errors --- .../TestSceneFollowPoints.cs | 561 +++++++++--------- .../Connections/FollowPointRenderer.cs | 2 + 2 files changed, 287 insertions(+), 276 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 3ebb747a21..ef6275a7e7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -1,276 +1,285 @@ -// // 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.Linq; -// using NUnit.Framework; -// using osu.Framework.Graphics; -// using osu.Framework.Graphics.Containers; -// using osu.Framework.Testing; -// using osu.Framework.Utils; -// using osu.Game.Beatmaps; -// using osu.Game.Beatmaps.ControlPoints; -// using osu.Game.Rulesets.Osu.Objects; -// using osu.Game.Rulesets.Osu.Objects.Drawables; -// using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; -// using osu.Game.Tests.Visual; -// using osuTK; -// -// namespace osu.Game.Rulesets.Osu.Tests -// { -// public class TestSceneFollowPoints : OsuTestScene -// { -// private Container hitObjectContainer; -// private FollowPointRenderer followPointRenderer; -// -// [SetUp] -// public void Setup() => Schedule(() => -// { -// Children = new Drawable[] -// { -// hitObjectContainer = new TestHitObjectContainer { RelativeSizeAxes = Axes.Both }, -// followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } -// }; -// }); -// -// [Test] -// public void TestAddObject() -// { -// addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); -// -// assertGroups(); -// } -// -// [Test] -// public void TestRemoveObject() -// { -// addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); -// -// removeObjectStep(() => getObject(0)); -// -// assertGroups(); -// } -// -// [Test] -// public void TestAddMultipleObjects() -// { -// addMultipleObjectsStep(); -// -// assertGroups(); -// } -// -// [Test] -// public void TestRemoveEndObject() -// { -// addMultipleObjectsStep(); -// -// removeObjectStep(() => getObject(4)); -// -// assertGroups(); -// } -// -// [Test] -// public void TestRemoveStartObject() -// { -// addMultipleObjectsStep(); -// -// removeObjectStep(() => getObject(0)); -// -// assertGroups(); -// } -// -// [Test] -// public void TestRemoveMiddleObject() -// { -// addMultipleObjectsStep(); -// -// removeObjectStep(() => getObject(2)); -// -// assertGroups(); -// } -// -// [Test] -// public void TestMoveObject() -// { -// addMultipleObjectsStep(); -// -// AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100)); -// -// assertGroups(); -// } -// -// [TestCase(0, 0)] // Start -> Start -// [TestCase(0, 2)] // Start -> Middle -// [TestCase(0, 5)] // Start -> End -// [TestCase(2, 0)] // Middle -> Start -// [TestCase(1, 3)] // Middle -> Middle (forwards) -// [TestCase(3, 1)] // Middle -> Middle (backwards) -// [TestCase(4, 0)] // End -> Start -// [TestCase(4, 2)] // End -> Middle -// [TestCase(4, 4)] // End -> End -// public void TestReorderObjects(int startIndex, int endIndex) -// { -// addMultipleObjectsStep(); -// -// reorderObjectStep(startIndex, endIndex); -// -// assertGroups(); -// } -// -// [Test] -// public void TestStackedObjects() -// { -// addObjectsStep(() => new OsuHitObject[] -// { -// new HitCircle { Position = new Vector2(300, 100) }, -// new HitCircle -// { -// Position = new Vector2(300, 300), -// StackHeight = 20 -// }, -// }); -// -// assertDirections(); -// } -// -// private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[] -// { -// new HitCircle { Position = new Vector2(100, 100) }, -// new HitCircle { Position = new Vector2(200, 200) }, -// new HitCircle { Position = new Vector2(300, 300) }, -// new HitCircle { Position = new Vector2(400, 400) }, -// new HitCircle { Position = new Vector2(500, 500) }, -// }); -// -// private void addObjectsStep(Func ctorFunc) -// { -// AddStep("add hitobjects", () => -// { -// var objects = ctorFunc(); -// -// for (int i = 0; i < objects.Length; i++) -// { -// objects[i].StartTime = Time.Current + 1000 + 500 * (i + 1); -// objects[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); -// -// DrawableOsuHitObject drawableObject = null; -// -// switch (objects[i]) -// { -// case HitCircle circle: -// drawableObject = new DrawableHitCircle(circle); -// break; -// -// case Slider slider: -// drawableObject = new DrawableSlider(slider); -// break; -// -// case Spinner spinner: -// drawableObject = new DrawableSpinner(spinner); -// break; -// } -// -// hitObjectContainer.Add(drawableObject); -// followPointRenderer.AddFollowPoints(objects[i]); -// } -// }); -// } -// -// private void removeObjectStep(Func getFunc) -// { -// AddStep("remove hitobject", () => -// { -// var drawableObject = getFunc.Invoke(); -// -// hitObjectContainer.Remove(drawableObject); -// followPointRenderer.RemoveFollowPoints(drawableObject.HitObject); -// }); -// } -// -// private void reorderObjectStep(int startIndex, int endIndex) -// { -// AddStep($"move object {startIndex} to {endIndex}", () => -// { -// DrawableOsuHitObject toReorder = getObject(startIndex); -// -// double targetTime; -// if (endIndex < hitObjectContainer.Count) -// targetTime = getObject(endIndex).HitObject.StartTime - 1; -// else -// targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1; -// -// hitObjectContainer.Remove(toReorder); -// toReorder.HitObject.StartTime = targetTime; -// hitObjectContainer.Add(toReorder); -// }); -// } -// -// private void assertGroups() -// { -// AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count); -// AddAssert("group endpoints are correct", () => -// { -// for (int i = 0; i < hitObjectContainer.Count; i++) -// { -// DrawableOsuHitObject expectedStart = getObject(i); -// DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; -// -// if (getGroup(i).Start != expectedStart.HitObject) -// throw new AssertionException($"Object {i} expected to be the start of group {i}."); -// -// if (getGroup(i).End != expectedEnd?.HitObject) -// throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}."); -// } -// -// return true; -// }); -// } -// -// private void assertDirections() -// { -// AddAssert("group directions are correct", () => -// { -// for (int i = 0; i < hitObjectContainer.Count; i++) -// { -// DrawableOsuHitObject expectedStart = getObject(i); -// DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; -// -// if (expectedEnd == null) -// continue; -// -// var points = getGroup(i).ChildrenOfType().ToArray(); -// if (points.Length == 0) -// continue; -// -// float expectedDirection = MathF.Atan2(expectedStart.Position.Y - expectedEnd.Position.Y, expectedStart.Position.X - expectedEnd.Position.X); -// float realDirection = MathF.Atan2(expectedStart.Position.Y - points[^1].Position.Y, expectedStart.Position.X - points[^1].Position.X); -// -// if (!Precision.AlmostEquals(expectedDirection, realDirection)) -// throw new AssertionException($"Expected group {i} in direction {expectedDirection}, but was {realDirection}."); -// } -// -// return true; -// }); -// } -// -// private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; -// -// private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index]; -// -// private class TestHitObjectContainer : Container -// { -// protected override int Compare(Drawable x, Drawable y) -// { -// var osuX = (DrawableOsuHitObject)x; -// var osuY = (DrawableOsuHitObject)y; -// -// int compare = osuX.HitObject.StartTime.CompareTo(osuY.HitObject.StartTime); -// -// if (compare == 0) -// return base.Compare(x, y); -// -// return compare; -// } -// } -// } -// } +// 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.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneFollowPoints : OsuTestScene + { + private Container hitObjectContainer; + private FollowPointRenderer followPointRenderer; + + [SetUp] + public void Setup() => Schedule(() => + { + Children = new Drawable[] + { + hitObjectContainer = new TestHitObjectContainer { RelativeSizeAxes = Axes.Both }, + followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } + }; + }); + + [Test] + public void TestAddObject() + { + addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); + + assertGroups(); + } + + [Test] + public void TestRemoveObject() + { + addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); + + removeObjectStep(() => getObject(0)); + + assertGroups(); + } + + [Test] + public void TestAddMultipleObjects() + { + addMultipleObjectsStep(); + + assertGroups(); + } + + [Test] + public void TestRemoveEndObject() + { + addMultipleObjectsStep(); + + removeObjectStep(() => getObject(4)); + + assertGroups(); + } + + [Test] + public void TestRemoveStartObject() + { + addMultipleObjectsStep(); + + removeObjectStep(() => getObject(0)); + + assertGroups(); + } + + [Test] + public void TestRemoveMiddleObject() + { + addMultipleObjectsStep(); + + removeObjectStep(() => getObject(2)); + + assertGroups(); + } + + [Test] + public void TestMoveObject() + { + addMultipleObjectsStep(); + + AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100)); + + assertGroups(); + } + + [TestCase(0, 0)] // Start -> Start + [TestCase(0, 2)] // Start -> Middle + [TestCase(0, 5)] // Start -> End + [TestCase(2, 0)] // Middle -> Start + [TestCase(1, 3)] // Middle -> Middle (forwards) + [TestCase(3, 1)] // Middle -> Middle (backwards) + [TestCase(4, 0)] // End -> Start + [TestCase(4, 2)] // End -> Middle + [TestCase(4, 4)] // End -> End + public void TestReorderObjects(int startIndex, int endIndex) + { + addMultipleObjectsStep(); + + reorderObjectStep(startIndex, endIndex); + + assertGroups(); + } + + [Test] + public void TestStackedObjects() + { + addObjectsStep(() => new OsuHitObject[] + { + new HitCircle { Position = new Vector2(300, 100) }, + new HitCircle + { + Position = new Vector2(300, 300), + StackHeight = 20 + }, + }); + + assertDirections(); + } + + private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }); + + private void addObjectsStep(Func ctorFunc) + { + AddStep("add hitobjects", () => + { + var objects = ctorFunc(); + + for (int i = 0; i < objects.Length; i++) + { + objects[i].StartTime = Time.Current + 1000 + 500 * (i + 1); + objects[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + DrawableOsuHitObject drawableObject = null; + + switch (objects[i]) + { + case HitCircle circle: + drawableObject = new DrawableHitCircle(circle); + break; + + case Slider slider: + drawableObject = new DrawableSlider(slider); + break; + + case Spinner spinner: + drawableObject = new DrawableSpinner(spinner); + break; + } + + hitObjectContainer.Add(drawableObject); + followPointRenderer.AddFollowPoints2(objects[i]); + } + }); + } + + private void removeObjectStep(Func getFunc) + { + AddStep("remove hitobject", () => + { + var drawableObject = getFunc.Invoke(); + + hitObjectContainer.Remove(drawableObject); + followPointRenderer.RemoveFollowPoints2(drawableObject.HitObject); + }); + } + + private void reorderObjectStep(int startIndex, int endIndex) + { + AddStep($"move object {startIndex} to {endIndex}", () => + { + DrawableOsuHitObject toReorder = getObject(startIndex); + + double targetTime; + if (endIndex < hitObjectContainer.Count) + targetTime = getObject(endIndex).HitObject.StartTime - 1; + else + targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1; + + hitObjectContainer.Remove(toReorder); + toReorder.HitObject.StartTime = targetTime; + hitObjectContainer.Add(toReorder); + }); + } + + private void assertGroups() + { + AddAssert("has correct group count", () => followPointRenderer.Entries.Count == hitObjectContainer.Count); + AddAssert("group endpoints are correct", () => + { + for (int i = 0; i < hitObjectContainer.Count; i++) + { + DrawableOsuHitObject expectedStart = getObject(i); + DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; + + if (getEntry(i).Start != expectedStart.HitObject) + throw new AssertionException($"Object {i} expected to be the start of group {i}."); + + if (getEntry(i).End != expectedEnd?.HitObject) + throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}."); + } + + return true; + }); + } + + private void assertDirections() + { + AddAssert("group directions are correct", () => + { + for (int i = 0; i < hitObjectContainer.Count; i++) + { + DrawableOsuHitObject expectedStart = getObject(i); + DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; + + if (expectedEnd == null) + continue; + + var manualClock = new ManualClock(); + followPointRenderer.Clock = new FramedClock(manualClock); + + manualClock.CurrentTime = expectedStart.HitObject.StartTime; + followPointRenderer.UpdateSubTree(); + + var points = getGroup(i).ChildrenOfType().ToArray(); + if (points.Length == 0) + continue; + + float expectedDirection = MathF.Atan2(expectedStart.Position.Y - expectedEnd.Position.Y, expectedStart.Position.X - expectedEnd.Position.X); + float realDirection = MathF.Atan2(expectedStart.Position.Y - points[^1].Position.Y, expectedStart.Position.X - points[^1].Position.X); + + if (!Precision.AlmostEquals(expectedDirection, realDirection)) + throw new AssertionException($"Expected group {i} in direction {expectedDirection}, but was {realDirection}."); + } + + return true; + }); + } + + private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; + + private FollowPointRenderer.FollowPointLifetimeEntry getEntry(int index) => followPointRenderer.Entries[index]; + + private FollowPointConnection getGroup(int index) => followPointRenderer.ChildrenOfType().Single(c => c.Entry == getEntry(index)); + + private class TestHitObjectContainer : Container + { + protected override int Compare(Drawable x, Drawable y) + { + var osuX = (DrawableOsuHitObject)x; + var osuY = (DrawableOsuHitObject)y; + + int compare = osuX.HitObject.StartTime.CompareTo(osuY.HitObject.StartTime); + + if (compare == 0) + return base.Compare(x, y); + + return compare; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index ac7b78a25b..702983b74a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public override bool RemoveCompletedTransforms => false; + public IReadOnlyList Entries => lifetimeEntries; + private DrawablePool connectionPool; private DrawablePool pointPool; From 17ff7fe163499324d044cac56267c4b852c56261 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 13:55:01 +0900 Subject: [PATCH 074/100] Fix failing test due to early lifetime end --- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 702983b74a..6751585def 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -143,8 +143,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public FollowPointLifetimeEntry(OsuHitObject start) { Start = start; - - LifetimeStart = LifetimeEnd = Start.StartTime; + LifetimeStart = Start.StartTime; } private OsuHitObject end; @@ -178,6 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections double fadeInTime = fadeOutTime - FollowPointConnection.PREEMPT; LifetimeStart = fadeInTime; + LifetimeEnd = double.MaxValue; // This will be set by the connection. } } } From 2fc53a278daa7ffd27b8ba8affdb6ed1d71f226d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 14:10:28 +0900 Subject: [PATCH 075/100] Add back reorder support --- .../Connections/FollowPointRenderer.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 6751585def..6bb32238bd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -27,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private readonly List lifetimeEntries = new List(); private readonly Dictionary connectionsInUse = new Dictionary(); + private readonly Dictionary startTimeMap = new Dictionary(); private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); public FollowPointRenderer() @@ -49,6 +51,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } public void AddFollowPoints2(OsuHitObject hitObject) + { + addEntry(hitObject); + + var startTimeBindable = hitObject.StartTimeBindable.GetBoundCopy(); + startTimeBindable.ValueChanged += _ => onStartTimeChanged(hitObject); + startTimeMap[hitObject] = startTimeBindable; + } + + public void RemoveFollowPoints2(OsuHitObject hitObject) + { + removeEntry(hitObject); + + startTimeMap[hitObject].UnbindAll(); + startTimeMap.Remove(hitObject); + } + + private void addEntry(OsuHitObject hitObject) { var newEntry = new FollowPointLifetimeEntry(hitObject); @@ -95,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections lifetimeManager.AddEntry(newEntry); } - public void RemoveFollowPoints2(OsuHitObject hitObject) + private void removeEntry(OsuHitObject hitObject) { int index = lifetimeEntries.FindIndex(e => e.Start == hitObject); var entry = lifetimeEntries[index]; @@ -136,6 +155,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections connectionsInUse.Remove(entry); } + private void onStartTimeChanged(OsuHitObject hitObject) + { + removeEntry(hitObject); + addEntry(hitObject); + } + public class FollowPointLifetimeEntry : LifetimeEntry { public readonly OsuHitObject Start; From c53a8fafe6d648c1dfdc4be449e35a3d2c11bc88 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 14:25:07 +0900 Subject: [PATCH 076/100] Make test fail as expected --- osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index ef6275a7e7..a6a7f54886 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -95,9 +95,19 @@ namespace osu.Game.Rulesets.Osu.Tests { addMultipleObjectsStep(); - AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100)); + AddStep("move hitobject", () => + { + var manualClock = new ManualClock(); + followPointRenderer.Clock = new FramedClock(manualClock); + + manualClock.CurrentTime = getObject(1).HitObject.StartTime; + followPointRenderer.UpdateSubTree(); + + getObject(2).HitObject.Position = new Vector2(300, 100); + }); assertGroups(); + assertDirections(); } [TestCase(0, 0)] // Start -> Start From 8c32d3f78194b75deb5d28b390a9f14272f557c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 14:32:23 +0900 Subject: [PATCH 077/100] Don't play flair animations / sounds when watching autoplay or viewing a result directly I think this is a pretty good place to be for now. The flair will play if you just watched a play (local, replay or spectator) but will not play if you are coming from song select (viewing a replay's result screen from the leaderboard) or in the case of autoplay. Closes #10762. --- osu.Game/Screens/Ranking/ResultsScreen.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index f8bdf0140c..ce3e618889 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Online.API; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Play; @@ -149,7 +150,12 @@ namespace osu.Game.Screens.Ranking }; if (Score != null) - ScorePanelList.AddScore(Score, true); + { + // only show flair / animation when arriving after watching a play that isn't autoplay. + bool shouldFlair = player != null && !Score.Mods.Any(m => m is ModAutoplay); + + ScorePanelList.AddScore(Score, shouldFlair); + } if (player != null && allowRetry) { From 2db42f8e6780eb2fd67854b54413ce8506a2895a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 14:35:44 +0900 Subject: [PATCH 078/100] Remove default allowRetry parameter value from ResultsScreen --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 2 +- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs | 2 +- osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs | 2 +- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/SpectatorResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 2 +- osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index ff96a999ec..44a2056732 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -326,7 +326,7 @@ namespace osu.Game.Tests.Visual.Ranking public HotkeyRetryOverlay RetryOverlay; public UnrankedSoloResultsScreen(ScoreInfo score) - : base(score) + : base(score, false) { Score.Beatmap.OnlineBeatmapID = 0; Score.Beatmap.Status = BeatmapSetOnlineStatus.Pending; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index cb0e2cfa8e..bb638bcf3a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -420,7 +420,7 @@ namespace osu.Game break; case ScorePresentType.Results: - screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo)); + screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo, false)); break; } }, validScreens: new[] { typeof(PlaySongSelect) }); diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index 04da943a10..0efa9c5196 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Multi.Play protected override ResultsScreen CreateResults(ScoreInfo score) { Debug.Assert(roomId.Value != null); - return new TimeshiftResultsScreen(score, roomId.Value.Value, playlistItem); + return new TimeshiftResultsScreen(score, roomId.Value.Value, playlistItem, true); } protected override ScoreInfo CreateScore() diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 8da6a530a8..3623208fa7 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Multi.Ranking [Resolved] private IAPIProvider api { get; set; } - public TimeshiftResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry = true) + public TimeshiftResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry) : base(score, allowRetry) { this.roomId = roomId; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b94f0a5062..d0a83e3c22 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -545,7 +545,7 @@ namespace osu.Game.Screens.Play protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; - protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score); + protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, true); #region Fail Logic diff --git a/osu.Game/Screens/Play/SpectatorResultsScreen.cs b/osu.Game/Screens/Play/SpectatorResultsScreen.cs index 56ccfd2253..dabdf0a139 100644 --- a/osu.Game/Screens/Play/SpectatorResultsScreen.cs +++ b/osu.Game/Screens/Play/SpectatorResultsScreen.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Play public class SpectatorResultsScreen : SoloResultsScreen { public SpectatorResultsScreen(ScoreInfo score) - : base(score) + : base(score, false) { } diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 9cf2e6757a..76b549da1a 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Ranking [Resolved] private RulesetStore rulesets { get; set; } - public SoloResultsScreen(ScoreInfo score, bool allowRetry = true) + public SoloResultsScreen(ScoreInfo score, bool allowRetry) : base(score, allowRetry) { } diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index ee8825640c..50a61ed4c2 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Select } protected void PresentScore(ScoreInfo score) => - FinaliseSelection(score.Beatmap, score.Ruleset, () => this.Push(new SoloResultsScreen(score))); + FinaliseSelection(score.Beatmap, score.Ruleset, () => this.Push(new SoloResultsScreen(score, false))); protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); From af67b4a346a7147a6132415955b3f25cc99accf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 14:57:08 +0900 Subject: [PATCH 079/100] Remove no longer necessary code from OsuPlayfield --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index c816502d61..ab77be488e 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; @@ -40,8 +39,6 @@ namespace osu.Game.Rulesets.Osu.UI protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); - private readonly Bindable playfieldBorderStyle = new BindableBool(); - private readonly IDictionary> poolDictionary = new Dictionary>(); public OsuPlayfield() @@ -67,12 +64,7 @@ namespace osu.Game.Rulesets.Osu.UI RelativeSizeAxes = Axes.Both, Depth = 1, }, - // Todo: This should not exist, but currently helps to reduce LOH allocations due to unbinding skin source events on judgement disposal - // Todo: Remove when hitobjects are properly pooled - new SkinProvidingContainer(null) - { - Child = HitObjectContainer, - }, + HitObjectContainer, approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both, From 45b1fcf26fcbc3ff9befe6637a538978a3e5d01d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 15:01:37 +0900 Subject: [PATCH 080/100] Remove unnecessary using statement --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index ab77be488e..6a59cdf112 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -19,7 +19,6 @@ using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; -using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Osu.UI From 185653b1d8b65ac4d0ce7b0ab4fe25a91b00d22b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 15:11:24 +0900 Subject: [PATCH 081/100] Remove depth specifications --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 29 ++++-------------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 6a59cdf112..0e98a1d439 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -44,31 +44,12 @@ namespace osu.Game.Rulesets.Osu.UI { InternalChildren = new Drawable[] { - playfieldBorder = new PlayfieldBorder - { - RelativeSizeAxes = Axes.Both, - Depth = 3 - }, - spinnerProxies = new ProxyContainer - { - RelativeSizeAxes = Axes.Both - }, - followPoints = new FollowPointRenderer - { - RelativeSizeAxes = Axes.Both, - Depth = 2, - }, - judgementLayer = new JudgementContainer - { - RelativeSizeAxes = Axes.Both, - Depth = 1, - }, + playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both }, + spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both }, + followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, + judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, HitObjectContainer, - approachCircles = new ProxyContainer - { - RelativeSizeAxes = Axes.Both, - Depth = -1, - }, + approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; hitPolicy = new OrderedHitPolicy(HitObjectContainer); From 33eea64cfc584bcd691ca3e200ad4c79f06a5956 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 15:31:04 +0900 Subject: [PATCH 082/100] Fix follow points not updating on positional changes --- .../Connections/FollowPointConnection.cs | 29 +++++++-- .../Connections/FollowPointRenderer.cs | 60 ++++++++++++++++++- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 1d82e91c0e..fe35cd02dc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -21,16 +21,33 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public FollowPointRenderer.FollowPointLifetimeEntry Entry; public DrawablePool Pool; - protected override void FreeAfterUse() - { - base.FreeAfterUse(); - ClearInternal(false); - } - protected override void PrepareForUse() { base.PrepareForUse(); + Entry.Invalidated += onEntryInvalidated; + + refreshPoints(); + } + + protected override void FreeAfterUse() + { + base.FreeAfterUse(); + + Entry.Invalidated -= onEntryInvalidated; + + // Return points to the pool. + ClearInternal(false); + + Entry = null; + } + + private void onEntryInvalidated() => refreshPoints(); + + private void refreshPoints() + { + ClearInternal(false); + OsuHitObject start = Entry.Start; OsuHitObject end = Entry.End; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 6bb32238bd..205c5c0d05 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.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 osu.Framework.Allocation; using osu.Framework.Bindables; @@ -117,7 +118,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private void removeEntry(OsuHitObject hitObject) { int index = lifetimeEntries.FindIndex(e => e.Start == hitObject); + var entry = lifetimeEntries[index]; + entry.UnbindEvents(); lifetimeEntries.RemoveAt(index); lifetimeManager.RemoveEntry(entry); @@ -161,14 +164,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections addEntry(hitObject); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + foreach (var entry in lifetimeEntries) + entry.UnbindEvents(); + lifetimeEntries.Clear(); + } + public class FollowPointLifetimeEntry : LifetimeEntry { + public event Action Invalidated; public readonly OsuHitObject Start; public FollowPointLifetimeEntry(OsuHitObject start) { Start = start; LifetimeStart = Start.StartTime; + + bindEvents(); } private OsuHitObject end; @@ -178,12 +193,51 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections get => end; set { + UnbindEvents(); + end = value; - computeLifetimes(); + + bindEvents(); + + refreshLifetimes(); } } - private void computeLifetimes() + private void bindEvents() + { + UnbindEvents(); + + // Note: Positions are bound for instantaneous feedback from positional changes from the editor, before ApplyDefaults() is called on hitobjects. + Start.DefaultsApplied += onDefaultsApplied; + Start.PositionBindable.ValueChanged += onPositionChanged; + + if (End != null) + { + End.DefaultsApplied += onDefaultsApplied; + End.PositionBindable.ValueChanged += onPositionChanged; + } + } + + public void UnbindEvents() + { + if (Start != null) + { + Start.DefaultsApplied -= onDefaultsApplied; + Start.PositionBindable.ValueChanged -= onPositionChanged; + } + + if (End != null) + { + End.DefaultsApplied -= onDefaultsApplied; + End.PositionBindable.ValueChanged -= onPositionChanged; + } + } + + private void onDefaultsApplied(HitObject obj) => refreshLifetimes(); + + private void onPositionChanged(ValueChangedEvent obj) => refreshLifetimes(); + + private void refreshLifetimes() { if (end == null) { @@ -203,6 +257,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections LifetimeStart = fadeInTime; LifetimeEnd = double.MaxValue; // This will be set by the connection. + + Invalidated?.Invoke(); } } } From 2ed2ddfe8a05f24bcc60f84f0e6eb9b96aaf0bd7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 15:39:25 +0900 Subject: [PATCH 083/100] Rename methods --- osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs | 4 ++-- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 4 ++-- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index a6a7f54886..e7c73b941d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Tests } hitObjectContainer.Add(drawableObject); - followPointRenderer.AddFollowPoints2(objects[i]); + followPointRenderer.AddFollowPoints(objects[i]); } }); } @@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Tests var drawableObject = getFunc.Invoke(); hitObjectContainer.Remove(drawableObject); - followPointRenderer.RemoveFollowPoints2(drawableObject.HitObject); + followPointRenderer.RemoveFollowPoints(drawableObject.HitObject); }); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 205c5c0d05..6dea16d01f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections MakeChildAlive(pointPool); } - public void AddFollowPoints2(OsuHitObject hitObject) + public void AddFollowPoints(OsuHitObject hitObject) { addEntry(hitObject); @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections startTimeMap[hitObject] = startTimeBindable; } - public void RemoveFollowPoints2(OsuHitObject hitObject) + public void RemoveFollowPoints(OsuHitObject hitObject) { removeEntry(hitObject); diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index a8d9423bf6..c816502d61 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -125,13 +125,13 @@ namespace osu.Game.Rulesets.Osu.UI protected override void OnHitObjectAdded(HitObject hitObject) { base.OnHitObjectAdded(hitObject); - followPoints.AddFollowPoints2((OsuHitObject)hitObject); + followPoints.AddFollowPoints((OsuHitObject)hitObject); } protected override void OnHitObjectRemoved(HitObject hitObject) { base.OnHitObjectRemoved(hitObject); - followPoints.RemoveFollowPoints2((OsuHitObject)hitObject); + followPoints.RemoveFollowPoints((OsuHitObject)hitObject); } public void OnHitObjectLoaded(Drawable drawable) From 2418f17b0cd11ea01bf59a1fba1eae70e81f4ee0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 16:19:02 +0900 Subject: [PATCH 084/100] Fix lifetime not being set correctly in some cases --- .../Objects/Drawables/Connections/FollowPointConnection.cs | 3 --- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index fe35cd02dc..3f6854786b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -53,9 +53,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections double startTime = start.GetEndTime(); - if (end == null || end.NewCombo || start is Spinner || end is Spinner) - return; - Vector2 startPosition = start.StackedEndPosition; Vector2 endPosition = end.StackedPosition; double endTime = end.StartTime; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 6dea16d01f..7067cf2728 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -239,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private void refreshLifetimes() { - if (end == null) + if (End == null || End.NewCombo || Start is Spinner || End is Spinner) { LifetimeEnd = LifetimeStart; return; From d4054c87d3d86bcc54e1e8024d6182956f21e895 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 16:15:17 +0900 Subject: [PATCH 085/100] Refactor TestSceneHitCircle to show judgements --- .../TestSceneHitCircle.cs | 67 ++++++++++++------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 596bc06c68..1278a0ff2d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -1,17 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osuTK; -using osu.Game.Rulesets.Mods; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Osu.Tests { @@ -38,13 +38,37 @@ namespace osu.Game.Rulesets.Osu.Tests } private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null) + { + var drawable = createSingle(circleSize, auto, timeOffset, positionOffset); + + var playfield = new TestOsuPlayfield(); + playfield.Add(drawable); + return playfield; + } + + private Drawable testStream(float circleSize, bool auto = false) + { + var playfield = new TestOsuPlayfield(); + + Vector2 pos = new Vector2(-250, 0); + + for (int i = 0; i <= 1000; i += 100) + { + playfield.Add(createSingle(circleSize, auto, i, pos)); + pos.X += 50; + } + + return playfield; + } + + private TestDrawableHitCircle createSingle(float circleSize, bool auto, double timeOffset, Vector2? positionOffset) { positionOffset ??= Vector2.Zero; var circle = new HitCircle { StartTime = Time.Current + 1000 + timeOffset, - Position = positionOffset.Value, + Position = OsuPlayfield.BASE_SIZE / 4 + positionOffset.Value, }; circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); @@ -53,31 +77,14 @@ namespace osu.Game.Rulesets.Osu.Tests foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); - return drawable; } protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) => new TestDrawableHitCircle(circle, auto) { - Anchor = Anchor.Centre, Depth = depthIndex++ }; - private Drawable testStream(float circleSize, bool auto = false) - { - var container = new Container { RelativeSizeAxes = Axes.Both }; - - Vector2 pos = new Vector2(-250, 0); - - for (int i = 0; i <= 1000; i += 100) - { - container.Add(testSingle(circleSize, auto, i, pos)); - pos.X += 50; - } - - return container; - } - protected class TestDrawableHitCircle : DrawableHitCircle { private readonly bool auto; @@ -101,5 +108,13 @@ namespace osu.Game.Rulesets.Osu.Tests base.CheckForResult(userTriggered, timeOffset); } } + + protected class TestOsuPlayfield : OsuPlayfield + { + public TestOsuPlayfield() + { + RelativeSizeAxes = Axes.Both; + } + } } } From 7fe0923fcfbbabd2befa0eda3fe07693dc7c70be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 16:14:38 +0900 Subject: [PATCH 086/100] Show main judgement content above hitobjects --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 16 ++++++++++++++-- .../Judgements/DefaultJudgementPiece.cs | 2 ++ .../Rulesets/Judgements/DrawableJudgement.cs | 19 +++++++++++++++++++ .../Judgements/IAnimatableJudgement.cs | 8 ++++++++ osu.Game/Skinning/LegacyJudgementPieceNew.cs | 4 ++++ osu.Game/Skinning/LegacyJudgementPieceOld.cs | 2 ++ 6 files changed, 49 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 0e98a1d439..3bd150c4d3 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.UI private readonly IDictionary> poolDictionary = new Dictionary>(); + private readonly Container judgementAboveHitObjectLayer; + public OsuPlayfield() { InternalChildren = new Drawable[] @@ -49,6 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, HitObjectContainer, + judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; @@ -58,13 +61,18 @@ namespace osu.Game.Rulesets.Osu.UI var hitWindows = new OsuHitWindows(); foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) - poolDictionary.Add(result, new DrawableJudgementPool(result)); + poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgmentLoaded)); AddRangeInternal(poolDictionary.Values); NewResult += onNewResult; } + private void onJudgmentLoaded(DrawableOsuJudgement judgement) + { + judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent()); + } + [BackgroundDependencyLoader(true)] private void load(OsuRulesetConfigManager config) { @@ -150,11 +158,13 @@ namespace osu.Game.Rulesets.Osu.UI private class DrawableJudgementPool : DrawablePool { private readonly HitResult result; + private readonly Action onLoaded; - public DrawableJudgementPool(HitResult result) + public DrawableJudgementPool(HitResult result, Action onLoaded) : base(10) { this.result = result; + this.onLoaded = onLoaded; } protected override DrawableOsuJudgement CreateNewDrawable() @@ -164,6 +174,8 @@ namespace osu.Game.Rulesets.Osu.UI // just a placeholder to initialise the correct drawable hierarchy for this pool. judgement.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null); + onLoaded?.Invoke(judgement); + return judgement; } } diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index b89c1f4e4f..c26dee119d 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -69,5 +69,7 @@ namespace osu.Game.Rulesets.Judgements this.FadeOutFromOne(800); } + + public Drawable GetAboveHitObjectsProxiedContent() => this.CreateProxy(); } } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 4aed38e689..3063656aaf 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; @@ -29,6 +30,8 @@ namespace osu.Game.Rulesets.Judgements protected SkinnableDrawable JudgementBody { get; private set; } + private readonly Container aboveHitObjectsContent; + [Resolved] private ISkinSource skinSource { get; set; } @@ -59,6 +62,12 @@ namespace osu.Game.Rulesets.Judgements { Size = new Vector2(judgement_size); Origin = Anchor.Centre; + + AddInternal(aboveHitObjectsContent = new Container + { + Depth = float.MinValue, + RelativeSizeAxes = Axes.Both + }); } [BackgroundDependencyLoader] @@ -67,6 +76,8 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); } + public Drawable GetProxyAboveHitObjectsContent() => aboveHitObjectsContent.CreateProxy(); + protected override void LoadComplete() { base.LoadComplete(); @@ -189,6 +200,7 @@ namespace osu.Game.Rulesets.Judgements if (JudgementBody != null) RemoveInternal(JudgementBody); + aboveHitObjectsContent.Clear(); AddInternal(JudgementBody = new SkinnableDrawable(new GameplaySkinComponent(type), _ => CreateDefaultJudgement(type), confineMode: ConfineMode.NoScaling) { @@ -196,6 +208,13 @@ namespace osu.Game.Rulesets.Judgements Origin = Anchor.Centre, }); + if (JudgementBody.Drawable is IAnimatableJudgement animatable) + { + var proxiedContent = animatable.GetAboveHitObjectsProxiedContent(); + if (proxiedContent != null) + aboveHitObjectsContent.Add(proxiedContent); + } + currentDrawableType = type; } diff --git a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs index 32312f1115..d37a270a20 100644 --- a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs @@ -10,6 +10,14 @@ namespace osu.Game.Rulesets.Judgements /// public interface IAnimatableJudgement : IDrawable { + /// + /// Start the animation for this judgement from the current point in time. + /// void PlayAnimation(); + + /// + /// Get proxied content which should be displayed above all hitobjects. + /// + Drawable GetAboveHitObjectsProxiedContent(); } } diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index 2a53820872..290571f46c 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; @@ -121,5 +122,8 @@ namespace osu.Game.Skinning break; } } + + [CanBeNull] + public Drawable GetAboveHitObjectsProxiedContent() => temporaryOldStyle?.CreateProxy(); // for new style judgements, only the old style temporary display is in front of objects. } } diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 3486dce081..5d74ab9ae3 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -69,5 +69,7 @@ namespace osu.Game.Skinning break; } } + + public Drawable GetAboveHitObjectsProxiedContent() => CreateProxy(); } } From a00e0d72793fc97724d3d94e313a9cfaa6f0438b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 16:30:48 +0900 Subject: [PATCH 087/100] Move CanBeNull specification to the interface --- osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs | 2 ++ osu.Game/Skinning/LegacyJudgementPieceNew.cs | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs index d37a270a20..b38b83b534 100644 --- a/osu.Game/Rulesets/Judgements/IAnimatableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/IAnimatableJudgement.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 JetBrains.Annotations; using osu.Framework.Graphics; namespace osu.Game.Rulesets.Judgements @@ -18,6 +19,7 @@ namespace osu.Game.Rulesets.Judgements /// /// Get proxied content which should be displayed above all hitobjects. /// + [CanBeNull] Drawable GetAboveHitObjectsProxiedContent(); } } diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs index 290571f46c..ca25efaa01 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; @@ -123,7 +122,6 @@ namespace osu.Game.Skinning } } - [CanBeNull] public Drawable GetAboveHitObjectsProxiedContent() => temporaryOldStyle?.CreateProxy(); // for new style judgements, only the old style temporary display is in front of objects. } } From 53b6d90ab4822e79b3c0569082580eaf2e5339df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 16:30:58 +0900 Subject: [PATCH 088/100] Don't show default judgements in front of objects for now --- osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index c26dee119d..d94346cb72 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -70,6 +70,6 @@ namespace osu.Game.Rulesets.Judgements this.FadeOutFromOne(800); } - public Drawable GetAboveHitObjectsProxiedContent() => this.CreateProxy(); + public Drawable GetAboveHitObjectsProxiedContent() => null; } } From 85eb98a7ec631531e545f896c2233445733db0af Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 16:43:07 +0900 Subject: [PATCH 089/100] Clean up some code reuse --- .../Connections/FollowPointConnection.cs | 23 +++++++++++++++---- .../Connections/FollowPointRenderer.cs | 8 +++---- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 3f6854786b..f9a594a3cb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -55,12 +55,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections Vector2 startPosition = start.StackedEndPosition; Vector2 endPosition = end.StackedPosition; - double endTime = end.StartTime; Vector2 distanceVector = endPosition - startPosition; int distance = (int)distanceVector.Length; float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); - double duration = endTime - startTime; double finalTransformEndTime = startTime; @@ -69,8 +67,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections float fraction = (float)d / distance; Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; Vector2 pointEndPosition = startPosition + fraction * distanceVector; - double fadeOutTime = startTime + fraction * duration; - double fadeInTime = fadeOutTime - PREEMPT; + + GetFadeTimes(start, end, (float)d / distance, out var fadeInTime, out var fadeOutTime); FollowPoint fp; @@ -98,5 +96,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections // todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed. Entry.LifetimeEnd = finalTransformEndTime; } + + /// + /// Computes the fade time of follow point positioned between two hitobjects. + /// + /// The first , where follow points should originate from. + /// The second , which follow points should target. + /// The fractional distance along and at which the follow point is to be located. + /// The fade-in time of the follow point/ + /// The fade-out time of the follow point. + public static void GetFadeTimes(OsuHitObject start, OsuHitObject end, float fraction, out double fadeInTime, out double fadeOutTime) + { + double startTime = start.GetEndTime(); + double duration = end.StartTime - startTime; + + fadeOutTime = startTime + fraction * duration; + fadeInTime = fadeOutTime - PREEMPT; + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 7067cf2728..b1914fde85 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -248,12 +248,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections Vector2 startPosition = Start.StackedEndPosition; Vector2 endPosition = End.StackedPosition; Vector2 distanceVector = endPosition - startPosition; + + // The lifetime start will match the fade-in time of the first follow point. float fraction = (int)(FollowPointConnection.SPACING * 1.5) / distanceVector.Length; - - double duration = End.StartTime - Start.GetEndTime(); - - double fadeOutTime = Start.StartTime + fraction * duration; - double fadeInTime = fadeOutTime - FollowPointConnection.PREEMPT; + FollowPointConnection.GetFadeTimes(Start, End, fraction, out var fadeInTime, out _); LifetimeStart = fadeInTime; LifetimeEnd = double.MaxValue; // This will be set by the connection. From c1f56cd0ba5d2deb9b7187e9915d8b74b26ee1c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 16:49:22 +0900 Subject: [PATCH 090/100] Remove aliveness hackery --- .../Connections/FollowPointRenderer.cs | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index b1914fde85..43914c4d57 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -43,12 +43,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { InternalChildren = new Drawable[] { - connectionPool = new DrawablePool(1, 200), - pointPool = new DrawablePool(50, 1000) + connectionPool = new DrawablePoolNoLifetime(1, 200), + pointPool = new DrawablePoolNoLifetime(50, 1000) }; - - MakeChildAlive(connectionPool); - MakeChildAlive(pointPool); } public void AddFollowPoints(OsuHitObject hitObject) @@ -136,7 +133,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } } - protected override bool CheckChildrenLife() => lifetimeManager.Update(Time.Current); + protected override bool CheckChildrenLife() + { + bool anyAliveChanged = base.CheckChildrenLife(); + anyAliveChanged |= lifetimeManager.Update(Time.Current); + return anyAliveChanged; + } private void onEntryBecameAlive(LifetimeEntry entry) { @@ -149,7 +151,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections connectionsInUse[entry] = connection; AddInternal(connection); - MakeChildAlive(connection); } private void onEntryBecameDead(LifetimeEntry entry) @@ -173,6 +174,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections lifetimeEntries.Clear(); } + private class DrawablePoolNoLifetime : DrawablePool + where T : PoolableDrawable, new() + { + public override bool RemoveWhenNotAlive => false; + + public DrawablePoolNoLifetime(int initialSize, int? maximumSize = null) + : base(initialSize, maximumSize) + { + } + } + public class FollowPointLifetimeEntry : LifetimeEntry { public event Action Invalidated; From 7bd75eca816adada1857bbb1445b0f469eeb1aab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 16:53:08 +0900 Subject: [PATCH 091/100] Separate classes --- .../TestSceneFollowPoints.cs | 2 +- .../Connections/FollowPointConnection.cs | 2 +- .../Connections/FollowPointLifetimeEntry.cs | 98 +++++++++++++++++++ .../Connections/FollowPointRenderer.cs | 89 ----------------- 4 files changed, 100 insertions(+), 91 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index e7c73b941d..fe67b63252 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -272,7 +272,7 @@ namespace osu.Game.Rulesets.Osu.Tests private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; - private FollowPointRenderer.FollowPointLifetimeEntry getEntry(int index) => followPointRenderer.Entries[index]; + private FollowPointLifetimeEntry getEntry(int index) => followPointRenderer.Entries[index]; private FollowPointConnection getGroup(int index) => followPointRenderer.ChildrenOfType().Single(c => c.Entry == getEntry(index)); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index f9a594a3cb..700d96eff3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public const int SPACING = 32; public const double PREEMPT = 800; - public FollowPointRenderer.FollowPointLifetimeEntry Entry; + public FollowPointLifetimeEntry Entry; public DrawablePool Pool; protected override void PrepareForUse() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs new file mode 100644 index 0000000000..a167cb2f0f --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointLifetimeEntry.cs @@ -0,0 +1,98 @@ +// 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.Bindables; +using osu.Framework.Graphics.Performance; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections +{ + public class FollowPointLifetimeEntry : LifetimeEntry + { + public event Action Invalidated; + public readonly OsuHitObject Start; + + public FollowPointLifetimeEntry(OsuHitObject start) + { + Start = start; + LifetimeStart = Start.StartTime; + + bindEvents(); + } + + private OsuHitObject end; + + public OsuHitObject End + { + get => end; + set + { + UnbindEvents(); + + end = value; + + bindEvents(); + + refreshLifetimes(); + } + } + + private void bindEvents() + { + UnbindEvents(); + + // Note: Positions are bound for instantaneous feedback from positional changes from the editor, before ApplyDefaults() is called on hitobjects. + Start.DefaultsApplied += onDefaultsApplied; + Start.PositionBindable.ValueChanged += onPositionChanged; + + if (End != null) + { + End.DefaultsApplied += onDefaultsApplied; + End.PositionBindable.ValueChanged += onPositionChanged; + } + } + + public void UnbindEvents() + { + if (Start != null) + { + Start.DefaultsApplied -= onDefaultsApplied; + Start.PositionBindable.ValueChanged -= onPositionChanged; + } + + if (End != null) + { + End.DefaultsApplied -= onDefaultsApplied; + End.PositionBindable.ValueChanged -= onPositionChanged; + } + } + + private void onDefaultsApplied(HitObject obj) => refreshLifetimes(); + + private void onPositionChanged(ValueChangedEvent obj) => refreshLifetimes(); + + private void refreshLifetimes() + { + if (End == null || End.NewCombo || Start is Spinner || End is Spinner) + { + LifetimeEnd = LifetimeStart; + return; + } + + Vector2 startPosition = Start.StackedEndPosition; + Vector2 endPosition = End.StackedPosition; + Vector2 distanceVector = endPosition - startPosition; + + // The lifetime start will match the fade-in time of the first follow point. + float fraction = (int)(FollowPointConnection.SPACING * 1.5) / distanceVector.Length; + FollowPointConnection.GetFadeTimes(Start, End, fraction, out var fadeInTime, out _); + + LifetimeStart = fadeInTime; + LifetimeEnd = double.MaxValue; // This will be set by the connection. + + Invalidated?.Invoke(); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 43914c4d57..3e85e528e8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.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 System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects; -using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { @@ -184,92 +182,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { } } - - public class FollowPointLifetimeEntry : LifetimeEntry - { - public event Action Invalidated; - public readonly OsuHitObject Start; - - public FollowPointLifetimeEntry(OsuHitObject start) - { - Start = start; - LifetimeStart = Start.StartTime; - - bindEvents(); - } - - private OsuHitObject end; - - public OsuHitObject End - { - get => end; - set - { - UnbindEvents(); - - end = value; - - bindEvents(); - - refreshLifetimes(); - } - } - - private void bindEvents() - { - UnbindEvents(); - - // Note: Positions are bound for instantaneous feedback from positional changes from the editor, before ApplyDefaults() is called on hitobjects. - Start.DefaultsApplied += onDefaultsApplied; - Start.PositionBindable.ValueChanged += onPositionChanged; - - if (End != null) - { - End.DefaultsApplied += onDefaultsApplied; - End.PositionBindable.ValueChanged += onPositionChanged; - } - } - - public void UnbindEvents() - { - if (Start != null) - { - Start.DefaultsApplied -= onDefaultsApplied; - Start.PositionBindable.ValueChanged -= onPositionChanged; - } - - if (End != null) - { - End.DefaultsApplied -= onDefaultsApplied; - End.PositionBindable.ValueChanged -= onPositionChanged; - } - } - - private void onDefaultsApplied(HitObject obj) => refreshLifetimes(); - - private void onPositionChanged(ValueChangedEvent obj) => refreshLifetimes(); - - private void refreshLifetimes() - { - if (End == null || End.NewCombo || Start is Spinner || End is Spinner) - { - LifetimeEnd = LifetimeStart; - return; - } - - Vector2 startPosition = Start.StackedEndPosition; - Vector2 endPosition = End.StackedPosition; - Vector2 distanceVector = endPosition - startPosition; - - // The lifetime start will match the fade-in time of the first follow point. - float fraction = (int)(FollowPointConnection.SPACING * 1.5) / distanceVector.Length; - FollowPointConnection.GetFadeTimes(Start, End, fraction, out var fadeInTime, out _); - - LifetimeStart = fadeInTime; - LifetimeEnd = double.MaxValue; // This will be set by the connection. - - Invalidated?.Invoke(); - } - } } } From a26b0915b4047d61032dc7c0e9fc893502ac70ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Nov 2020 20:08:05 +0900 Subject: [PATCH 092/100] Fix scheduled tasks not being cleaned up between test steps --- .../TestSceneShaking.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs index d692be89b2..7e973d0971 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs @@ -1,8 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Diagnostics; +using osu.Framework.Threading; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -10,6 +13,19 @@ namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneShaking : TestSceneHitCircle { + private readonly List scheduledTasks = new List(); + + protected override IBeatmap CreateBeatmapForSkinProvider() + { + // best way to run cleanup before a new step is run + foreach (var task in scheduledTasks) + task.Cancel(); + + scheduledTasks.Clear(); + + return base.CreateBeatmapForSkinProvider(); + } + protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) { var drawableHitObject = base.CreateDrawableHitCircle(circle, auto); @@ -17,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests Debug.Assert(drawableHitObject.HitObject.HitWindows != null); double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current; - Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay); + scheduledTasks.Add(Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay)); return drawableHitObject; } From 96abee3fde991e91f1fe14663a9e130c4b4a7005 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Nov 2020 22:43:10 +0900 Subject: [PATCH 093/100] Fix silent NRE in slider selection blueprint --- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 7ae4f387ca..d592e129d9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -44,6 +44,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; } + private readonly BindableList controlPoints = new BindableList(); + private readonly IBindable pathVersion = new Bindable(); + public SliderSelectionBlueprint(DrawableSlider slider) : base(slider) { @@ -61,13 +64,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders }; } - private IBindable pathVersion; - protected override void LoadComplete() { base.LoadComplete(); - pathVersion = HitObject.Path.Version.GetBoundCopy(); + controlPoints.BindTo(HitObject.Path.ControlPoints); + + pathVersion.BindTo(HitObject.Path.Version); pathVersion.BindValueChanged(_ => updatePath()); BodyPiece.UpdateFrom(HitObject); @@ -164,8 +167,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } } - private BindableList controlPoints => HitObject.Path.ControlPoints; - private int addControlPoint(Vector2 position) { position -= HitObject.Position; From c4cb1440ab0a02701f3501b04aa73ef3e8e3cdc3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 21 Nov 2020 02:59:01 +0300 Subject: [PATCH 094/100] Rename PaginatedContainerHeader to ProfileSubsectionHeader --- ...ntainerHeader.cs => TestSceneProfileSubsectionHeader.cs} | 6 +++--- osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 4 ++-- ...ginatedContainerHeader.cs => ProfileSubsectionHeader.cs} | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestScenePaginatedContainerHeader.cs => TestSceneProfileSubsectionHeader.cs} (95%) rename osu.Game/Overlays/Profile/Sections/{PaginatedContainerHeader.cs => ProfileSubsectionHeader.cs} (95%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs similarity index 95% rename from osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs index 2e9f919cfd..cd226662d7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePaginatedContainerHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs @@ -11,12 +11,12 @@ using osu.Framework.Allocation; namespace osu.Game.Tests.Visual.UserInterface { - public class TestScenePaginatedContainerHeader : OsuTestScene + public class TestSceneProfileSubsectionHeader : OsuTestScene { [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); - private PaginatedContainerHeader header; + private ProfileSubsectionHeader header; [Test] public void TestHiddenCounter() @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void createHeader(string text, CounterVisibilityState state, int initialValue = 0) { Clear(); - Add(header = new PaginatedContainerHeader(text, state) + Add(header = new ProfileSubsectionHeader(text, state) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index c1107ce907..5bcd7d34c2 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Profile.Sections private readonly string missingText; private ShowMoreButton moreButton; private OsuSpriteText missing; - private PaginatedContainerHeader header; + private ProfileSubsectionHeader header; private readonly string headerText; private readonly CounterVisibilityState counterVisibilityState; @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Profile.Sections Children = new Drawable[] { - header = new PaginatedContainerHeader(headerText, counterVisibilityState) + header = new ProfileSubsectionHeader(headerText, counterVisibilityState) { Alpha = string.IsNullOrEmpty(headerText) ? 0 : 1 }, diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs similarity index 95% rename from osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs rename to osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs index 8c617e5fbd..5858cebe89 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainerHeader.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics; namespace osu.Game.Overlays.Profile.Sections { - public class PaginatedContainerHeader : CompositeDrawable, IHasCurrentValue + public class ProfileSubsectionHeader : CompositeDrawable, IHasCurrentValue { private readonly BindableWithCurrent current = new BindableWithCurrent(); @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Profile.Sections private CounterPill counterPill; - public PaginatedContainerHeader(string text, CounterVisibilityState counterState) + public ProfileSubsectionHeader(string text, CounterVisibilityState counterState) { this.text = text; this.counterState = counterState; From 718ba9253bce4351e2dc07aaa495c943b70b9803 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 21 Nov 2020 03:18:24 +0300 Subject: [PATCH 095/100] Implement ProfileSubsection component --- .../Beatmaps/PaginatedBeatmapContainer.cs | 2 +- .../PaginatedMostPlayedBeatmapContainer.cs | 2 +- .../Kudosu/PaginatedKudosuHistoryContainer.cs | 2 +- ...ainer.cs => PaginatedProfileSubsection.cs} | 66 ++++------------ .../Profile/Sections/ProfileSubsection.cs | 78 +++++++++++++++++++ .../Sections/Ranks/PaginatedScoreContainer.cs | 2 +- .../PaginatedRecentActivityContainer.cs | 2 +- 7 files changed, 100 insertions(+), 54 deletions(-) rename osu.Game/Overlays/Profile/Sections/{PaginatedContainer.cs => PaginatedProfileSubsection.cs} (61%) create mode 100644 osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 4b7de8de90..780d7ea986 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Overlays.Profile.Sections.Beatmaps { - public class PaginatedBeatmapContainer : PaginatedContainer + public class PaginatedBeatmapContainer : PaginatedProfileSubsection { private const float panel_padding = 10f; private readonly BeatmapSetType type; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 556f3139dd..e5bb1f8008 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -13,7 +13,7 @@ using osu.Game.Users; namespace osu.Game.Overlays.Profile.Sections.Historical { - public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer + public class PaginatedMostPlayedBeatmapContainer : PaginatedProfileSubsection { public PaginatedMostPlayedBeatmapContainer(Bindable user) : base(user, "Most Played Beatmaps", "No records. :(", CounterVisibilityState.AlwaysVisible) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs index 1b8bd23eb4..008d89d881 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs @@ -11,7 +11,7 @@ using System.Collections.Generic; namespace osu.Game.Overlays.Profile.Sections.Kudosu { - public class PaginatedKudosuHistoryContainer : PaginatedContainer + public class PaginatedKudosuHistoryContainer : PaginatedProfileSubsection { public PaginatedKudosuHistoryContainer(Bindable user) : base(user, missingText: "This user hasn't received any kudosu!") diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs similarity index 61% rename from osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs rename to osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index 5bcd7d34c2..1f897d704a 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -6,10 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Online.API; -using osu.Game.Rulesets; using osu.Game.Users; using System.Collections.Generic; using System.Linq; @@ -18,7 +15,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Profile.Sections { - public abstract class PaginatedContainer : FillFlowContainer + public abstract class PaginatedProfileSubsection : ProfileSubsection { [Resolved] private IAPIProvider api { get; set; } @@ -26,42 +23,25 @@ namespace osu.Game.Overlays.Profile.Sections protected int VisiblePages; protected int ItemsPerPage; - protected readonly Bindable User = new Bindable(); - protected FillFlowContainer ItemsContainer; - protected RulesetStore Rulesets; + protected FillFlowContainer ItemsContainer { get; private set; } private APIRequest> retrievalRequest; private CancellationTokenSource loadCancellation; - private readonly string missingText; private ShowMoreButton moreButton; - private OsuSpriteText missing; - private ProfileSubsectionHeader header; - private readonly string headerText; - private readonly CounterVisibilityState counterVisibilityState; - - protected PaginatedContainer(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + protected PaginatedProfileSubsection(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + : base(user, headerText, missingText, counterVisibilityState) { - this.headerText = headerText; - this.missingText = missingText; - this.counterVisibilityState = counterVisibilityState; - User.BindTo(user); } - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) + protected override Drawable CreateContent() => new FillFlowContainer { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Direction = FillDirection.Vertical; - + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new ProfileSubsectionHeader(headerText, counterVisibilityState) - { - Alpha = string.IsNullOrEmpty(headerText) ? 0 : 1 - }, ItemsContainer = new FillFlowContainer { AutoSizeAxes = Axes.Y, @@ -75,22 +55,11 @@ namespace osu.Game.Overlays.Profile.Sections Alpha = 0, Margin = new MarginPadding { Top = 10 }, Action = showMore, - }, - missing = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 15), - Text = missingText, - Alpha = 0, - }, - }; + } + } + }; - Rulesets = rulesets; - - User.ValueChanged += onUserChanged; - User.TriggerChange(); - } - - private void onUserChanged(ValueChangedEvent e) + protected override void OnUserChanged(ValueChangedEvent e) { loadCancellation?.Cancel(); retrievalRequest?.Cancel(); @@ -124,15 +93,15 @@ namespace osu.Game.Overlays.Profile.Sections moreButton.Hide(); moreButton.IsLoading = false; - if (!string.IsNullOrEmpty(missing.Text)) - missing.Show(); + if (!string.IsNullOrEmpty(Missing.Text)) + Missing.Show(); return; } LoadComponentsAsync(items.Select(CreateDrawableItem).Where(d => d != null), drawables => { - missing.Hide(); + Missing.Hide(); moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); moreButton.IsLoading = false; @@ -142,8 +111,6 @@ namespace osu.Game.Overlays.Profile.Sections protected virtual int GetCount(User user) => 0; - protected void SetCount(int value) => header.Current.Value = value; - protected virtual void OnItemsReceived(List items) { } @@ -154,8 +121,9 @@ namespace osu.Game.Overlays.Profile.Sections protected override void Dispose(bool isDisposing) { - base.Dispose(isDisposing); retrievalRequest?.Cancel(); + loadCancellation?.Cancel(); + base.Dispose(isDisposing); } } } diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs new file mode 100644 index 0000000000..751b35e342 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Users; +using JetBrains.Annotations; + +namespace osu.Game.Overlays.Profile.Sections +{ + public abstract class ProfileSubsection : FillFlowContainer + { + protected readonly Bindable User = new Bindable(); + + protected RulesetStore Rulesets { get; private set; } + + protected OsuSpriteText Missing { get; private set; } + + private readonly string headerText; + private readonly string missingText; + private readonly CounterVisibilityState counterVisibilityState; + + private ProfileSubsectionHeader header; + + protected ProfileSubsection(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + { + this.headerText = headerText; + this.missingText = missingText; + this.counterVisibilityState = counterVisibilityState; + User.BindTo(user); + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + + Children = new[] + { + header = new ProfileSubsectionHeader(headerText, counterVisibilityState) + { + Alpha = string.IsNullOrEmpty(headerText) ? 0 : 1 + }, + CreateContent(), + Missing = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 15), + Text = missingText, + Alpha = 0, + }, + }; + + Rulesets = rulesets; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + User.BindValueChanged(OnUserChanged, true); + } + + [NotNull] + protected abstract Drawable CreateContent(); + + protected virtual void OnUserChanged(ValueChangedEvent e) + { + } + + protected void SetCount(int value) => header.Current.Value = value; + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 1ce3079d52..53f6d375ca 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -14,7 +14,7 @@ using osu.Framework.Allocation; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedContainer + public class PaginatedScoreContainer : PaginatedProfileSubsection { private readonly ScoreType type; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index 08f39c6272..d7101a8147 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -13,7 +13,7 @@ using osu.Framework.Allocation; namespace osu.Game.Overlays.Profile.Sections.Recent { - public class PaginatedRecentActivityContainer : PaginatedContainer + public class PaginatedRecentActivityContainer : PaginatedProfileSubsection { public PaginatedRecentActivityContainer(Bindable user) : base(user, missingText: "This user hasn't done anything notable recently!") From 99a95790c3ac901b83d34c67f3dc485920437b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 Nov 2020 14:36:59 +0100 Subject: [PATCH 096/100] Resolve test failure --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 44a2056732..192d65f3fe 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -326,7 +326,7 @@ namespace osu.Game.Tests.Visual.Ranking public HotkeyRetryOverlay RetryOverlay; public UnrankedSoloResultsScreen(ScoreInfo score) - : base(score, false) + : base(score, true) { Score.Beatmap.OnlineBeatmapID = 0; Score.Beatmap.Status = BeatmapSetOnlineStatus.Pending; From 875f986979b22832eeff88a96bf0448241325730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 Nov 2020 14:38:38 +0100 Subject: [PATCH 097/100] Remove default from base ResultsScreen too --- osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs | 2 +- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 9ef9649f77..5323f58a66 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -318,7 +318,7 @@ namespace osu.Game.Tests.Visual.Background private class FadeAccessibleResults : ResultsScreen { public FadeAccessibleResults(ScoreInfo score) - : base(score) + : base(score, true) { } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 192d65f3fe..b2be7cdf88 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -256,7 +256,7 @@ namespace osu.Game.Tests.Visual.Ranking public HotkeyRetryOverlay RetryOverlay; public TestResultsScreen(ScoreInfo score) - : base(score) + : base(score, true) { } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index f8bdf0140c..626db9baa6 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Ranking private readonly bool allowRetry; - protected ResultsScreen(ScoreInfo score, bool allowRetry = true) + protected ResultsScreen(ScoreInfo score, bool allowRetry) { Score = score; this.allowRetry = allowRetry; From 11c3ccfcaa4a4a2adb130e3bbbcda97a0ead8c78 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 21 Nov 2020 22:49:56 +0300 Subject: [PATCH 098/100] Move rulesets property to PaginatedProfileSubsection --- .../Profile/Sections/PaginatedProfileSubsection.cs | 4 ++++ osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs | 7 +------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index 1f897d704a..3e6b20bf14 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; namespace osu.Game.Overlays.Profile.Sections { @@ -20,6 +21,9 @@ namespace osu.Game.Overlays.Profile.Sections [Resolved] private IAPIProvider api { get; set; } + [Resolved] + protected RulesetStore Rulesets { get; private set; } + protected int VisiblePages; protected int ItemsPerPage; diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs index 751b35e342..6f68804827 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; using osu.Game.Users; using JetBrains.Annotations; @@ -17,8 +16,6 @@ namespace osu.Game.Overlays.Profile.Sections { protected readonly Bindable User = new Bindable(); - protected RulesetStore Rulesets { get; private set; } - protected OsuSpriteText Missing { get; private set; } private readonly string headerText; @@ -36,7 +33,7 @@ namespace osu.Game.Overlays.Profile.Sections } [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) + private void load() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -56,8 +53,6 @@ namespace osu.Game.Overlays.Profile.Sections Alpha = 0, }, }; - - Rulesets = rulesets; } protected override void LoadComplete() From dbfc839df379c65b09279704036347384ce6ef90 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 21 Nov 2020 23:03:54 +0300 Subject: [PATCH 099/100] Move missing text to PaginatedProfileSubsection --- .../Sections/PaginatedProfileSubsection.cs | 19 +++++++++++++++---- .../Profile/Sections/ProfileSubsection.cs | 16 ++-------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index 3e6b20bf14..b5ae949105 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -13,6 +13,8 @@ using System.Linq; using System.Threading; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; namespace osu.Game.Overlays.Profile.Sections { @@ -33,10 +35,13 @@ namespace osu.Game.Overlays.Profile.Sections private CancellationTokenSource loadCancellation; private ShowMoreButton moreButton; + private OsuSpriteText missing; + private readonly string missingText; protected PaginatedProfileSubsection(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) - : base(user, headerText, missingText, counterVisibilityState) + : base(user, headerText, counterVisibilityState) { + this.missingText = missingText; } protected override Drawable CreateContent() => new FillFlowContainer @@ -59,6 +64,12 @@ namespace osu.Game.Overlays.Profile.Sections Alpha = 0, Margin = new MarginPadding { Top = 10 }, Action = showMore, + }, + missing = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 15), + Text = missingText, + Alpha = 0, } } }; @@ -97,15 +108,15 @@ namespace osu.Game.Overlays.Profile.Sections moreButton.Hide(); moreButton.IsLoading = false; - if (!string.IsNullOrEmpty(Missing.Text)) - Missing.Show(); + if (!string.IsNullOrEmpty(missingText)) + missing.Show(); return; } LoadComponentsAsync(items.Select(CreateDrawableItem).Where(d => d != null), drawables => { - Missing.Hide(); + missing.Hide(); moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); moreButton.IsLoading = false; diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs index 6f68804827..0743823113 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs @@ -5,8 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Users; using JetBrains.Annotations; @@ -16,18 +14,14 @@ namespace osu.Game.Overlays.Profile.Sections { protected readonly Bindable User = new Bindable(); - protected OsuSpriteText Missing { get; private set; } - private readonly string headerText; - private readonly string missingText; private readonly CounterVisibilityState counterVisibilityState; private ProfileSubsectionHeader header; - protected ProfileSubsection(Bindable user, string headerText = "", string missingText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + protected ProfileSubsection(Bindable user, string headerText = "", CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) { this.headerText = headerText; - this.missingText = missingText; this.counterVisibilityState = counterVisibilityState; User.BindTo(user); } @@ -45,13 +39,7 @@ namespace osu.Game.Overlays.Profile.Sections { Alpha = string.IsNullOrEmpty(headerText) ? 0 : 1 }, - CreateContent(), - Missing = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 15), - Text = missingText, - Alpha = 0, - }, + CreateContent() }; } From fe4c6220418b1f8560f6e6bcdc9c8994e9912efd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 21 Nov 2020 23:13:46 +0300 Subject: [PATCH 100/100] Make OnUserChanged private --- .../Profile/Sections/PaginatedProfileSubsection.cs | 8 +++++++- .../Overlays/Profile/Sections/ProfileSubsection.cs | 10 ---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index b5ae949105..51e5622f68 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -74,7 +74,13 @@ namespace osu.Game.Overlays.Profile.Sections } }; - protected override void OnUserChanged(ValueChangedEvent e) + protected override void LoadComplete() + { + base.LoadComplete(); + User.BindValueChanged(onUserChanged, true); + } + + private void onUserChanged(ValueChangedEvent e) { loadCancellation?.Cancel(); retrievalRequest?.Cancel(); diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs index 0743823113..3e331f85e9 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs @@ -43,19 +43,9 @@ namespace osu.Game.Overlays.Profile.Sections }; } - protected override void LoadComplete() - { - base.LoadComplete(); - User.BindValueChanged(OnUserChanged, true); - } - [NotNull] protected abstract Drawable CreateContent(); - protected virtual void OnUserChanged(ValueChangedEvent e) - { - } - protected void SetCount(int value) => header.Current.Value = value; } }