1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-17 19:04:00 +08:00

Compare commits

...

61 Commits

27 changed files with 735 additions and 229 deletions
+2 -1
View File
@@ -64,7 +64,8 @@ jobs:
matrix:
os:
- { prettyname: Windows, fullname: windows-latest }
- { prettyname: macOS, fullname: macos-latest }
# macOS runner performance has gotten unbearably slow so let's turn them off temporarily.
# - { prettyname: macOS, fullname: macos-latest }
- { prettyname: Linux, fullname: ubuntu-latest }
threadingMode: ['SingleThread', 'MultiThreaded']
timeout-minutes: 120
+1 -1
View File
@@ -55,7 +55,7 @@ When in doubt, it's probably best to start with a discussion first. We will esca
While pull requests from unaffiliated contributors are welcome, please note that due to significant community interest and limited review throughput, the core team's primary focus is on the issues which are currently [on the roadmap](https://github.com/orgs/ppy/projects/7/views/6). Reviewing PRs that fall outside of the scope of the roadmap is done on a best-effort basis, so please be aware that it may take a while before a core maintainer gets around to review your change.
The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. We also have a [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label, although from experience it is not used very often, as it is relatively rare that we can spot an issue that will definitively be a good first issue for a new contributor regardless of their programming experience.
The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. We also have a [`good first issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label, although from experience it is not used very often, as it is relatively rare that we can spot an issue that will definitively be a good first issue for a new contributor regardless of their programming experience.
In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive.
@@ -193,7 +193,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private void addPart(Vector2 screenSpacePosition)
{
parts[currentIndex].Position = screenSpacePosition;
parts[currentIndex].Position = ToLocalSpace(screenSpacePosition);
parts[currentIndex].Time = time + 1;
++parts[currentIndex].InvalidationID;
@@ -285,9 +285,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
if (time - part.Time >= 1)
continue;
Vector2 screenSpacePos = Source.ToScreenSpace(part.Position);
vertexBatch.Add(new TexturedTrailVertex
{
Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y + size.Y * (1 - originPosition.Y)),
Position = new Vector2(screenSpacePos.X - size.X * originPosition.X, screenSpacePos.Y + size.Y * (1 - originPosition.Y)),
TexturePosition = textureRect.BottomLeft,
TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
@@ -296,7 +298,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex
{
Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y + size.Y * (1 - originPosition.Y)),
Position = new Vector2(screenSpacePos.X + size.X * (1 - originPosition.X), screenSpacePos.Y + size.Y * (1 - originPosition.Y)),
TexturePosition = textureRect.BottomRight,
TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.BottomRight.Linear,
@@ -305,7 +307,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex
{
Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y - size.Y * originPosition.Y),
Position = new Vector2(screenSpacePos.X + size.X * (1 - originPosition.X), screenSpacePos.Y - size.Y * originPosition.Y),
TexturePosition = textureRect.TopRight,
TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.TopRight.Linear,
@@ -314,7 +316,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex
{
Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y - size.Y * originPosition.Y),
Position = new Vector2(screenSpacePos.X - size.X * originPosition.X, screenSpacePos.Y - size.Y * originPosition.Y),
TexturePosition = textureRect.TopLeft,
TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.TopLeft.Linear,
@@ -19,6 +19,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
<ProjectReference Include="..\osu.Game.Tests\osu.Game.Tests.csproj" />
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,70 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osu.Game.Screens.Edit.GameplayTest;
using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Tests.Visual;
using osuTK.Input;
namespace osu.Game.Rulesets.Taiko.Tests.Editor
{
public partial class TestSceneTaikoEditorTestGameplay : EditorTestScene
{
protected override bool IsolateSavingFromDatabase => false;
protected override Ruleset CreateEditorRuleset() => new TaikoRuleset();
[Resolved]
private OsuGameBase game { get; set; } = null!;
[Resolved]
private BeatmapManager beatmaps { get; set; } = null!;
private BeatmapSetInfo importedBeatmapSet = null!;
public override void SetUpSteps()
{
AddStep("import test beatmap", () => importedBeatmapSet = BeatmapImportHelper.LoadOszIntoOsu(game).GetResultSafely());
base.SetUpSteps();
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
=> beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 1));
[Test]
public void TestBasicGameplayTest()
{
AddStep("add objects", () =>
{
EditorBeatmap.Clear();
EditorBeatmap.Add(new Swell { StartTime = 500, EndTime = 1500 });
EditorBeatmap.Add(new Hit { StartTime = 3000 });
});
AddStep("seek to 250", () => EditorClock.Seek(250));
AddUntilStep("wait for seek", () => EditorClock.CurrentTime, () => Is.EqualTo(250));
AddStep("click test gameplay button", () =>
{
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
InputManager.MoveMouseTo(button);
InputManager.Click(MouseButton.Left);
});
AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveRequiredPopupDialog);
AddStep("save changes", () => DialogOverlay.CurrentDialog!.PerformOkAction());
AddUntilStep("player pushed", () => Stack.CurrentScreen is EditorPlayer);
AddUntilStep("wait for return to editor", () => Stack.CurrentScreen is Screens.Edit.Editor);
}
}
}
@@ -315,10 +315,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
hitObjectContainer.Add(drawableSwell);
});
// You might think that this should be a SwellTick since we're before the swell, but SwellTicks get no StartTime (ie. they are zero).
// This works fine in gameplay because they are judged whenever the user pressed, rather than being timed hits.
// But for sample playback purposes they can be ignored as noise.
AddAssert("most valid object is swell", () => triggerSource.GetMostValidObject(), Is.InstanceOf<Swell>);
AddAssert("most valid object is swell tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf<SwellTick>);
checkSamples(HitType.Centre, false, HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK);
checkSamples(HitType.Rim, false, HitSampleInfo.HIT_CLAP, SampleControlPoint.DEFAULT_BANK);
@@ -352,10 +349,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
hitObjectContainer.Add(drawableSwell);
});
// You might think that this should be a SwellTick since we're before the swell, but SwellTicks get no StartTime (ie. they are zero).
// This works fine in gameplay because they are judged whenever the user pressed, rather than being timed hits.
// But for sample playback purposes they can be ignored as noise.
AddAssert("most valid object is swell", () => triggerSource.GetMostValidObject(), Is.InstanceOf<Swell>);
AddAssert("most valid object is swell tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf<SwellTick>);
checkSamples(HitType.Centre, false, HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_DRUM);
checkSamples(HitType.Rim, false, HitSampleInfo.HIT_CLAP, HitSampleInfo.BANK_DRUM);
@@ -11,5 +11,6 @@
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
<ProjectReference Include="..\osu.Game.Tests\osu.Game.Tests.csproj" />
</ItemGroup>
</Project>
+1
View File
@@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
cancellationToken.ThrowIfCancellationRequested();
AddNested(new SwellTick
{
StartTime = StartTime,
Samples = Samples
});
}
@@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
@@ -17,14 +18,14 @@ namespace osu.Game.Tests.Visual.DailyChallenge
{
public partial class TestSceneDailyChallengeEventFeed : OsuTestScene
{
private DailyChallengeEventFeed feed = null!;
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
[Test]
public void TestBasicAppearance()
[SetUpSteps]
public void SetUpSteps()
{
DailyChallengeEventFeed feed = null!;
AddStep("create content", () => Children = new Drawable[]
{
new Box
@@ -35,22 +36,28 @@ namespace osu.Game.Tests.Visual.DailyChallenge
feed = new DailyChallengeEventFeed
{
RelativeSizeAxes = Axes.Both,
Height = 0.3f,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
});
AddSliderStep("adjust width", 0.1f, 1, 1, width =>
{
if (feed.IsNotNull())
feed.Width = width;
});
AddSliderStep("adjust height", 0.1f, 1, 1, height =>
AddSliderStep("adjust height", 0.1f, 1, 0.3f, height =>
{
if (feed.IsNotNull())
feed.Height = height;
});
}
AddStep("add normal score", () =>
[Test]
public void TestBasicAppearance()
{
AddRepeatStep("add normal score", () =>
{
var ev = new NewScoreEvent(1, new APIUser
{
@@ -60,9 +67,9 @@ namespace osu.Game.Tests.Visual.DailyChallenge
}, RNG.Next(1_000_000), null);
feed.AddNewScore(ev);
});
}, 50);
AddStep("add new user best", () =>
AddRepeatStep("add new user best", () =>
{
var ev = new NewScoreEvent(1, new APIUser
{
@@ -75,9 +82,9 @@ namespace osu.Game.Tests.Visual.DailyChallenge
testScore.TotalScore = RNG.Next(1_000_000);
feed.AddNewScore(ev);
});
}, 50);
AddStep("add top 10 score", () =>
AddRepeatStep("add top 10 score", () =>
{
var ev = new NewScoreEvent(1, new APIUser
{
@@ -87,6 +94,25 @@ namespace osu.Game.Tests.Visual.DailyChallenge
}, RNG.Next(1_000_000), RNG.Next(1, 10));
feed.AddNewScore(ev);
}, 50);
}
[Test]
public void TestMassAdd()
{
AddStep("add 1000 scores at once", () =>
{
for (int i = 0; i < 1000; i++)
{
var ev = new NewScoreEvent(1, new APIUser
{
Id = 2,
Username = "peppy",
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
}, RNG.Next(1_000_000), null);
feed.AddNewScore(ev);
}
});
}
}
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.DailyChallenge;
using osu.Game.Screens.OnlinePlay.DailyChallenge.Events;
@@ -61,6 +62,8 @@ namespace osu.Game.Tests.Visual.DailyChallenge
breakdown.AddNewScore(ev);
});
AddStep("set user score", () => breakdown.UserBestScore.Value = new MultiplayerScore { TotalScore = RNG.Next(1_000_000) });
AddStep("unset user score", () => breakdown.UserBestScore.Value = null);
}
}
}
@@ -0,0 +1,86 @@
// 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.Framework.Allocation;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.DailyChallenge;
using osu.Game.Screens.OnlinePlay.DailyChallenge.Events;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Visual.DailyChallenge
{
public partial class TestSceneDailyChallengeTotalsDisplay : OsuTestScene
{
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
[Test]
public void TestBasicAppearance()
{
DailyChallengeTotalsDisplay totals = null!;
AddStep("create content", () => Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background4,
},
totals = new DailyChallengeTotalsDisplay
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
});
AddSliderStep("adjust width", 0.1f, 1, 1, width =>
{
if (totals.IsNotNull())
totals.Width = width;
});
AddSliderStep("adjust height", 0.1f, 1, 1, height =>
{
if (totals.IsNotNull())
totals.Height = height;
});
AddStep("set counts", () => totals.SetInitialCounts(totalPassCount: 9650, cumulativeTotalScore: 10_000_000_000));
AddStep("add normal score", () =>
{
var ev = new NewScoreEvent(1, new APIUser
{
Id = 2,
Username = "peppy",
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
}, RNG.Next(1_000_000), null);
totals.AddNewScore(ev);
});
AddStep("spam scores", () =>
{
for (int i = 0; i < 1000; ++i)
{
var ev = new NewScoreEvent(1, new APIUser
{
Id = 2,
Username = "peppy",
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
}, RNG.Next(1_000_000), RNG.Next(11, 1000));
var testScore = TestResources.CreateTestScoreInfo();
testScore.TotalScore = RNG.Next(1_000_000);
totals.AddNewScore(ev);
}
});
}
}
}
@@ -117,6 +117,9 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("state entered downloading", () => downloadStarted);
AddUntilStep("state left downloading", () => downloadFinished);
AddStep("change score to null", () => downloadButton.Score.Value = null);
AddUntilStep("state changed to unknown", () => downloadButton.State.Value, () => Is.EqualTo(DownloadState.Unknown));
}
[Test]
@@ -76,6 +76,24 @@ namespace osu.Game.Tests.Visual.UserInterface
InputManager.Click(MouseButton.Left);
});
AddAssert("text selected", () => numberBoxes.First().SelectedText == "987654321");
AddStep("click away", () =>
{
InputManager.MoveMouseTo(Vector2.Zero);
InputManager.Click(MouseButton.Left);
});
Drawable textContainer = null!;
AddStep("move mouse to end of text", () =>
{
textContainer = numberBoxes.First().ChildrenOfType<Container>().ElementAt(1);
InputManager.MoveMouseTo(textContainer.ScreenSpaceDrawQuad.TopRight);
});
AddStep("hold mouse", () => InputManager.PressButton(MouseButton.Left));
AddStep("drag to half", () => InputManager.MoveMouseTo(textContainer.ScreenSpaceDrawQuad.BottomRight - new Vector2(textContainer.ScreenSpaceDrawQuad.Width / 2 + 1f, 0)));
AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("half text selected", () => numberBoxes.First().SelectedText == "54321");
}
private void clearTextboxes(IEnumerable<OsuTextBox> textBoxes) => AddStep("clear textbox", () => textBoxes.ForEach(textBox => textBox.Text = null));
@@ -261,7 +261,8 @@ namespace osu.Game.Graphics.UserInterface
base.OnFocus(e);
if (SelectAllOnFocus)
// we may become focused from an ongoing drag operation, we don't want to overwrite selection in that case.
if (SelectAllOnFocus && string.IsNullOrEmpty(SelectedText))
SelectAll();
}
@@ -25,5 +25,17 @@ namespace osu.Game.Online.Metadata
/// </summary>
[Key(1)]
public long[] TotalScoreDistribution { get; set; } = new long[TOTAL_SCORE_DISTRIBUTION_BINS];
/// <summary>
/// The cumulative total of all passing scores (across all users) for the playlist item so far.
/// </summary>
[Key(2)]
public long CumulativeScore { get; set; }
/// <summary>
/// The last score to have been processed into provided statistics. Generally only for server-side accounting purposes.
/// </summary>
[Key(3)]
public ulong LastProcessedScoreID { get; set; }
}
}
+13
View File
@@ -46,6 +46,9 @@ namespace osu.Game.Online.Rooms
[JsonProperty("statistics")]
public Dictionary<HitResult, int> Statistics = new Dictionary<HitResult, int>();
[JsonProperty("maximum_statistics")]
public Dictionary<HitResult, int> MaximumStatistics = new Dictionary<HitResult, int>();
[JsonProperty("passed")]
public bool Passed { get; set; }
@@ -58,9 +61,15 @@ namespace osu.Game.Online.Rooms
[JsonProperty("position")]
public int? Position { get; set; }
[JsonProperty("pp")]
public double? PP { get; set; }
[JsonProperty("has_replay")]
public bool HasReplay { get; set; }
[JsonProperty("ranked")]
public bool Ranked { get; set; }
/// <summary>
/// Any scores in the room around this score.
/// </summary>
@@ -83,13 +92,17 @@ namespace osu.Game.Online.Rooms
MaxCombo = MaxCombo,
BeatmapInfo = beatmap,
Ruleset = rulesets.GetRuleset(playlistItem.RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {playlistItem.RulesetID} not found locally"),
Passed = Passed,
Statistics = Statistics,
MaximumStatistics = MaximumStatistics,
User = User,
Accuracy = Accuracy,
Date = EndedAt,
HasOnlineReplay = HasReplay,
Rank = Rank,
Mods = Mods?.Select(m => m.ToMod(rulesetInstance)).ToArray() ?? Array.Empty<Mod>(),
PP = PP,
Ranked = Ranked,
Position = Position,
};
+17 -4
View File
@@ -63,6 +63,8 @@ using osu.Game.Screens;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Footer;
using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.DailyChallenge;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
@@ -749,23 +751,34 @@ namespace osu.Game
return;
}
// This should be able to be performed from song select, but that is disabled for now
// This should be able to be performed from song select always, but that is disabled for now
// due to the weird decoupled ruleset logic (which can cause a crash in certain filter scenarios).
//
// As a special case, if the beatmap and ruleset already match, allow immediately displaying the score from song select.
// This is guaranteed to not crash, and feels better from a user's perspective (ie. if they are clicking a score in the
// song select leaderboard).
// Similar exemptions are made here for online flows where there are good chances that beatmap and ruleset match
// (playlists / multiplayer / daily challenge).
IEnumerable<Type> validScreens =
Beatmap.Value.BeatmapInfo.Equals(databasedBeatmap) && Ruleset.Value.Equals(databasedScore.ScoreInfo.Ruleset)
? new[] { typeof(SongSelect) }
? new[] { typeof(SongSelect), typeof(OnlinePlayScreen), typeof(DailyChallenge) }
: Array.Empty<Type>();
PerformFromScreen(screen =>
{
Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset}) to match score");
Ruleset.Value = databasedScore.ScoreInfo.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
// some screens (mostly online) disable the ruleset/beatmap bindable.
// attempting to set the ruleset/beatmap in that state will crash.
// however, the `validScreens` pre-check above should ensure that we actually never come from one of those screens
// while simultaneously having mismatched ruleset/beatmap.
// therefore this is just a safety against touching the possibly-disabled bindables if we don't actually have to touch them.
// if it ever fails, then this probably *should* crash anyhow (so that we can fix it).
if (!Ruleset.Value.Equals(databasedScore.ScoreInfo.Ruleset))
Ruleset.Value = databasedScore.ScoreInfo.Ruleset;
if (!Beatmap.Value.BeatmapInfo.Equals(databasedBeatmap))
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
switch (presentType)
{
@@ -21,8 +21,8 @@ namespace osu.Game.Screens.OnlinePlay.Components
private void load(OnlinePlayBeatmapAvailabilityTracker beatmapTracker)
{
availability.BindTo(beatmapTracker.Availability);
availability.BindValueChanged(_ => updateState());
Enabled.BindValueChanged(_ => updateState(), true);
}
@@ -33,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
get
{
if (Enabled.Value)
if (base.Enabled.Value)
return string.Empty;
if (availability.Value.State != DownloadState.LocallyAvailable)
@@ -13,9 +13,11 @@ using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics.Containers;
@@ -39,7 +41,8 @@ using osuTK;
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
public partial class DailyChallenge : OsuScreen
[Cached(typeof(IPreviewTrackOwner))]
public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner
{
private readonly Room room;
private readonly PlaylistItem playlistItem;
@@ -58,6 +61,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
private IDisposable? userModsSelectOverlayRegistration;
private DailyChallengeScoreBreakdown breakdown = null!;
private DailyChallengeTotalsDisplay totals = null!;
private DailyChallengeEventFeed feed = null!;
[Cached]
@@ -90,8 +94,13 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
[Resolved]
protected IAPIProvider API { get; private set; } = null!;
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; } = null!;
public override bool DisallowExternalBeatmapRulesetChanges => true;
public override bool? ApplyModTrackAdjustments => true;
public DailyChallenge(Room room)
{
this.room = room;
@@ -126,169 +135,174 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
RelativeSizeAxes = Axes.Both,
},
new Header(ButtonSystemStrings.DailyChallenge.ToSentence(), null),
new GridContainer
new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
Child = new GridContainer
{
Horizontal = WaveOverlayContainer.WIDTH_PADDING,
Top = Header.HEIGHT,
},
RowDimensions =
[
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Absolute, 10),
new Dimension(),
new Dimension(GridSizeMode.Absolute, 30),
new Dimension(GridSizeMode.Absolute, 50)
],
Content = new[]
{
new Drawable[]
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
new DrawableRoomPlaylistItem(playlistItem)
{
RelativeSizeAxes = Axes.X,
AllowReordering = false,
Scale = new Vector2(1.4f),
Width = 1 / 1.4f,
}
Horizontal = WaveOverlayContainer.WIDTH_PADDING,
Top = Header.HEIGHT,
},
null,
RowDimensions =
[
new Container
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Absolute, 10),
new Dimension(),
new Dimension(GridSizeMode.Absolute, 30),
new Dimension(GridSizeMode.Absolute, 50)
],
Content = new[]
{
new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 10,
Children = new Drawable[]
new DrawableRoomPlaylistItem(playlistItem)
{
new Box
RelativeSizeAxes = Axes.X,
AllowReordering = false,
Scale = new Vector2(1.4f),
Width = 1 / 1.4f,
}
},
null,
[
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 10,
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background4,
},
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(10),
ColumnDimensions =
[
new Dimension(),
new Dimension(GridSizeMode.Absolute, 10),
new Dimension(),
new Dimension(GridSizeMode.Absolute, 10),
new Dimension()
],
Content = new[]
new Box
{
new Drawable?[]
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background4,
},
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(10),
ColumnDimensions =
[
new Dimension(),
new Dimension(GridSizeMode.Absolute, 10),
new Dimension(),
new Dimension(GridSizeMode.Absolute, 10),
new Dimension()
],
Content = new[]
{
new GridContainer
new Drawable?[]
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RowDimensions =
[
new Dimension(),
new Dimension()
],
Content = new[]
new GridContainer
{
new Drawable[]
{
new DailyChallengeCarousel
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new DailyChallengeTimeRemainingRing(),
breakdown = new DailyChallengeScoreBreakdown(),
}
}
},
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RowDimensions =
[
feed = new DailyChallengeEventFeed
{
RelativeSizeAxes = Axes.Both,
PresentScore = presentScore
}
new Dimension(),
new Dimension()
],
},
},
null,
// Middle column (leaderboard)
leaderboard = new DailyChallengeLeaderboard(room, playlistItem)
{
RelativeSizeAxes = Axes.Both,
PresentScore = presentScore,
},
// Spacer
null,
// Main right column
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
Content = new[]
{
new SectionHeader("Chat")
new Drawable[]
{
new DailyChallengeCarousel
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new DailyChallengeTimeRemainingRing(),
breakdown = new DailyChallengeScoreBreakdown(),
totals = new DailyChallengeTotalsDisplay(),
}
}
},
[
feed = new DailyChallengeEventFeed
{
RelativeSizeAxes = Axes.Both,
PresentScore = presentScore
}
],
},
[new MatchChatDisplay(room) { RelativeSizeAxes = Axes.Both }]
},
RowDimensions =
[
new Dimension(GridSizeMode.AutoSize),
new Dimension()
]
},
null,
// Middle column (leaderboard)
leaderboard = new DailyChallengeLeaderboard(room, playlistItem)
{
RelativeSizeAxes = Axes.Both,
PresentScore = presentScore,
},
// Spacer
null,
// Main right column
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new SectionHeader("Chat")
},
[new MatchChatDisplay(room) { RelativeSizeAxes = Axes.Both }]
},
RowDimensions =
[
new Dimension(GridSizeMode.AutoSize),
new Dimension()
]
},
}
}
}
}
}
}
],
null,
[
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
],
null,
[
new Container
{
Horizontal = -WaveOverlayContainer.WIDTH_PADDING,
},
Children = new Drawable[]
{
new Box
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background5,
Horizontal = -WaveOverlayContainer.WIDTH_PADDING,
},
footerButtons = new FillFlowContainer
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Padding = new MarginPadding(5),
Spacing = new Vector2(10),
Children = new Drawable[]
new Box
{
new PlaylistsReadyButton
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background5,
},
footerButtons = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Padding = new MarginPadding(5),
Spacing = new Vector2(10),
Children = new Drawable[]
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Size = new Vector2(250, 1),
Action = startPlay
new PlaylistsReadyButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Size = new Vector2(250, 1),
Action = startPlay
}
}
}
},
},
}
}
}
],
],
}
}
}
}
@@ -296,6 +310,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
LoadComponent(userModsSelectOverlay = new RoomModSelectOverlay
{
Beatmap = { BindTarget = Beatmap },
SelectedMods = { BindTarget = userMods },
IsValidMod = _ => false
});
@@ -318,6 +333,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
}
metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet;
((IBindable<MultiplayerScore?>)breakdown.UserBestScore).BindTo(leaderboard.UserBestScore);
}
private void presentScore(long id)
@@ -346,10 +363,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
Schedule(() =>
{
breakdown.AddNewScore(ev);
totals.AddNewScore(ev);
feed.AddNewScore(ev);
if (e.NewRank <= 50)
Schedule(() => leaderboard.RefetchScores());
Scheduler.AddOnce(() => leaderboard.RefetchScores());
});
});
}
@@ -374,6 +392,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
var beatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == playlistItem.Beatmap.OnlineID);
Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally.
Ruleset.Value = rulesets.GetRuleset(playlistItem.RulesetID);
applyLoopingToTrack();
}
@@ -416,7 +435,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
var itemStats = stats.SingleOrDefault(item => item.PlaylistItemID == playlistItem.ID);
if (itemStats == null) return;
Schedule(() => breakdown.SetInitialCounts(itemStats.TotalScoreDistribution));
Schedule(() =>
{
breakdown.SetInitialCounts(itemStats.TotalScoreDistribution);
totals.SetInitialCounts(itemStats.TotalScoreDistribution.Sum(c => c), itemStats.CumulativeScore);
});
});
beatmapAvailabilityTracker.SelectedItem.Value = playlistItem;
@@ -436,6 +459,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
userModsSelectOverlay.Hide();
cancelTrackLooping();
previewTrackManager.StopAnyPlaying(this);
}
public override bool OnExiting(ScreenExitEvent e)
@@ -443,6 +467,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
waves.Hide();
userModsSelectOverlay.Hide();
cancelTrackLooping();
previewTrackManager.StopAnyPlaying(this);
this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut();
roomManager.PartRoom();
@@ -486,7 +511,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
sampleStart?.Play();
this.Push(new PlayerLoader(() => new PlaylistsPlayer(room, playlistItem)
{
Exited = () => leaderboard.RefetchScores()
Exited = () => Scheduler.AddOnce(() => leaderboard.RefetchScores())
}));
}
@@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
@@ -22,6 +23,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
public Action<long>? PresentScore { get; init; }
private readonly Queue<NewScoreEvent> newScores = new Queue<NewScoreEvent>();
[BackgroundDependencyLoader]
private void load()
{
@@ -47,24 +50,33 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
public void AddNewScore(NewScoreEvent newScoreEvent)
{
var row = new NewScoreEventRow(newScoreEvent)
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
PresentScore = PresentScore,
};
flow.Add(row);
row.Delay(15000).Then().FadeOut(300, Easing.OutQuint).Expire();
newScores.Enqueue(newScoreEvent);
// ensure things don't get too out-of-hand.
if (newScores.Count > 25)
newScores.Dequeue();
}
protected override void Update()
{
base.Update();
while (newScores.TryDequeue(out var newScore))
{
flow.Add(new NewScoreEventRow(newScore)
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
PresentScore = PresentScore,
});
}
for (int i = 0; i < flow.Count; ++i)
{
var row = flow[i];
row.Alpha = Interpolation.ValueAt(Math.Clamp(row.Y + flow.DrawHeight, 0, flow.DrawHeight), 0f, 1f, 0, flow.DrawHeight, Easing.Out);
if (row.Y < -flow.DrawHeight)
{
row.RemoveAndDisposeImmediately();
@@ -109,7 +121,14 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
},
text = new LinkFlowContainer(t =>
{
t.Font = OsuFont.Default.With(weight: newScore.NewRank == null ? FontWeight.Medium : FontWeight.Bold);
FontWeight fontWeight = FontWeight.Medium;
if (newScore.NewRank < 100)
fontWeight = FontWeight.Bold;
else if (newScore.NewRank < 1000)
fontWeight = FontWeight.SemiBold;
t.Font = OsuFont.Default.With(weight: fontWeight);
t.Colour = newScore.NewRank < 10 ? colours.Orange1 : Colour4.White;
})
{
@@ -120,8 +139,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
};
text.AddUserLink(newScore.User);
text.AddText(" got ");
text.AddLink($"{newScore.TotalScore:N0} points", () => PresentScore?.Invoke(newScore.ScoreID));
text.AddText(" scored ");
text.AddLink($"{newScore.TotalScore:N0}", () => PresentScore?.Invoke(newScore.ScoreID));
if (newScore.NewRank != null)
text.AddText($" and achieved rank #{newScore.NewRank.Value:N0}");
@@ -22,6 +22,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
public partial class DailyChallengeLeaderboard : CompositeDrawable
{
public IBindable<MultiplayerScore?> UserBestScore => userBestScore;
private readonly Bindable<MultiplayerScore?> userBestScore = new Bindable<MultiplayerScore?>();
public Action<long>? PresentScore { get; init; }
private readonly Room room;
@@ -118,14 +121,21 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
RefetchScores();
}
private IndexPlaylistScoresRequest? request;
public void RefetchScores()
{
var request = new IndexPlaylistScoresRequest(room.RoomID.Value!.Value, playlistItem.ID);
if (request?.CompletionState == APIRequestCompletionState.Waiting)
return;
request = new IndexPlaylistScoresRequest(room.RoomID.Value!.Value, playlistItem.ID);
request.Success += req => Schedule(() =>
{
var best = req.Scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo)).ToArray();
var userBest = req.UserScore?.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo);
userBestScore.Value = req.UserScore;
var userBest = userBestScore.Value?.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo);
cancellationTokenSource?.Cancel();
cancellationTokenSource = null;
@@ -4,14 +4,17 @@
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Metadata;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.DailyChallenge.Events;
using osuTK;
@@ -20,6 +23,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
public partial class DailyChallengeScoreBreakdown : CompositeDrawable
{
public Bindable<MultiplayerScore?> UserBestScore { get; } = new Bindable<MultiplayerScore?>();
private FillFlowContainer<Bar> barsContainer = null!;
private const int bin_count = MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS;
@@ -44,29 +49,24 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
for (int i = 0; i < bin_count; ++i)
{
LocalisableString? label = null;
switch (i)
{
case 2:
case 4:
case 6:
case 8:
label = @$"{100 * i}k";
break;
case 10:
label = @"1M";
break;
}
barsContainer.Add(new Bar(label)
barsContainer.Add(new Bar(100_000 * i, 100_000 * (i + 1) - 1)
{
Width = 1f / bin_count,
});
}
}
protected override void LoadComplete()
{
base.LoadComplete();
UserBestScore.BindValueChanged(_ =>
{
foreach (var bar in barsContainer)
bar.ContainsLocalUser.Value = UserBestScore.Value is not null && bar.BinStart <= UserBestScore.Value.TotalScore && UserBestScore.Value.TotalScore <= bar.BinEnd;
});
}
public void AddNewScore(NewScoreEvent newScoreEvent)
{
int targetBin = (int)Math.Clamp(Math.Floor((float)newScoreEvent.TotalScore / 100000), 0, bin_count - 1);
@@ -113,20 +113,34 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
barsContainer[i].UpdateCounts(bins[i], max);
}
private partial class Bar : CompositeDrawable
private partial class Bar : CompositeDrawable, IHasTooltip
{
private readonly LocalisableString? label;
public BindableBool ContainsLocalUser { get; } = new BindableBool();
public readonly int BinStart;
public readonly int BinEnd;
private long count;
private long max;
public Container CircularBar { get; private set; } = null!;
public Bar(LocalisableString? label = null)
private Box fill = null!;
private Box flashLayer = null!;
private OsuSpriteText userIndicator = null!;
public Bar(int binStart, int binEnd)
{
this.label = label;
BinStart = binStart;
BinEnd = binEnd;
}
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
[Resolved]
private OsuColour colours { get; set; } = null!;
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
@@ -142,35 +156,83 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
},
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Masking = true,
Child = CircularBar = new Container
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Height = 0.01f,
Masking = true,
CornerRadius = 10,
Colour = colourProvider.Highlight1,
Child = new Box
CircularBar = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Height = 0.01f,
Masking = true,
CornerRadius = 10,
Children = new Drawable[]
{
fill = new Box
{
RelativeSizeAxes = Axes.Both,
},
flashLayer = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
},
}
},
userIndicator = new OsuSpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Colour = colours.Orange1,
Text = "You",
Font = OsuFont.Default.With(weight: FontWeight.Bold),
Alpha = 0,
RelativePositionAxes = Axes.Y,
Margin = new MarginPadding { Bottom = 5, },
}
}
},
});
string? label = null;
switch (BinStart)
{
case 200_000:
case 400_000:
case 600_000:
case 800_000:
label = @$"{BinStart / 1000}k";
break;
case 1_000_000:
label = @"1M";
break;
}
if (label != null)
{
AddInternal(new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomCentre,
Text = label.Value,
Text = label,
Colour = colourProvider.Content2,
});
}
}
protected override void LoadComplete()
{
base.LoadComplete();
ContainsLocalUser.BindValueChanged(_ =>
{
fill.FadeColour(ContainsLocalUser.Value ? colours.Orange1 : colourProvider.Highlight1, 300, Easing.OutQuint);
userIndicator.FadeTo(ContainsLocalUser.Value ? 1 : 0, 300, Easing.OutQuint);
}, true);
FinishTransforms(true);
}
protected override void Update()
{
base.Update();
@@ -185,10 +247,14 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
count = newCount;
max = newMax;
CircularBar.ResizeHeightTo(0.01f + 0.99f * count / max, 300, Easing.OutQuint);
float height = 0.01f + 0.99f * count / max;
CircularBar.ResizeHeightTo(height, 300, Easing.OutQuint);
userIndicator.MoveToY(-height, 300, Easing.OutQuint);
if (isIncrement)
CircularBar.FlashColour(Colour4.White, 600, Easing.OutQuint);
flashLayer.FadeOutFromOne(600, Easing.OutQuint);
}
public LocalisableString TooltipText => LocalisableString.Format("{0:N0} passes in {1:N0} - {2:N0} range", count, BinStart, BinEnd);
}
}
}
@@ -0,0 +1,141 @@
// 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 osu.Framework.Allocation;
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.Graphics.UserInterface;
using osu.Game.Screens.OnlinePlay.DailyChallenge.Events;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
public partial class DailyChallengeTotalsDisplay : CompositeDrawable
{
private Container passCountContainer = null!;
private TotalRollingCounter passCounter = null!;
private Container totalScoreContainer = null!;
private TotalRollingCounter totalScoreCounter = null!;
private long totalPassCountInstantaneous;
private long cumulativeTotalScoreInstantaneous;
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions =
[
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
],
Content = new[]
{
new Drawable[]
{
new SectionHeader("Total pass count")
},
new Drawable[]
{
passCountContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Child = passCounter = new TotalRollingCounter
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
}
},
new Drawable[]
{
new SectionHeader("Cumulative total score")
},
new Drawable[]
{
totalScoreContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Child = totalScoreCounter = new TotalRollingCounter
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
}
},
}
};
}
public void SetInitialCounts(long totalPassCount, long cumulativeTotalScore)
{
totalPassCountInstantaneous = totalPassCount;
cumulativeTotalScoreInstantaneous = cumulativeTotalScore;
}
public void AddNewScore(NewScoreEvent ev)
{
totalPassCountInstantaneous += 1;
cumulativeTotalScoreInstantaneous += ev.TotalScore;
}
protected override void Update()
{
base.Update();
passCounter.Current.Value = totalPassCountInstantaneous;
totalScoreCounter.Current.Value = cumulativeTotalScoreInstantaneous;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
var totalPassCountProportionOfParent = Vector2.Divide(passCountContainer.DrawSize, passCounter.DrawSize);
passCounter.Scale = new Vector2(Math.Min(Math.Min(totalPassCountProportionOfParent.X, totalPassCountProportionOfParent.Y) * 0.8f, 1));
var totalScoreTextProportionOfParent = Vector2.Divide(totalScoreContainer.DrawSize, totalScoreCounter.DrawSize);
totalScoreCounter.Scale = new Vector2(Math.Min(Math.Min(totalScoreTextProportionOfParent.X, totalScoreTextProportionOfParent.Y) * 0.8f, 1));
}
private partial class TotalRollingCounter : RollingCounter<long>
{
protected override double RollingDuration => 1000;
protected override Easing RollingEasing => Easing.OutPow10;
protected override bool IsRollingProportional => true;
protected override double GetProportionalDuration(long currentValue, long newValue)
{
long change = Math.Abs(newValue - currentValue);
if (change < 10)
return 0;
return Math.Min(6000, RollingDuration * Math.Sqrt(change) / 100);
}
protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText
{
Font = OsuFont.Default.With(size: 80f, fixedWidth: true),
Spacing = new Vector2(-4, 0)
};
protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(@"N0");
}
}
}
@@ -29,7 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
var scoreInfos = base.PerformSuccessCallback(callback, scores, pivot);
Schedule(() => SelectedScore.Value = scoreInfos.SingleOrDefault(score => score.OnlineID == scoreId));
Schedule(() => SelectedScore.Value ??= scoreInfos.SingleOrDefault(score => score.OnlineID == scoreId));
return scoreInfos;
}
@@ -68,9 +68,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
get
{
if (Enabled.Value)
return string.Empty;
if (!enoughTimeLeft)
return "No time left!";
@@ -18,7 +18,7 @@ namespace osu.Game.Screens.Ranking
{
public partial class ReplayDownloadButton : CompositeDrawable, IKeyBindingHandler<GlobalAction>
{
public readonly Bindable<ScoreInfo> Score = new Bindable<ScoreInfo>();
public readonly Bindable<ScoreInfo?> Score = new Bindable<ScoreInfo?>();
protected readonly Bindable<DownloadState> State = new Bindable<DownloadState>();
@@ -44,7 +44,7 @@ namespace osu.Game.Screens.Ranking
}
}
public ReplayDownloadButton(ScoreInfo score)
public ReplayDownloadButton(ScoreInfo? score)
{
Score.Value = score;
Size = new Vector2(50, 30);
@@ -67,11 +67,11 @@ namespace osu.Game.Screens.Ranking
switch (State.Value)
{
case DownloadState.LocallyAvailable:
game?.PresentScore(Score.Value, ScorePresentType.Gameplay);
game?.PresentScore(Score.Value!, ScorePresentType.Gameplay);
break;
case DownloadState.NotDownloaded:
scoreDownloader.Download(Score.Value);
scoreDownloader.Download(Score.Value!);
break;
case DownloadState.Importing:
@@ -88,6 +88,8 @@ namespace osu.Game.Screens.Ranking
State.ValueChanged -= exportWhenReady;
downloadTracker?.RemoveAndDisposeImmediately();
downloadTracker = null;
State.SetDefault();
if (score.NewValue != null)
{
@@ -147,7 +149,7 @@ namespace osu.Game.Screens.Ranking
{
if (state.NewValue != DownloadState.LocallyAvailable) return;
scoreManager.Export(Score.Value);
scoreManager.Export(Score.Value!);
State.ValueChanged -= exportWhenReady;
}
+4 -3
View File
@@ -179,11 +179,11 @@ namespace osu.Game.Screens.Ranking
Scheduler.AddDelayed(() => OverlayActivationMode.Value = OverlayActivation.All, shouldFlair ? AccuracyCircle.TOTAL_DURATION + 1000 : 0);
}
if (SelectedScore.Value != null && AllowWatchingReplay)
if (AllowWatchingReplay)
{
buttons.Add(new ReplayDownloadButton(SelectedScore.Value)
{
Score = { BindTarget = SelectedScore! },
Score = { BindTarget = SelectedScore },
Width = 300
});
}
@@ -398,7 +398,8 @@ namespace osu.Game.Screens.Ranking
break;
case GlobalAction.Select:
StatisticsPanel.ToggleVisibility();
if (SelectedScore.Value != null)
StatisticsPanel.ToggleVisibility();
return true;
}