mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 20:22:55 +08:00
Merge branch 'master' into fix-carousel-sort-change-after-play
This commit is contained in:
commit
4a6f6300c5
@ -0,0 +1,19 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneManiaModFadeIn : ModTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
[TestCase(0.5f)]
|
||||
[TestCase(0.1f)]
|
||||
[TestCase(0.7f)]
|
||||
public void TestCoverage(float coverage) => CreateModTest(new ModTestData { Mod = new ManiaModFadeIn { Coverage = { Value = coverage } }, PassCondition = () => true });
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneManiaModHidden : ModTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
[TestCase(0.5f)]
|
||||
[TestCase(0.2f)]
|
||||
[TestCase(0.8f)]
|
||||
public void TestCoverage(float coverage) => CreateModTest(new ModTestData { Mod = new ManiaModHidden { Coverage = { Value = coverage } }, PassCondition = () => true });
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
|
||||
@ -18,5 +19,13 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray();
|
||||
|
||||
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll;
|
||||
|
||||
public override BindableNumber<float> Coverage { get; } = new BindableFloat(0.5f)
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 0.1f,
|
||||
MaxValue = 0.7f,
|
||||
Default = 0.5f,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Framework.Bindables;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
@ -13,6 +14,14 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override LocalisableString Description => @"Keys fade out before you hit them!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override BindableNumber<float> Coverage { get; } = new BindableFloat(0.5f)
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 0.2f,
|
||||
MaxValue = 0.8f,
|
||||
Default = 0.5f,
|
||||
};
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray();
|
||||
|
||||
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -22,6 +24,9 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
/// </summary>
|
||||
protected abstract CoverExpandDirection ExpandDirection { get; }
|
||||
|
||||
[SettingSource("Coverage", "The proportion of playfield height that notes will be hidden for.")]
|
||||
public abstract BindableNumber<float> Coverage { get; }
|
||||
|
||||
public virtual void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
||||
{
|
||||
ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield;
|
||||
@ -36,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
c.RelativeSizeAxes = Axes.Both;
|
||||
c.Direction = ExpandDirection;
|
||||
c.Coverage = 0.5f;
|
||||
c.Coverage = Coverage.Value;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -31,10 +31,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
[Test]
|
||||
public void TestMaximumDistanceTrackingWithoutMovement(
|
||||
[Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
|
||||
float circleSize,
|
||||
[Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
|
||||
double velocity)
|
||||
[Values(0, 5, 10)] float circleSize,
|
||||
[Values(0, 5, 10)] double velocity)
|
||||
{
|
||||
const double time_slider_start = 1000;
|
||||
|
||||
|
BIN
osu.Game.Tests/Resources/Archives/modified-default-20230117.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-default-20230117.osk
Normal file
Binary file not shown.
@ -44,7 +44,9 @@ namespace osu.Game.Tests.Skins
|
||||
// Covers TextElement and BeatmapInfoDrawable
|
||||
"Archives/modified-default-20221102.osk",
|
||||
// Covers BPM counter.
|
||||
"Archives/modified-default-20221205.osk"
|
||||
"Archives/modified-default-20221205.osk",
|
||||
// Covers judgement counter.
|
||||
"Archives/modified-default-20230117.osk"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
144
osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs
Normal file
144
osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs
Normal file
@ -0,0 +1,144 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD.JudgementCounter;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneJudgementCounter : OsuTestScene
|
||||
{
|
||||
private ScoreProcessor scoreProcessor = null!;
|
||||
private JudgementTally judgementTally = null!;
|
||||
private TestJudgementCounterDisplay counterDisplay = null!;
|
||||
|
||||
private readonly Bindable<JudgementResult> lastJudgementResult = new Bindable<JudgementResult>();
|
||||
|
||||
private int iteration;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetupSteps() => AddStep("Create components", () =>
|
||||
{
|
||||
var ruleset = CreateRuleset();
|
||||
|
||||
Debug.Assert(ruleset != null);
|
||||
|
||||
scoreProcessor = new ScoreProcessor(ruleset);
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[] { (typeof(ScoreProcessor), scoreProcessor), (typeof(Ruleset), ruleset) },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
judgementTally = new JudgementTally(),
|
||||
new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[] { (typeof(JudgementTally), judgementTally) },
|
||||
Child = counterDisplay = new TestJudgementCounterDisplay
|
||||
{
|
||||
Margin = new MarginPadding { Top = 100 },
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||
|
||||
private void applyOneJudgement(HitResult result)
|
||||
{
|
||||
lastJudgementResult.Value = new OsuJudgementResult(new HitObject
|
||||
{
|
||||
StartTime = iteration * 10000
|
||||
}, new OsuJudgement())
|
||||
{
|
||||
Type = result,
|
||||
};
|
||||
scoreProcessor.ApplyResult(lastJudgementResult.Value);
|
||||
|
||||
iteration++;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddJudgementsToCounters()
|
||||
{
|
||||
AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Great), 2);
|
||||
AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Miss), 2);
|
||||
AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Meh), 2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddWhilstHidden()
|
||||
{
|
||||
AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.LargeTickHit), 2);
|
||||
AddAssert("Check value added whilst hidden", () => hiddenCount() == 2);
|
||||
AddStep("Show all judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.All);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangeFlowDirection()
|
||||
{
|
||||
AddStep("Set direction vertical", () => counterDisplay.FlowDirection.Value = Direction.Vertical);
|
||||
AddStep("Set direction horizontal", () => counterDisplay.FlowDirection.Value = Direction.Horizontal);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToggleJudgementNames()
|
||||
{
|
||||
AddStep("Hide judgement names", () => counterDisplay.ShowJudgementNames.Value = false);
|
||||
AddWaitStep("wait some", 2);
|
||||
AddAssert("Assert hidden", () => counterDisplay.CounterFlow.Children.First().ResultName.Alpha == 0);
|
||||
AddStep("Hide judgement names", () => counterDisplay.ShowJudgementNames.Value = true);
|
||||
AddWaitStep("wait some", 2);
|
||||
AddAssert("Assert shown", () => counterDisplay.CounterFlow.Children.First().ResultName.Alpha == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHideMaxValue()
|
||||
{
|
||||
AddStep("Hide max judgement", () => counterDisplay.ShowMaxJudgement.Value = false);
|
||||
AddWaitStep("wait some", 2);
|
||||
AddAssert("Check max hidden", () => counterDisplay.CounterFlow.ChildrenOfType<JudgementCounter>().First().Alpha == 0);
|
||||
AddStep("Show max judgement", () => counterDisplay.ShowMaxJudgement.Value = true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCycleDisplayModes()
|
||||
{
|
||||
AddStep("Show basic judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.Simple);
|
||||
AddWaitStep("wait some", 2);
|
||||
AddAssert("Check only basic", () => counterDisplay.CounterFlow.ChildrenOfType<JudgementCounter>().Last().Alpha == 0);
|
||||
AddStep("Show normal judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.Normal);
|
||||
AddStep("Show all judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.All);
|
||||
AddWaitStep("wait some", 2);
|
||||
AddAssert("Check all visible", () => counterDisplay.CounterFlow.ChildrenOfType<JudgementCounter>().Last().Alpha == 1);
|
||||
}
|
||||
|
||||
private int hiddenCount()
|
||||
{
|
||||
var num = counterDisplay.CounterFlow.Children.First(child => child.Result.Type == HitResult.LargeTickHit);
|
||||
return num.Result.ResultCount.Value;
|
||||
}
|
||||
|
||||
private partial class TestJudgementCounterDisplay : JudgementCounterDisplay
|
||||
{
|
||||
public new FillFlowContainer<JudgementCounter> CounterFlow => base.CounterFlow;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -11,7 +9,10 @@ using osu.Game.Overlays;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -27,15 +28,20 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||
|
||||
private CommentsContainer commentsContainer;
|
||||
private CommentsContainer commentsContainer = null!;
|
||||
|
||||
private TextBox editorTextBox = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Child = new BasicScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = commentsContainer = new CommentsContainer()
|
||||
});
|
||||
};
|
||||
editorTextBox = commentsContainer.ChildrenOfType<TextBox>().First();
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestIdleState()
|
||||
@ -126,6 +132,44 @@ namespace osu.Game.Tests.Visual.Online
|
||||
commentsContainer.ChildrenOfType<DrawableComment>().Count(d => d.Comment.Pinned == withPinned) == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPost()
|
||||
{
|
||||
setUpCommentsResponse(new CommentBundle { Comments = new List<Comment>() });
|
||||
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||
AddAssert("no comments placeholder shown", () => commentsContainer.ChildrenOfType<CommentsContainer.NoCommentsPlaceholder>().Any());
|
||||
|
||||
setUpPostResponse();
|
||||
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
|
||||
AddStep("submit", () => commentsContainer.ChildrenOfType<RoundedButton>().First().TriggerClick());
|
||||
|
||||
AddUntilStep("comment sent", () =>
|
||||
{
|
||||
string writtenText = editorTextBox.Current.Value;
|
||||
var comment = commentsContainer.ChildrenOfType<DrawableComment>().LastOrDefault();
|
||||
return comment != null && comment.ChildrenOfType<SpriteText>().Any(y => y.Text == writtenText);
|
||||
});
|
||||
AddAssert("no comments placeholder removed", () => !commentsContainer.ChildrenOfType<CommentsContainer.NoCommentsPlaceholder>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPostWithExistingComments()
|
||||
{
|
||||
setUpCommentsResponse(getExampleComments());
|
||||
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||
|
||||
setUpPostResponse();
|
||||
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
|
||||
AddStep("submit", () => commentsContainer.ChildrenOfType<CommentEditor>().Single().ChildrenOfType<RoundedButton>().First().TriggerClick());
|
||||
|
||||
AddUntilStep("comment sent", () =>
|
||||
{
|
||||
string writtenText = editorTextBox.Current.Value;
|
||||
var comment = commentsContainer.ChildrenOfType<DrawableComment>().LastOrDefault();
|
||||
return comment != null && comment.ChildrenOfType<SpriteText>().Any(y => y.Text == writtenText);
|
||||
});
|
||||
}
|
||||
|
||||
private void setUpCommentsResponse(CommentBundle commentBundle)
|
||||
=> AddStep("set up response", () =>
|
||||
{
|
||||
@ -139,7 +183,33 @@ namespace osu.Game.Tests.Visual.Online
|
||||
};
|
||||
});
|
||||
|
||||
private CommentBundle getExampleComments(bool withPinned = false)
|
||||
private void setUpPostResponse()
|
||||
=> AddStep("set up response", () =>
|
||||
{
|
||||
dummyAPI.HandleRequest = request =>
|
||||
{
|
||||
if (!(request is CommentPostRequest req))
|
||||
return false;
|
||||
|
||||
req.TriggerSuccess(new CommentBundle
|
||||
{
|
||||
Comments = new List<Comment>
|
||||
{
|
||||
new Comment
|
||||
{
|
||||
Id = 98,
|
||||
Message = req.Message,
|
||||
LegacyName = "FirstUser",
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
VotesCount = 98,
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
};
|
||||
});
|
||||
|
||||
private static CommentBundle getExampleComments(bool withPinned = false)
|
||||
{
|
||||
var bundle = new CommentBundle
|
||||
{
|
||||
|
@ -19,6 +19,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"join the real-time discussion");
|
||||
|
||||
/// <summary>
|
||||
/// "Mention"
|
||||
/// </summary>
|
||||
public static LocalisableString MentionUser => new TranslatableString(getKey(@"mention_user"), @"Mention");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
41
osu.Game/Online/API/Requests/CommentPostRequest.cs
Normal file
41
osu.Game/Online/API/Requests/CommentPostRequest.cs
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Net.Http;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class CommentPostRequest : APIRequest<CommentBundle>
|
||||
{
|
||||
public readonly CommentableType Commentable;
|
||||
public readonly long CommentableId;
|
||||
public readonly string Message;
|
||||
public readonly long? ParentCommentId;
|
||||
|
||||
public CommentPostRequest(CommentableType commentable, long commentableId, string message, long? parentCommentId = null)
|
||||
{
|
||||
Commentable = commentable;
|
||||
CommentableId = commentableId;
|
||||
Message = message;
|
||||
ParentCommentId = parentCommentId;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var req = base.CreateWebRequest();
|
||||
req.Method = HttpMethod.Post;
|
||||
|
||||
req.AddParameter(@"comment[commentable_type]", Commentable.ToString().ToLowerInvariant());
|
||||
req.AddParameter(@"comment[commentable_id]", $"{CommentableId}");
|
||||
req.AddParameter(@"comment[message]", Message);
|
||||
if (ParentCommentId.HasValue)
|
||||
req.AddParameter(@"comment[parent_id]", $"{ParentCommentId}");
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => "comments";
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ namespace osu.Game.Online.Chat
|
||||
/// </summary>
|
||||
public partial class StandAloneChatDisplay : CompositeDrawable
|
||||
{
|
||||
[Cached]
|
||||
public readonly Bindable<Channel> Channel = new Bindable<Channel>();
|
||||
|
||||
protected readonly ChatTextBox TextBox;
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -24,6 +25,7 @@ using osu.Game.Online.Chat;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using ChatStrings = osu.Game.Localisation.ChatStrings;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
@ -65,6 +67,9 @@ namespace osu.Game.Overlays.Chat
|
||||
[Resolved(canBeNull: true)]
|
||||
private UserProfileOverlay? profileOverlay { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private Bindable<Channel?>? currentChannel { get; set; }
|
||||
|
||||
private readonly APIUser user;
|
||||
private readonly OsuSpriteText drawableText;
|
||||
|
||||
@ -156,6 +161,14 @@ namespace osu.Game.Overlays.Chat
|
||||
if (!user.Equals(api.LocalUser.Value))
|
||||
items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel));
|
||||
|
||||
if (currentChannel?.Value != null)
|
||||
{
|
||||
items.Add(new OsuMenuItem(ChatStrings.MentionUser, MenuItemType.Standard, () =>
|
||||
{
|
||||
currentChannel.Value.TextBoxMessage.Value += $"@{user.Username} ";
|
||||
}));
|
||||
}
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.API;
|
||||
@ -17,16 +18,22 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Threading;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Comments
|
||||
{
|
||||
[Cached]
|
||||
public partial class CommentsContainer : CompositeDrawable
|
||||
{
|
||||
private readonly Bindable<CommentableType> type = new Bindable<CommentableType>();
|
||||
private readonly BindableLong id = new BindableLong();
|
||||
public IBindable<CommentableType> Type => type;
|
||||
public IBindable<long> Id => id;
|
||||
|
||||
public readonly Bindable<CommentsSortCriteria> Sort = new Bindable<CommentsSortCriteria>();
|
||||
public readonly BindableBool ShowDeleted = new BindableBool();
|
||||
@ -46,12 +53,14 @@ namespace osu.Game.Overlays.Comments
|
||||
private DeletedCommentsCounter deletedCommentsCounter;
|
||||
private CommentsShowMoreButton moreButton;
|
||||
private TotalCommentsCounter commentCounter;
|
||||
private UpdateableAvatar avatar;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
@ -86,6 +95,32 @@ namespace osu.Game.Overlays.Comments
|
||||
},
|
||||
},
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = 50, Vertical = 20 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
avatar = new UpdateableAvatar(api.LocalUser.Value)
|
||||
{
|
||||
Size = new Vector2(50),
|
||||
CornerExponent = 2,
|
||||
CornerRadius = 25,
|
||||
Masking = true,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Padding = new MarginPadding { Left = 60 },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = new NewCommentEditor
|
||||
{
|
||||
OnPost = prependPostedComments
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new CommentsHeader
|
||||
{
|
||||
Sort = { BindTarget = Sort },
|
||||
@ -151,6 +186,7 @@ namespace osu.Game.Overlays.Comments
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
User.BindValueChanged(_ => refetchComments());
|
||||
User.BindValueChanged(e => avatar.User = e.NewValue);
|
||||
Sort.BindValueChanged(_ => refetchComments(), true);
|
||||
base.LoadComplete();
|
||||
}
|
||||
@ -245,7 +281,6 @@ namespace osu.Game.Overlays.Comments
|
||||
{
|
||||
pinnedContent.AddRange(loaded.Where(d => d.Comment.Pinned));
|
||||
content.AddRange(loaded.Where(d => !d.Comment.Pinned));
|
||||
|
||||
deletedCommentsCounter.Count.Value += topLevelComments.Select(d => d.Comment).Count(c => c.IsDeleted && c.IsTopLevel);
|
||||
|
||||
if (bundle.HasMore)
|
||||
@ -288,6 +323,34 @@ namespace osu.Game.Overlays.Comments
|
||||
}
|
||||
}
|
||||
|
||||
private void prependPostedComments(CommentBundle bundle)
|
||||
{
|
||||
var topLevelComments = new List<DrawableComment>();
|
||||
|
||||
foreach (var comment in bundle.Comments)
|
||||
{
|
||||
// Exclude possible duplicated comments.
|
||||
if (CommentDictionary.ContainsKey(comment.Id))
|
||||
continue;
|
||||
|
||||
topLevelComments.Add(getDrawableComment(comment));
|
||||
}
|
||||
|
||||
if (topLevelComments.Any())
|
||||
{
|
||||
LoadComponentsAsync(topLevelComments, loaded =>
|
||||
{
|
||||
if (content.Count > 0 && content[0] is NoCommentsPlaceholder placeholder)
|
||||
content.Remove(placeholder, true);
|
||||
|
||||
foreach (var comment in loaded)
|
||||
{
|
||||
content.Insert((int)-Clock.CurrentTime, comment);
|
||||
}
|
||||
}, (loadCancellation = new CancellationTokenSource()).Token);
|
||||
}
|
||||
}
|
||||
|
||||
private DrawableComment getDrawableComment(Comment comment)
|
||||
{
|
||||
if (CommentDictionary.TryGetValue(comment.Id, out var existing))
|
||||
@ -317,7 +380,7 @@ namespace osu.Game.Overlays.Comments
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
||||
private partial class NoCommentsPlaceholder : CompositeDrawable
|
||||
internal partial class NoCommentsPlaceholder : CompositeDrawable
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -336,5 +399,41 @@ namespace osu.Game.Overlays.Comments
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private partial class NewCommentEditor : CommentEditor
|
||||
{
|
||||
[Resolved]
|
||||
private CommentsContainer commentsContainer { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
public Action<CommentBundle> OnPost;
|
||||
|
||||
//TODO should match web, left empty due to no multiline support
|
||||
protected override LocalisableString FooterText => default;
|
||||
|
||||
protected override LocalisableString CommitButtonText => CommonStrings.ButtonsPost;
|
||||
|
||||
protected override LocalisableString TextBoxPlaceholder => CommentsStrings.PlaceholderNew;
|
||||
|
||||
protected override void OnCommit(string text)
|
||||
{
|
||||
ShowLoadingSpinner = true;
|
||||
CommentPostRequest req = new CommentPostRequest(commentsContainer.Type.Value, commentsContainer.Id.Value, text);
|
||||
req.Failure += e => Schedule(() =>
|
||||
{
|
||||
ShowLoadingSpinner = false;
|
||||
Logger.Error(e, "Posting comment failed.");
|
||||
});
|
||||
req.Success += cb => Schedule(() =>
|
||||
{
|
||||
ShowLoadingSpinner = false;
|
||||
Current.Value = string.Empty;
|
||||
OnPost?.Invoke(cb);
|
||||
});
|
||||
api.Queue(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,11 @@ namespace osu.Game.Overlays.Comments
|
||||
/// </summary>
|
||||
public bool WasDeleted { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tracks this comment's level of nesting. 0 means that this comment has no parents.
|
||||
/// </summary>
|
||||
public int Level { get; private set; }
|
||||
|
||||
private FillFlowContainer childCommentsVisibilityContainer = null!;
|
||||
private FillFlowContainer childCommentsContainer = null!;
|
||||
private LoadRepliesButton loadRepliesButton = null!;
|
||||
@ -70,7 +75,7 @@ namespace osu.Game.Overlays.Comments
|
||||
private GridContainer content = null!;
|
||||
private VotePill votePill = null!;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
[Resolved]
|
||||
private IDialogOverlay? dialogOverlay { get; set; }
|
||||
|
||||
[Resolved]
|
||||
@ -79,7 +84,7 @@ namespace osu.Game.Overlays.Comments
|
||||
[Resolved]
|
||||
private GameHost host { get; set; } = null!;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
[Resolved]
|
||||
private OnScreenDisplay? onScreenDisplay { get; set; }
|
||||
|
||||
public DrawableComment(Comment comment)
|
||||
@ -88,12 +93,16 @@ namespace osu.Game.Overlays.Comments
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
private void load(OverlayColourProvider colourProvider, DrawableComment? parentComment)
|
||||
{
|
||||
LinkFlowContainer username;
|
||||
FillFlowContainer info;
|
||||
CommentMarkdownContainer message;
|
||||
|
||||
Level = parentComment?.Level + 1 ?? 0;
|
||||
|
||||
float childrenPadding = Level < 6 ? 20 : 5;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
InternalChildren = new Drawable[]
|
||||
@ -248,7 +257,7 @@ namespace osu.Game.Overlays.Comments
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Padding = new MarginPadding { Left = 20 },
|
||||
Padding = new MarginPadding { Left = childrenPadding },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
childCommentsContainer = new FillFlowContainer
|
||||
|
110
osu.Game/Overlays/Profile/Header/Components/ExtendedDetails.cs
Normal file
110
osu.Game/Overlays/Profile/Header/Components/ExtendedDetails.cs
Normal file
@ -0,0 +1,110 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
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.Resources.Localisation.Web;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
public partial class ExtendedDetails : CompositeDrawable
|
||||
{
|
||||
public Bindable<UserProfileData?> User { get; } = new Bindable<UserProfileData?>();
|
||||
|
||||
private SpriteText rankedScore = null!;
|
||||
private SpriteText hitAccuracy = null!;
|
||||
private SpriteText playCount = null!;
|
||||
private SpriteText totalScore = null!;
|
||||
private SpriteText totalHits = null!;
|
||||
private SpriteText maximumCombo = null!;
|
||||
private SpriteText replaysWatched = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
var font = OsuFont.Default.With(size: 12);
|
||||
const float vertical_spacing = 4;
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
// this should really be a grid, but trying to avoid one to avoid the performance hit.
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(20, 0),
|
||||
Children = new[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
Name = @"Labels",
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, vertical_spacing),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsRankedScore },
|
||||
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsHitAccuracy },
|
||||
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsPlayCount },
|
||||
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsTotalScore },
|
||||
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsTotalHits },
|
||||
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsMaximumCombo },
|
||||
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsReplaysWatchedByOthers },
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Name = @"Values",
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, vertical_spacing),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
rankedScore = new OsuSpriteText { Font = font },
|
||||
hitAccuracy = new OsuSpriteText { Font = font },
|
||||
playCount = new OsuSpriteText { Font = font },
|
||||
totalScore = new OsuSpriteText { Font = font },
|
||||
totalHits = new OsuSpriteText { Font = font },
|
||||
maximumCombo = new OsuSpriteText { Font = font },
|
||||
replaysWatched = new OsuSpriteText { Font = font },
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
User.BindValueChanged(user => updateStatistics(user.NewValue?.User.Statistics), true);
|
||||
}
|
||||
|
||||
private void updateStatistics(UserStatistics? statistics)
|
||||
{
|
||||
if (statistics == null)
|
||||
{
|
||||
Alpha = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
Alpha = 1;
|
||||
|
||||
rankedScore.Text = statistics.RankedScore.ToLocalisableString(@"N0");
|
||||
hitAccuracy.Text = statistics.DisplayAccuracy;
|
||||
playCount.Text = statistics.PlayCount.ToLocalisableString(@"N0");
|
||||
totalScore.Text = statistics.TotalScore.ToLocalisableString(@"N0");
|
||||
totalHits.Text = statistics.TotalHits.ToLocalisableString(@"N0");
|
||||
maximumCombo.Text = statistics.MaxCombo.ToLocalisableString(@"N0");
|
||||
replaysWatched.Text = statistics.ReplaysWatched.ToLocalisableString(@"N0");
|
||||
}
|
||||
}
|
||||
}
|
186
osu.Game/Overlays/Profile/Header/Components/MainDetails.cs
Normal file
186
osu.Game/Overlays/Profile/Header/Components/MainDetails.cs
Normal file
@ -0,0 +1,186 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
public partial class MainDetails : CompositeDrawable
|
||||
{
|
||||
private readonly Dictionary<ScoreRank, ScoreRankInfo> scoreRankInfos = new Dictionary<ScoreRank, ScoreRankInfo>();
|
||||
private ProfileValueDisplay medalInfo = null!;
|
||||
private ProfileValueDisplay ppInfo = null!;
|
||||
private ProfileValueDisplay detailGlobalRank = null!;
|
||||
private ProfileValueDisplay detailCountryRank = null!;
|
||||
private RankGraph rankGraph = null!;
|
||||
|
||||
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
AutoSizeDuration = 200,
|
||||
AutoSizeEasing = Easing.OutQuint,
|
||||
Masking = true,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 15),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(20),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
detailGlobalRank = new ProfileValueDisplay(true)
|
||||
{
|
||||
Title = UsersStrings.ShowRankGlobalSimple,
|
||||
},
|
||||
detailCountryRank = new ProfileValueDisplay(true)
|
||||
{
|
||||
Title = UsersStrings.ShowRankCountrySimple,
|
||||
},
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 60,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
rankGraph = new RankGraph
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
medalInfo = new ProfileValueDisplay
|
||||
{
|
||||
Title = UsersStrings.ShowStatsMedals,
|
||||
},
|
||||
ppInfo = new ProfileValueDisplay
|
||||
{
|
||||
Title = "pp",
|
||||
},
|
||||
new TotalPlayTime
|
||||
{
|
||||
User = { BindTarget = User }
|
||||
},
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new[]
|
||||
{
|
||||
scoreRankInfos[ScoreRank.XH] = new ScoreRankInfo(ScoreRank.XH),
|
||||
scoreRankInfos[ScoreRank.X] = new ScoreRankInfo(ScoreRank.X),
|
||||
scoreRankInfos[ScoreRank.SH] = new ScoreRankInfo(ScoreRank.SH),
|
||||
scoreRankInfos[ScoreRank.S] = new ScoreRankInfo(ScoreRank.S),
|
||||
scoreRankInfos[ScoreRank.A] = new ScoreRankInfo(ScoreRank.A),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
User.BindValueChanged(e => updateDisplay(e.NewValue), true);
|
||||
}
|
||||
|
||||
private void updateDisplay(UserProfileData? data)
|
||||
{
|
||||
var user = data?.User;
|
||||
|
||||
medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0";
|
||||
ppInfo.Content = user?.Statistics?.PP?.ToLocalisableString("#,##0") ?? (LocalisableString)"0";
|
||||
|
||||
foreach (var scoreRankInfo in scoreRankInfos)
|
||||
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
|
||||
|
||||
detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
|
||||
rankGraph.Statistics.Value = user?.Statistics;
|
||||
}
|
||||
|
||||
private partial class ScoreRankInfo : CompositeDrawable
|
||||
{
|
||||
private readonly OsuSpriteText rankCount;
|
||||
|
||||
public int RankCount
|
||||
{
|
||||
set => rankCount.Text = value.ToLocalisableString("#,##0");
|
||||
}
|
||||
|
||||
public ScoreRankInfo(ScoreRank rank)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 44,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new DrawableRank(rank)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 22,
|
||||
},
|
||||
rankCount = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,33 +1,17 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Overlays.Profile.Header.Components;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Profile.Header
|
||||
{
|
||||
public partial class DetailHeaderContainer : CompositeDrawable
|
||||
{
|
||||
private readonly Dictionary<ScoreRank, ScoreRankInfo> scoreRankInfos = new Dictionary<ScoreRank, ScoreRankInfo>();
|
||||
private ProfileValueDisplay medalInfo = null!;
|
||||
private ProfileValueDisplay ppInfo = null!;
|
||||
private ProfileValueDisplay detailGlobalRank = null!;
|
||||
private ProfileValueDisplay detailCountryRank = null!;
|
||||
private RankGraph rankGraph = null!;
|
||||
|
||||
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -35,8 +19,6 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
{
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
User.ValueChanged += e => updateDisplay(e.NewValue);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
@ -44,149 +26,52 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
new FillFlowContainer
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
AutoSizeDuration = 200,
|
||||
AutoSizeEasing = Easing.OutQuint,
|
||||
Masking = true,
|
||||
Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 },
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 15),
|
||||
Children = new Drawable[]
|
||||
Child = new GridContainer
|
||||
{
|
||||
new FillFlowContainer
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(20),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
detailGlobalRank = new ProfileValueDisplay(true)
|
||||
{
|
||||
Title = UsersStrings.ShowRankGlobalSimple,
|
||||
},
|
||||
detailCountryRank = new ProfileValueDisplay(true)
|
||||
{
|
||||
Title = UsersStrings.ShowRankCountrySimple,
|
||||
},
|
||||
}
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
new Container
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 60,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
rankGraph = new RankGraph
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
}
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
new Container
|
||||
Content = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
new MainDetails
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
User = { BindTarget = User }
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 2,
|
||||
Colour = colourProvider.Background6,
|
||||
Margin = new MarginPadding { Horizontal = 15 }
|
||||
},
|
||||
new ExtendedDetails
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
medalInfo = new ProfileValueDisplay
|
||||
{
|
||||
Title = UsersStrings.ShowStatsMedals,
|
||||
},
|
||||
ppInfo = new ProfileValueDisplay
|
||||
{
|
||||
Title = "pp",
|
||||
},
|
||||
new TotalPlayTime
|
||||
{
|
||||
User = { BindTarget = User }
|
||||
},
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new[]
|
||||
{
|
||||
scoreRankInfos[ScoreRank.XH] = new ScoreRankInfo(ScoreRank.XH),
|
||||
scoreRankInfos[ScoreRank.X] = new ScoreRankInfo(ScoreRank.X),
|
||||
scoreRankInfos[ScoreRank.SH] = new ScoreRankInfo(ScoreRank.SH),
|
||||
scoreRankInfos[ScoreRank.S] = new ScoreRankInfo(ScoreRank.S),
|
||||
scoreRankInfos[ScoreRank.A] = new ScoreRankInfo(ScoreRank.A),
|
||||
}
|
||||
User = { BindTarget = User }
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private void updateDisplay(UserProfileData? data)
|
||||
{
|
||||
var user = data?.User;
|
||||
|
||||
medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0";
|
||||
ppInfo.Content = user?.Statistics?.PP?.ToLocalisableString("#,##0") ?? (LocalisableString)"0";
|
||||
|
||||
foreach (var scoreRankInfo in scoreRankInfos)
|
||||
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
|
||||
|
||||
detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
|
||||
rankGraph.Statistics.Value = user?.Statistics;
|
||||
}
|
||||
|
||||
private partial class ScoreRankInfo : CompositeDrawable
|
||||
{
|
||||
private readonly OsuSpriteText rankCount;
|
||||
|
||||
public int RankCount
|
||||
{
|
||||
set => rankCount.Text = value.ToLocalisableString("#,##0");
|
||||
}
|
||||
|
||||
public ScoreRankInfo(ScoreRank rank)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 44,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new DrawableRank(rank)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 22,
|
||||
},
|
||||
rankCount = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,18 +5,15 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays.Profile.Header.Components;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osuTK;
|
||||
|
||||
@ -38,7 +35,6 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
private OsuSpriteText titleText = null!;
|
||||
private UpdateableFlag userFlag = null!;
|
||||
private OsuSpriteText userCountryText = null!;
|
||||
private FillFlowContainer userStats = null!;
|
||||
private GroupBadgeFlow groupBadgeFlow = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -164,16 +160,6 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
}
|
||||
}
|
||||
},
|
||||
userStats = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 300,
|
||||
Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN },
|
||||
Padding = new MarginPadding { Vertical = 15 },
|
||||
Spacing = new Vector2(0, 2)
|
||||
}
|
||||
};
|
||||
|
||||
User.BindValueChanged(user => updateUser(user.NewValue));
|
||||
@ -192,43 +178,6 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
titleText.Text = user?.Title ?? string.Empty;
|
||||
titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff");
|
||||
groupBadgeFlow.User.Value = user;
|
||||
|
||||
userStats.Clear();
|
||||
|
||||
if (user?.Statistics != null)
|
||||
{
|
||||
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsRankedScore, user.Statistics.RankedScore.ToLocalisableString("#,##0")));
|
||||
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsHitAccuracy, user.Statistics.DisplayAccuracy));
|
||||
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsPlayCount, user.Statistics.PlayCount.ToLocalisableString("#,##0")));
|
||||
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsTotalScore, user.Statistics.TotalScore.ToLocalisableString("#,##0")));
|
||||
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsTotalHits, user.Statistics.TotalHits.ToLocalisableString("#,##0")));
|
||||
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsMaximumCombo, user.Statistics.MaxCombo.ToLocalisableString("#,##0")));
|
||||
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsReplaysWatchedByOthers, user.Statistics.ReplaysWatched.ToLocalisableString("#,##0")));
|
||||
}
|
||||
}
|
||||
|
||||
private partial class UserStatsLine : Container
|
||||
{
|
||||
public UserStatsLine(LocalisableString left, LocalisableString right)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 15),
|
||||
Text = left,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold),
|
||||
Text = right,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,14 +15,12 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
|
||||
[Resolved]
|
||||
private IGameplayClock gameplayClock { get; set; } = null!;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private DrawableRuleset? drawableRuleset { get; set; }
|
||||
[Resolved]
|
||||
private IFrameStableClock? frameStableClock { get; set; }
|
||||
|
||||
public int Value { get; private set; }
|
||||
|
||||
// Even though `FrameStabilityContainer` caches as a `GameplayClock`, we need to check it directly via `drawableRuleset`
|
||||
// as this calculator is not contained within the `FrameStabilityContainer` and won't see the dependency.
|
||||
private IGameplayClock clock => drawableRuleset?.FrameStableClock ?? gameplayClock;
|
||||
private IGameplayClock clock => frameStableClock ?? gameplayClock;
|
||||
|
||||
public ClicksPerSecondCalculator()
|
||||
{
|
||||
|
@ -0,0 +1,82 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using 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.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
{
|
||||
public partial class JudgementCounter : VisibilityContainer
|
||||
{
|
||||
public BindableBool ShowName = new BindableBool();
|
||||
public Bindable<FillDirection> Direction = new Bindable<FillDirection>();
|
||||
|
||||
public readonly JudgementTally.JudgementCount Result;
|
||||
|
||||
public JudgementCounter(JudgementTally.JudgementCount result) => Result = result;
|
||||
|
||||
public OsuSpriteText ResultName = null!;
|
||||
private FillFlowContainer flowContainer = null!;
|
||||
private JudgementRollingCounter counter = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, IBindable<RulesetInfo> ruleset)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = flowContainer = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
counter = new JudgementRollingCounter
|
||||
{
|
||||
Current = Result.ResultCount
|
||||
},
|
||||
ResultName = new OsuSpriteText
|
||||
{
|
||||
Alpha = 0,
|
||||
Font = OsuFont.Numeric.With(size: 8),
|
||||
Text = ruleset.Value.CreateInstance().GetDisplayNameForHitResult(Result.Type)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var result = Result.Type;
|
||||
|
||||
Colour = result.IsBasic() ? colours.ForHitResult(Result.Type) : !result.IsBonus() ? colours.PurpleLight : colours.PurpleLighter;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
ShowName.BindValueChanged(value =>
|
||||
ResultName.FadeTo(value.NewValue ? 1 : 0, JudgementCounterDisplay.TRANSFORM_DURATION, Easing.OutQuint), true);
|
||||
|
||||
Direction.BindValueChanged(direction =>
|
||||
{
|
||||
flowContainer.Direction = direction.NewValue;
|
||||
changeAnchor(direction.NewValue == FillDirection.Vertical ? Anchor.TopLeft : Anchor.BottomLeft);
|
||||
|
||||
void changeAnchor(Anchor anchor) => counter.Anchor = ResultName.Anchor = counter.Origin = ResultName.Origin = anchor;
|
||||
}, true);
|
||||
|
||||
base.LoadComplete();
|
||||
}
|
||||
|
||||
protected override void PopIn() => this.FadeIn(JudgementCounterDisplay.TRANSFORM_DURATION, Easing.OutQuint);
|
||||
protected override void PopOut() => this.FadeOut(100);
|
||||
|
||||
private sealed partial class JudgementRollingCounter : RollingCounter<int>
|
||||
{
|
||||
protected override OsuSpriteText CreateSpriteText()
|
||||
=> base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true, size: 16));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
{
|
||||
public partial class JudgementCounterDisplay : CompositeDrawable, ISkinnableDrawable
|
||||
{
|
||||
public const int TRANSFORM_DURATION = 250;
|
||||
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
[SettingSource("Display mode")]
|
||||
public Bindable<DisplayMode> Mode { get; set; } = new Bindable<DisplayMode>();
|
||||
|
||||
[SettingSource("Counter direction")]
|
||||
public Bindable<Direction> FlowDirection { get; set; } = new Bindable<Direction>();
|
||||
|
||||
[SettingSource("Show judgement names")]
|
||||
public BindableBool ShowJudgementNames { get; set; } = new BindableBool(true);
|
||||
|
||||
[SettingSource("Show max judgement")]
|
||||
public BindableBool ShowMaxJudgement { get; set; } = new BindableBool(true);
|
||||
|
||||
[Resolved]
|
||||
private JudgementTally tally { get; set; } = null!;
|
||||
|
||||
protected FillFlowContainer<JudgementCounter> CounterFlow = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
InternalChild = CounterFlow = new FillFlowContainer<JudgementCounter>
|
||||
{
|
||||
Direction = getFillDirection(FlowDirection.Value),
|
||||
Spacing = new Vector2(10),
|
||||
AutoSizeAxes = Axes.Both
|
||||
};
|
||||
|
||||
foreach (var result in tally.Results)
|
||||
CounterFlow.Add(createCounter(result));
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
FlowDirection.BindValueChanged(direction =>
|
||||
{
|
||||
var convertedDirection = getFillDirection(direction.NewValue);
|
||||
|
||||
CounterFlow.Direction = convertedDirection;
|
||||
|
||||
foreach (var counter in CounterFlow.Children)
|
||||
counter.Direction.Value = convertedDirection;
|
||||
}, true);
|
||||
|
||||
Mode.BindValueChanged(_ => updateMode(), true);
|
||||
|
||||
ShowMaxJudgement.BindValueChanged(value =>
|
||||
{
|
||||
var firstChild = CounterFlow.Children.FirstOrDefault();
|
||||
firstChild.FadeTo(value.NewValue ? 1 : 0, TRANSFORM_DURATION, Easing.OutQuint);
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void updateMode()
|
||||
{
|
||||
foreach (var counter in CounterFlow.Children)
|
||||
{
|
||||
if (shouldShow(counter))
|
||||
counter.Show();
|
||||
else
|
||||
counter.Hide();
|
||||
}
|
||||
|
||||
bool shouldShow(JudgementCounter counter)
|
||||
{
|
||||
if (counter.Result.Type.IsBasic())
|
||||
return true;
|
||||
|
||||
switch (Mode.Value)
|
||||
{
|
||||
case DisplayMode.Simple:
|
||||
return false;
|
||||
|
||||
case DisplayMode.Normal:
|
||||
return !counter.Result.Type.IsBonus();
|
||||
|
||||
case DisplayMode.All:
|
||||
return true;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private FillDirection getFillDirection(Direction flow)
|
||||
{
|
||||
switch (flow)
|
||||
{
|
||||
case Direction.Horizontal:
|
||||
return FillDirection.Horizontal;
|
||||
|
||||
case Direction.Vertical:
|
||||
return FillDirection.Vertical;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(flow), flow, @"Unsupported direction");
|
||||
}
|
||||
}
|
||||
|
||||
private JudgementCounter createCounter(JudgementTally.JudgementCount info) =>
|
||||
new JudgementCounter(info)
|
||||
{
|
||||
State = { Value = Visibility.Hidden },
|
||||
ShowName = { BindTarget = ShowJudgementNames }
|
||||
};
|
||||
|
||||
public enum DisplayMode
|
||||
{
|
||||
Simple,
|
||||
Normal,
|
||||
All
|
||||
}
|
||||
}
|
||||
}
|
60
osu.Game/Screens/Play/HUD/JudgementCounter/JudgementTally.cs
Normal file
60
osu.Game/Screens/Play/HUD/JudgementCounter/JudgementTally.cs
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
{
|
||||
/// <summary>
|
||||
/// Keeps track of judgements for a current play session, exposing bindable counts which can
|
||||
/// be used for display purposes.
|
||||
/// </summary>
|
||||
public partial class JudgementTally : CompositeDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private ScoreProcessor scoreProcessor { get; set; } = null!;
|
||||
|
||||
public List<JudgementCount> Results = new List<JudgementCount>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IBindable<RulesetInfo> ruleset)
|
||||
{
|
||||
foreach (var result in ruleset.Value.CreateInstance().GetHitResults())
|
||||
{
|
||||
Results.Add(new JudgementCount
|
||||
{
|
||||
Type = result.result,
|
||||
ResultCount = new BindableInt()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
scoreProcessor.NewJudgement += judgement => updateCount(judgement, false);
|
||||
scoreProcessor.JudgementReverted += judgement => updateCount(judgement, true);
|
||||
}
|
||||
|
||||
private void updateCount(JudgementResult judgement, bool revert)
|
||||
{
|
||||
foreach (JudgementCount result in Results.Where(result => result.Type == judgement.Type))
|
||||
result.ResultCount.Value = revert ? result.ResultCount.Value - 1 : result.ResultCount.Value + 1;
|
||||
}
|
||||
|
||||
public struct JudgementCount
|
||||
{
|
||||
public HitResult Type { get; set; }
|
||||
|
||||
public BindableInt ResultCount { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -25,10 +25,15 @@ namespace osu.Game.Screens.Play.HUD
|
||||
[Resolved]
|
||||
protected IGameplayClock GameplayClock { get; private set; } = null!;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private DrawableRuleset? drawableRuleset { get; set; }
|
||||
[Resolved]
|
||||
private IFrameStableClock? frameStableClock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The reference clock is used to accurately tell the current playfield's time (including catch-up lag).
|
||||
/// However, if none is available (i.e. used in tests), we fall back to the gameplay clock.
|
||||
/// </summary>
|
||||
protected IClock FrameStableClock => frameStableClock ?? GameplayClock;
|
||||
|
||||
private IClock? referenceClock;
|
||||
private IEnumerable<HitObject>? objects;
|
||||
|
||||
public IEnumerable<HitObject> Objects
|
||||
@ -58,12 +63,11 @@ namespace osu.Game.Screens.Play.HUD
|
||||
protected virtual void UpdateObjects(IEnumerable<HitObject> objects) { }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(DrawableRuleset? drawableRuleset)
|
||||
{
|
||||
if (drawableRuleset != null)
|
||||
{
|
||||
Objects = drawableRuleset.Objects;
|
||||
referenceClock = drawableRuleset.FrameStableClock;
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,9 +78,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
if (objects == null)
|
||||
return;
|
||||
|
||||
// The reference clock is used to accurately tell the playfield's time. This is obtained from the drawable ruleset.
|
||||
// However, if no drawable ruleset is available (i.e. used in tests), we fall back to the gameplay clock.
|
||||
double currentTime = referenceClock?.CurrentTime ?? GameplayClock.CurrentTime;
|
||||
double currentTime = FrameStableClock.CurrentTime;
|
||||
|
||||
bool isInIntro = currentTime < FirstHitTime;
|
||||
|
||||
|
@ -22,6 +22,7 @@ using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
||||
using osu.Game.Screens.Play.HUD.JudgementCounter;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osu.Game.Localisation;
|
||||
@ -59,6 +60,9 @@ namespace osu.Game.Screens.Play
|
||||
[Cached]
|
||||
private readonly ClicksPerSecondCalculator clicksPerSecondCalculator;
|
||||
|
||||
[Cached]
|
||||
private readonly JudgementTally tally;
|
||||
|
||||
public Bindable<bool> ShowHealthBar = new Bindable<bool>(true);
|
||||
|
||||
private readonly DrawableRuleset drawableRuleset;
|
||||
@ -104,6 +108,8 @@ namespace osu.Game.Screens.Play
|
||||
Children = new Drawable[]
|
||||
{
|
||||
CreateFailingLayer(),
|
||||
//Needs to be initialized before skinnable drawables.
|
||||
tally = new JudgementTally(),
|
||||
mainComponents = new MainComponentsContainer
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
|
@ -309,6 +309,8 @@ namespace osu.Game.Screens.Play
|
||||
});
|
||||
}
|
||||
|
||||
dependencies.CacheAs(DrawableRuleset.FrameStableClock);
|
||||
|
||||
// add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components.
|
||||
// also give the overlays the ruleset skin provider to allow rulesets to potentially override HUD elements (used to disable combo counters etc.)
|
||||
// we may want to limit this in the future to disallow rulesets from outright replacing elements the user expects to be there.
|
||||
|
@ -596,6 +596,9 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
public void FlushPendingFilterOperations()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
|
||||
if (PendingFilter?.Completed == false)
|
||||
{
|
||||
applyActiveCriteria(false);
|
||||
|
@ -863,6 +863,7 @@ namespace osu.Game.Screens.Select
|
||||
// if we have a pending filter operation, we want to run it now.
|
||||
// it could change selection (ie. if the ruleset has been changed).
|
||||
Carousel.FlushPendingFilterOperations();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user