mirror of
https://github.com/ppy/osu.git
synced 2024-12-05 10:33:22 +08:00
Merge branch 'master'
This commit is contained in:
commit
fa3c95c296
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@ -114,7 +114,10 @@ jobs:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Install .NET workloads
|
||||
run: dotnet workload install android
|
||||
# since windows image 20241113.3.0, not specifying a version here
|
||||
# installs the .NET 7 version of android workload for very unknown reasons.
|
||||
# revisit once we upgrade to .NET 9, it's probably fixed there.
|
||||
run: dotnet workload install android --version (dotnet --version)
|
||||
|
||||
- name: Compile
|
||||
run: dotnet build -c Debug osu.Android.slnf
|
||||
|
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1118.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1128.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -164,10 +164,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
|
||||
private Drawable getResult(HitResult result)
|
||||
{
|
||||
if (!hit_result_mapping.ContainsKey(result))
|
||||
if (!hit_result_mapping.TryGetValue(result, out var value))
|
||||
return null;
|
||||
|
||||
string filename = this.GetManiaSkinConfig<string>(hit_result_mapping[result])?.Value
|
||||
string filename = this.GetManiaSkinConfig<string>(value)?.Value
|
||||
?? default_hit_result_skin_filenames[result];
|
||||
|
||||
var animation = this.GetAnimation(filename, true, true, frameLength: 1000 / 20d);
|
||||
|
@ -354,6 +354,23 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("retry count is 1", () => player.RestartCount == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLastScoreNullAfterExitingPlayer()
|
||||
{
|
||||
AddUntilStep("wait for last play null", getLastPlay, () => Is.Null);
|
||||
|
||||
var getOriginalPlayer = playToCompletion();
|
||||
|
||||
AddStep("attempt to retry", () => getOriginalPlayer().ChildrenOfType<HotkeyRetryOverlay>().First().Action());
|
||||
AddUntilStep("wait for last play matches player", getLastPlay, () => Is.EqualTo(getOriginalPlayer().Score.ScoreInfo));
|
||||
|
||||
AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player);
|
||||
AddStep("exit player", () => (Game.ScreenStack.CurrentScreen as Player)?.Exit());
|
||||
AddUntilStep("wait for last play null", getLastPlay, () => Is.Null);
|
||||
|
||||
ScoreInfo getLastPlay() => Game.Dependencies.Get<SessionStatics>().Get<ScoreInfo>(Static.LastLocalUserScore);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRetryImmediatelyAfterCompletion()
|
||||
{
|
||||
|
@ -457,6 +457,61 @@ namespace osu.Game.Tests.Visual.Online
|
||||
waitForChannel1Visible();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPublicChannelsSortedByName()
|
||||
{
|
||||
// Intentionally join back to front.
|
||||
AddStep("Show overlay with channel 2", () =>
|
||||
{
|
||||
channelManager.CurrentChannel.Value = channelManager.JoinChannel(testChannel2);
|
||||
chatOverlay.Show();
|
||||
});
|
||||
AddUntilStep("second channel is at top of list", () => getFirstVisiblePublicChannel().Channel == testChannel2);
|
||||
|
||||
AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
|
||||
AddUntilStep("first channel is at top of list", () => getFirstVisiblePublicChannel().Channel == testChannel1);
|
||||
|
||||
AddStep("message in channel 2", () =>
|
||||
{
|
||||
testChannel2.AddNewMessages(new Message(1) { Content = "hi!", Sender = new APIUser { Username = "person" } });
|
||||
});
|
||||
AddUntilStep("first channel still at top of list", () => getFirstVisiblePublicChannel().Channel == testChannel1);
|
||||
|
||||
ChannelListItem getFirstVisiblePublicChannel() =>
|
||||
chatOverlay.ChildrenOfType<ChannelList>().Single().PublicChannelGroup.ItemFlow.FlowingChildren.OfType<ChannelListItem>().First(item => item.Channel.Type == ChannelType.Public);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPrivateChannelsSortedByRecent()
|
||||
{
|
||||
Channel pmChannel1 = createPrivateChannel();
|
||||
Channel pmChannel2 = createPrivateChannel();
|
||||
|
||||
joinChannel(pmChannel1);
|
||||
joinChannel(pmChannel2);
|
||||
|
||||
AddStep("Show overlay", () => chatOverlay.Show());
|
||||
|
||||
AddUntilStep("first channel is at top of list", () => getFirstVisiblePMChannel().Channel == pmChannel1);
|
||||
|
||||
AddStep("message in channel 2", () =>
|
||||
{
|
||||
pmChannel2.AddNewMessages(new Message(1) { Content = "hi!", Sender = new APIUser { Username = "person" } });
|
||||
});
|
||||
|
||||
AddUntilStep("wait for first channel raised to top of list", () => getFirstVisiblePMChannel().Channel == pmChannel2);
|
||||
|
||||
AddStep("message in channel 1", () =>
|
||||
{
|
||||
pmChannel1.AddNewMessages(new Message(1) { Content = "hi!", Sender = new APIUser { Username = "person" } });
|
||||
});
|
||||
|
||||
AddUntilStep("wait for first channel raised to top of list", () => getFirstVisiblePMChannel().Channel == pmChannel1);
|
||||
|
||||
ChannelListItem getFirstVisiblePMChannel() =>
|
||||
chatOverlay.ChildrenOfType<ChannelList>().Single().PrivateChannelGroup.ItemFlow.FlowingChildren.OfType<ChannelListItem>().First(item => item.Channel.Type == ChannelType.PM);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestKeyboardNewChannel()
|
||||
{
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v));
|
||||
AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v));
|
||||
AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v));
|
||||
AddSliderStep("playcount", 0, 999, 0, v => update(s => s.PlayCount = v));
|
||||
AddSliderStep("playcount", 0, 1500, 1, v => update(s => s.PlayCount = v));
|
||||
AddStep("create", () =>
|
||||
{
|
||||
Clear();
|
||||
@ -66,8 +66,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestPlayCountRankingTier()
|
||||
{
|
||||
AddAssert("1 before silver", () => DailyChallengeStatsTooltip.TierForPlayCount(30) == RankingTier.Bronze);
|
||||
AddAssert("first silver", () => DailyChallengeStatsTooltip.TierForPlayCount(31) == RankingTier.Silver);
|
||||
AddAssert("1 before silver", () => DailyChallengeStatsTooltip.TierForPlayCount(29) == RankingTier.Bronze);
|
||||
AddAssert("first silver", () => DailyChallengeStatsTooltip.TierForPlayCount(30) == RankingTier.Silver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@ -31,7 +32,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
private const int scores_per_result = 10;
|
||||
private const int real_user_position = 200;
|
||||
|
||||
private TestResultsScreen resultsScreen = null!;
|
||||
private ResultsScreen resultsScreen = null!;
|
||||
|
||||
private int lowestScoreId; // Score ID of the lowest score in the list.
|
||||
private int highestScoreId; // Score ID of the highest score in the list.
|
||||
@ -68,11 +69,11 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowWithUserScore()
|
||||
public void TestShowUserScore()
|
||||
{
|
||||
AddStep("bind user score info handler", () => bindHandler(userScore: userScore));
|
||||
|
||||
createResults(() => userScore);
|
||||
createResultsWithScore(() => userScore);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded);
|
||||
@ -81,11 +82,24 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowNullUserScore()
|
||||
public void TestShowUserBest()
|
||||
{
|
||||
AddStep("bind user score info handler", () => bindHandler(userScore: userScore));
|
||||
|
||||
createUserBestResults();
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.UserID == userScore.UserID).State == PanelState.Expanded);
|
||||
AddAssert($"score panel position is {real_user_position}",
|
||||
() => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.UserID == userScore.UserID).ScorePosition.Value == real_user_position);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowNonUserScores()
|
||||
{
|
||||
AddStep("bind user score info handler", () => bindHandler());
|
||||
|
||||
createResults();
|
||||
createUserBestResults();
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert("top score selected", () => this.ChildrenOfType<ScorePanel>().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded);
|
||||
@ -96,7 +110,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
AddStep("bind user score info handler", () => bindHandler(true, userScore));
|
||||
|
||||
createResults(() => userScore);
|
||||
createResultsWithScore(() => userScore);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert("more than 1 panel displayed", () => this.ChildrenOfType<ScorePanel>().Count() > 1);
|
||||
@ -104,11 +118,11 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowNullUserScoreWithDelay()
|
||||
public void TestShowNonUserScoresWithDelay()
|
||||
{
|
||||
AddStep("bind delayed handler", () => bindHandler(true));
|
||||
|
||||
createResults();
|
||||
createUserBestResults();
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert("top score selected", () => this.ChildrenOfType<ScorePanel>().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded);
|
||||
@ -119,7 +133,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
AddStep("bind delayed handler", () => bindHandler(true));
|
||||
|
||||
createResults();
|
||||
createUserBestResults();
|
||||
waitForDisplay();
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
@ -127,13 +141,16 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
int beforePanelCount = 0;
|
||||
|
||||
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
|
||||
AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
|
||||
AddStep("scroll to right", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
|
||||
|
||||
AddAssert("right loading spinner shown", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible);
|
||||
|
||||
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
|
||||
AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden);
|
||||
AddAssert("right loading spinner hidden", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden);
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,29 +159,36 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
AddStep("bind delayed handler with scores", () => bindHandler(delayed: true));
|
||||
|
||||
createResults();
|
||||
createUserBestResults();
|
||||
waitForDisplay();
|
||||
|
||||
int beforePanelCount = 0;
|
||||
|
||||
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
|
||||
AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
|
||||
AddStep("scroll to right", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
|
||||
|
||||
AddAssert("right loading spinner shown", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible);
|
||||
|
||||
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
|
||||
AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden);
|
||||
AddAssert("right loading spinner hidden", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden);
|
||||
|
||||
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
|
||||
AddStep("bind delayed handler with no scores", () => bindHandler(delayed: true, noScores: true));
|
||||
AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
|
||||
AddStep("scroll to right", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
|
||||
|
||||
AddAssert("right loading spinner shown", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible);
|
||||
|
||||
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert("count not increased", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount);
|
||||
AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden);
|
||||
AddAssert("right loading spinner hidden", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden);
|
||||
|
||||
AddAssert("no placeholders shown", () => this.ChildrenOfType<MessagePlaceholder>().Count(), () => Is.Zero);
|
||||
}
|
||||
|
||||
@ -173,7 +197,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
AddStep("bind user score info handler", () => bindHandler(userScore: userScore));
|
||||
|
||||
createResults(() => userScore);
|
||||
createResultsWithScore(() => userScore);
|
||||
waitForDisplay();
|
||||
|
||||
AddStep("bind delayed handler", () => bindHandler(true));
|
||||
@ -183,30 +207,36 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
int beforePanelCount = 0;
|
||||
|
||||
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
|
||||
AddStep("scroll to left", () => resultsScreen.ScorePanelList.ChildrenOfType<OsuScrollContainer>().Single().ScrollToStart(false));
|
||||
AddStep("scroll to left", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().ChildrenOfType<OsuScrollContainer>().Single().ScrollToStart(false));
|
||||
|
||||
AddAssert("left loading spinner shown", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Visible);
|
||||
|
||||
AddAssert("left loading spinner shown", () => resultsScreen.LeftSpinner.State.Value == Visibility.Visible);
|
||||
waitForDisplay();
|
||||
|
||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
|
||||
AddAssert("left loading spinner hidden", () => resultsScreen.LeftSpinner.State.Value == Visibility.Hidden);
|
||||
AddAssert("left loading spinner hidden", () =>
|
||||
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Hidden);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the <see cref="TestUserBestResultsScreen"/> with no scores provided by the API.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestShowWithNoScores()
|
||||
public void TestShowUserBestWithNoScoresPresent()
|
||||
{
|
||||
AddStep("bind user score info handler", () => bindHandler(noScores: true));
|
||||
createResults();
|
||||
AddAssert("no scores visible", () => !resultsScreen.ScorePanelList.GetScorePanels().Any());
|
||||
createUserBestResults();
|
||||
AddAssert("no scores visible", () => !resultsScreen.ChildrenOfType<ScorePanelList>().Single().GetScorePanels().Any());
|
||||
AddAssert("placeholder shown", () => this.ChildrenOfType<MessagePlaceholder>().Count(), () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
private void createResults(Func<ScoreInfo>? getScore = null)
|
||||
private void createResultsWithScore(Func<ScoreInfo> getScore)
|
||||
{
|
||||
AddStep("load results", () =>
|
||||
{
|
||||
LoadScreen(resultsScreen = new TestResultsScreen(getScore?.Invoke(), 1, new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
LoadScreen(resultsScreen = new TestScoreResultsScreen(getScore(), 1, new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
{
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
|
||||
}));
|
||||
@ -215,14 +245,27 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
AddUntilStep("wait for screen to load", () => resultsScreen.IsLoaded);
|
||||
}
|
||||
|
||||
private void createUserBestResults()
|
||||
{
|
||||
AddStep("load results", () =>
|
||||
{
|
||||
LoadScreen(resultsScreen = new TestUserBestResultsScreen(1, new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
{
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
|
||||
}, 2));
|
||||
});
|
||||
|
||||
AddUntilStep("wait for screen to load", () => resultsScreen.IsLoaded);
|
||||
}
|
||||
|
||||
private void waitForDisplay()
|
||||
{
|
||||
AddUntilStep("wait for scores loaded", () =>
|
||||
requestComplete
|
||||
// request handler may need to fire more than once to get scores.
|
||||
&& totalCount > 0
|
||||
&& resultsScreen.ScorePanelList.GetScorePanels().Count() == totalCount
|
||||
&& resultsScreen.ScorePanelList.AllPanelsVisible);
|
||||
&& resultsScreen.ChildrenOfType<ScorePanelList>().Single().GetScorePanels().Count() == totalCount
|
||||
&& resultsScreen.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
|
||||
AddWaitStep("wait for display", 5);
|
||||
}
|
||||
|
||||
@ -231,6 +274,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
// pre-check for requests we should be handling (as they are scheduled below).
|
||||
switch (request)
|
||||
{
|
||||
case ShowPlaylistScoreRequest:
|
||||
case ShowPlaylistUserScoreRequest:
|
||||
case IndexPlaylistScoresRequest:
|
||||
break;
|
||||
@ -253,7 +297,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
switch (request)
|
||||
{
|
||||
case ShowPlaylistUserScoreRequest s:
|
||||
case ShowPlaylistScoreRequest s:
|
||||
if (userScore == null)
|
||||
triggerFail(s);
|
||||
else
|
||||
@ -261,6 +305,14 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
break;
|
||||
|
||||
case ShowPlaylistUserScoreRequest u:
|
||||
if (userScore == null)
|
||||
triggerFail(u);
|
||||
else
|
||||
triggerSuccess(u, createUserResponse(userScore));
|
||||
|
||||
break;
|
||||
|
||||
case IndexPlaylistScoresRequest i:
|
||||
triggerSuccess(i, createIndexResponse(i, noScores));
|
||||
break;
|
||||
@ -314,7 +366,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
MaxCombo = userScore.MaxCombo,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Id = 2 + i,
|
||||
Username = $"peppy{i}",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
},
|
||||
@ -329,7 +381,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
MaxCombo = userScore.MaxCombo,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Id = 2 + i,
|
||||
Username = $"peppy{i}",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
},
|
||||
@ -363,7 +415,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
MaxCombo = 1000,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Id = 2 + i,
|
||||
Username = $"peppy{i}",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
},
|
||||
@ -410,18 +462,22 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
};
|
||||
}
|
||||
|
||||
private partial class TestResultsScreen : PlaylistItemUserResultsScreen
|
||||
private partial class TestScoreResultsScreen : PlaylistItemScoreResultsScreen
|
||||
{
|
||||
public new LoadingSpinner LeftSpinner => base.LeftSpinner;
|
||||
public new LoadingSpinner CentreSpinner => base.CentreSpinner;
|
||||
public new LoadingSpinner RightSpinner => base.RightSpinner;
|
||||
public new ScorePanelList ScorePanelList => base.ScorePanelList;
|
||||
|
||||
public TestResultsScreen(ScoreInfo? score, int roomId, PlaylistItem playlistItem)
|
||||
public TestScoreResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem)
|
||||
: base(score, roomId, playlistItem)
|
||||
{
|
||||
AllowRetry = true;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class TestUserBestResultsScreen : PlaylistItemUserBestResultsScreen
|
||||
{
|
||||
public TestUserBestResultsScreen(int roomId, PlaylistItem playlistItem, int userId)
|
||||
: base(roomId, playlistItem, userId)
|
||||
{
|
||||
AllowRetry = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,13 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
@ -14,6 +19,9 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
|
||||
|
||||
[Test]
|
||||
@ -37,5 +45,29 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen());
|
||||
AddAssert("status is still ended", () => roomScreen.Room.Status, Is.TypeOf<RoomStatusEnded>);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCloseButtonGoesAwayAfterGracePeriod()
|
||||
{
|
||||
Room room = null!;
|
||||
PlaylistsRoomSubScreen roomScreen = null!;
|
||||
|
||||
AddStep("create room", () =>
|
||||
{
|
||||
RoomManager.AddRoom(room = new Room
|
||||
{
|
||||
Name = @"Test Room",
|
||||
Host = api.LocalUser.Value,
|
||||
Category = RoomCategory.Normal,
|
||||
StartDate = DateTimeOffset.Now.AddMinutes(-5).AddSeconds(3),
|
||||
EndDate = DateTimeOffset.Now.AddMinutes(30)
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room)));
|
||||
AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen());
|
||||
AddAssert("close button present", () => roomScreen.ChildrenOfType<DangerousRoundedButton>().Any());
|
||||
AddUntilStep("wait for close button to disappear", () => !roomScreen.ChildrenOfType<DangerousRoundedButton>().Any());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -408,7 +408,7 @@ namespace osu.Game.Beatmaps
|
||||
// user requested abort
|
||||
return;
|
||||
|
||||
var video = b.Files.FirstOrDefault(f => OsuGameBase.VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.OrdinalIgnoreCase)));
|
||||
var video = b.Files.FirstOrDefault(f => SupportedExtensions.VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
if (video != null)
|
||||
{
|
||||
@ -559,7 +559,11 @@ namespace osu.Game.Beatmaps
|
||||
// If we seem to be missing files, now is a good time to re-fetch.
|
||||
bool missingFiles = beatmapInfo.BeatmapSet?.Files.Count == 0;
|
||||
|
||||
if (refetch || beatmapInfo.IsManaged || missingFiles)
|
||||
if (beatmapInfo.IsManaged)
|
||||
{
|
||||
beatmapInfo = beatmapInfo.Detach();
|
||||
}
|
||||
else if (refetch || missingFiles)
|
||||
{
|
||||
Guid id = beatmapInfo.ID;
|
||||
beatmapInfo = Realm.Run(r => r.Find<BeatmapInfo>(id)?.Detach()) ?? beatmapInfo;
|
||||
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
@ -446,7 +447,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
// Some very old beatmaps had incorrect type specifications for their backgrounds (ie. using 1 for VIDEO
|
||||
// instead of 0 for BACKGROUND). To handle this gracefully, check the file extension against known supported
|
||||
// video extensions and handle similar to a background if it doesn't match.
|
||||
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant()))
|
||||
if (!SupportedExtensions.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant()))
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
|
||||
lineSupportedByEncoder = true;
|
||||
|
@ -10,6 +10,7 @@ using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Storyboards.Commands;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -112,7 +113,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
//
|
||||
// This avoids potential weird crashes when ffmpeg attempts to parse an image file as a video
|
||||
// (see https://github.com/ppy/osu/issues/22829#issuecomment-1465552451).
|
||||
if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path).ToLowerInvariant()))
|
||||
if (!SupportedExtensions.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path).ToLowerInvariant()))
|
||||
break;
|
||||
|
||||
storyboard.GetLayer("Video").Add(storyboardSprite = new StoryboardVideo(path, offset));
|
||||
|
@ -10,6 +10,7 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Configuration
|
||||
{
|
||||
@ -77,7 +78,8 @@ namespace osu.Game.Configuration
|
||||
TouchInputActive,
|
||||
|
||||
/// <summary>
|
||||
/// Stores the local user's last score (can be completed or aborted).
|
||||
/// Contains the local user's last score (can be completed or aborted) after exiting <see cref="Player"/>.
|
||||
/// Will be cleared to <c>null</c> when leaving <see cref="PlayerLoader"/>.
|
||||
/// </summary>
|
||||
LastLocalUserScore,
|
||||
|
||||
|
@ -245,8 +245,8 @@ namespace osu.Game.Database
|
||||
var scoreProcessor = ruleset.CreateScoreProcessor();
|
||||
|
||||
// warning: ordering is important here - both total score and ranks are dependent on accuracy!
|
||||
score.Accuracy = computeAccuracy(score, scoreProcessor);
|
||||
score.Rank = computeRank(score, scoreProcessor);
|
||||
score.Accuracy = ComputeAccuracy(score, scoreProcessor);
|
||||
score.Rank = ComputeRank(score, scoreProcessor);
|
||||
(score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, beatmap);
|
||||
}
|
||||
|
||||
@ -269,8 +269,8 @@ namespace osu.Game.Database
|
||||
var scoreProcessor = ruleset.CreateScoreProcessor();
|
||||
|
||||
// warning: ordering is important here - both total score and ranks are dependent on accuracy!
|
||||
score.Accuracy = computeAccuracy(score, scoreProcessor);
|
||||
score.Rank = computeRank(score, scoreProcessor);
|
||||
score.Accuracy = ComputeAccuracy(score, scoreProcessor);
|
||||
score.Rank = ComputeRank(score, scoreProcessor);
|
||||
(score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes);
|
||||
}
|
||||
|
||||
@ -313,7 +313,8 @@ namespace osu.Game.Database
|
||||
/// <param name="difficulty">The beatmap difficulty.</param>
|
||||
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
|
||||
/// <returns>The standardised total score.</returns>
|
||||
private static (long withoutMods, long withMods) convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
|
||||
private static (long withoutMods, long withMods) convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty,
|
||||
LegacyScoreAttributes attributes)
|
||||
{
|
||||
if (!score.IsLegacyScore)
|
||||
return (score.TotalScoreWithoutMods, score.TotalScore);
|
||||
@ -620,24 +621,31 @@ namespace osu.Game.Database
|
||||
}
|
||||
}
|
||||
|
||||
private static double computeAccuracy(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor)
|
||||
public static double ComputeAccuracy(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor)
|
||||
=> ComputeAccuracy(scoreInfo.Statistics, scoreInfo.MaximumStatistics, scoreProcessor);
|
||||
|
||||
public static double ComputeAccuracy(IReadOnlyDictionary<HitResult, int> statistics, IReadOnlyDictionary<HitResult, int> maximumStatistics, ScoreProcessor scoreProcessor)
|
||||
{
|
||||
int baseScore = scoreInfo.Statistics.Where(kvp => kvp.Key.AffectsAccuracy())
|
||||
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
|
||||
int maxBaseScore = scoreInfo.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy())
|
||||
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
|
||||
int baseScore = statistics.Where(kvp => kvp.Key.AffectsAccuracy())
|
||||
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
|
||||
int maxBaseScore = maximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy())
|
||||
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
|
||||
|
||||
return maxBaseScore == 0 ? 1 : baseScore / (double)maxBaseScore;
|
||||
}
|
||||
|
||||
public static ScoreRank ComputeRank(ScoreInfo scoreInfo) => computeRank(scoreInfo, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor());
|
||||
public static ScoreRank ComputeRank(ScoreInfo scoreInfo) =>
|
||||
ComputeRank(scoreInfo.Accuracy, scoreInfo.Statistics, scoreInfo.Mods, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor());
|
||||
|
||||
private static ScoreRank computeRank(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor)
|
||||
public static ScoreRank ComputeRank(ScoreInfo scoreInfo, ScoreProcessor processor) =>
|
||||
ComputeRank(scoreInfo.Accuracy, scoreInfo.Statistics, scoreInfo.Mods, processor);
|
||||
|
||||
public static ScoreRank ComputeRank(double accuracy, IReadOnlyDictionary<HitResult, int> statistics, IList<Mod> mods, ScoreProcessor scoreProcessor)
|
||||
{
|
||||
var rank = scoreProcessor.RankFromScore(scoreInfo.Accuracy, scoreInfo.Statistics);
|
||||
var rank = scoreProcessor.RankFromScore(accuracy, statistics);
|
||||
|
||||
foreach (var mod in scoreInfo.Mods.OfType<IApplicableToScoreProcessor>())
|
||||
rank = mod.AdjustRank(rank, scoreInfo.Accuracy);
|
||||
foreach (var mod in mods.OfType<IApplicableToScoreProcessor>())
|
||||
rank = mod.AdjustRank(rank, accuracy);
|
||||
|
||||
return rank;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterfaceV2.FileSelection;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
@ -96,24 +97,18 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OsuGameBase.VIDEO_EXTENSIONS.Contains(File.Extension.ToLowerInvariant()))
|
||||
string extension = File.Extension.ToLowerInvariant();
|
||||
|
||||
if (SupportedExtensions.VIDEO_EXTENSIONS.Contains(extension))
|
||||
return FontAwesome.Regular.FileVideo;
|
||||
|
||||
switch (File.Extension)
|
||||
{
|
||||
case @".ogg":
|
||||
case @".mp3":
|
||||
case @".wav":
|
||||
return FontAwesome.Regular.FileAudio;
|
||||
if (SupportedExtensions.AUDIO_EXTENSIONS.Contains(extension))
|
||||
return FontAwesome.Regular.FileAudio;
|
||||
|
||||
case @".jpg":
|
||||
case @".jpeg":
|
||||
case @".png":
|
||||
return FontAwesome.Regular.FileImage;
|
||||
if (SupportedExtensions.IMAGE_EXTENSIONS.Contains(extension))
|
||||
return FontAwesome.Regular.FileImage;
|
||||
|
||||
default:
|
||||
return FontAwesome.Regular.File;
|
||||
}
|
||||
return FontAwesome.Regular.File;
|
||||
}
|
||||
}
|
||||
|
||||
|
27
osu.Game/Online/API/Requests/ClosePlaylistRequest.cs
Normal file
27
osu.Game/Online/API/Requests/ClosePlaylistRequest.cs
Normal file
@ -0,0 +1,27 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class ClosePlaylistRequest : APIRequest
|
||||
{
|
||||
private readonly long roomId;
|
||||
|
||||
public ClosePlaylistRequest(long roomId)
|
||||
{
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var request = base.CreateWebRequest();
|
||||
request.Method = HttpMethod.Delete;
|
||||
return request;
|
||||
}
|
||||
|
||||
protected override string Target => $@"rooms/{roomId}";
|
||||
}
|
||||
}
|
@ -161,7 +161,7 @@ namespace osu.Game.Online.Chat
|
||||
Messages.AddRange(messages);
|
||||
|
||||
long? maxMessageId = messages.Max(m => m.Id);
|
||||
if (maxMessageId > LastMessageId)
|
||||
if (LastMessageId == null || maxMessageId > LastMessageId)
|
||||
LastMessageId = maxMessageId;
|
||||
|
||||
purgeOldMessages();
|
||||
|
@ -375,6 +375,7 @@ namespace osu.Game.Online.Rooms
|
||||
Type = other.Type;
|
||||
MaxParticipants = other.MaxParticipants;
|
||||
ParticipantCount = other.ParticipantCount;
|
||||
StartDate = other.StartDate;
|
||||
EndDate = other.EndDate;
|
||||
UserScore = other.UserScore;
|
||||
QueueMode = other.QueueMode;
|
||||
|
@ -195,7 +195,8 @@ namespace osu.Game
|
||||
|
||||
private MainMenu menuScreen;
|
||||
|
||||
private VersionManager versionManager;
|
||||
[CanBeNull]
|
||||
private DevBuildBanner devBuildBanner;
|
||||
|
||||
[CanBeNull]
|
||||
private IntroScreen introScreen;
|
||||
@ -1055,10 +1056,7 @@ namespace osu.Game
|
||||
}, topMostOverlayContent.Add);
|
||||
|
||||
if (!IsDeployedBuild)
|
||||
{
|
||||
dependencies.Cache(versionManager = new VersionManager());
|
||||
loadComponentSingleFile(versionManager, ScreenContainer.Add);
|
||||
}
|
||||
loadComponentSingleFile(devBuildBanner = new DevBuildBanner(), ScreenContainer.Add);
|
||||
|
||||
loadComponentSingleFile(osuLogo, _ =>
|
||||
{
|
||||
@ -1564,12 +1562,12 @@ namespace osu.Game
|
||||
{
|
||||
case IntroScreen intro:
|
||||
introScreen = intro;
|
||||
versionManager?.Show();
|
||||
devBuildBanner?.Show();
|
||||
break;
|
||||
|
||||
case MainMenu menu:
|
||||
menuScreen = menu;
|
||||
versionManager?.Show();
|
||||
devBuildBanner?.Show();
|
||||
break;
|
||||
|
||||
case Player player:
|
||||
@ -1577,7 +1575,7 @@ namespace osu.Game
|
||||
break;
|
||||
|
||||
default:
|
||||
versionManager?.Hide();
|
||||
devBuildBanner?.Hide();
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -73,8 +73,6 @@ namespace osu.Game
|
||||
[Cached(typeof(OsuGameBase))]
|
||||
public partial class OsuGameBase : Framework.Game, ICanAcceptFiles, IBeatSyncProvider
|
||||
{
|
||||
public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv", ".mpg", ".wmv", ".m4v" };
|
||||
|
||||
#if DEBUG
|
||||
public const string GAME_NAME = "osu! (development)";
|
||||
#else
|
||||
|
@ -37,11 +37,13 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
|
||||
private readonly Dictionary<Channel, ChannelListItem> channelMap = new Dictionary<Channel, ChannelListItem>();
|
||||
|
||||
public ChannelGroup AnnounceChannelGroup { get; private set; } = null!;
|
||||
public ChannelGroup PublicChannelGroup { get; private set; } = null!;
|
||||
public ChannelGroup PrivateChannelGroup { get; private set; } = null!;
|
||||
|
||||
private OsuScrollContainer scroll = null!;
|
||||
private SearchContainer groupFlow = null!;
|
||||
private ChannelGroup announceChannelGroup = null!;
|
||||
private ChannelGroup publicChannelGroup = null!;
|
||||
private ChannelGroup privateChannelGroup = null!;
|
||||
|
||||
private ChannelListItem selector = null!;
|
||||
private TextBox searchTextBox = null!;
|
||||
|
||||
@ -77,10 +79,10 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
RelativeSizeAxes = Axes.X,
|
||||
}
|
||||
},
|
||||
announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper()),
|
||||
publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()),
|
||||
AnnounceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper(), false),
|
||||
PublicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper(), false),
|
||||
selector = new ChannelListItem(ChannelListingChannel),
|
||||
privateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper()),
|
||||
PrivateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper(), true),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -111,9 +113,9 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
item.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan);
|
||||
item.OnRequestLeave += chan => OnRequestLeave?.Invoke(chan);
|
||||
|
||||
FillFlowContainer<ChannelListItem> flow = getFlowForChannel(channel);
|
||||
ChannelGroup group = getGroupFromChannel(channel);
|
||||
channelMap.Add(channel, item);
|
||||
flow.Add(item);
|
||||
group.AddChannel(item);
|
||||
|
||||
updateVisibility();
|
||||
}
|
||||
@ -123,50 +125,58 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
if (!channelMap.TryGetValue(channel, out var item))
|
||||
return;
|
||||
|
||||
FillFlowContainer<ChannelListItem> flow = getFlowForChannel(channel);
|
||||
ChannelGroup group = getGroupFromChannel(channel);
|
||||
|
||||
channelMap.Remove(channel);
|
||||
flow.Remove(item, true);
|
||||
group.RemoveChannel(item);
|
||||
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
public ChannelListItem GetItem(Channel channel) => channelMap[channel];
|
||||
public ChannelListItem GetItem(Channel channel)
|
||||
{
|
||||
if (!channelMap.TryGetValue(channel, out var item))
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public void ScrollChannelIntoView(Channel channel) => scroll.ScrollIntoView(GetItem(channel));
|
||||
|
||||
private FillFlowContainer<ChannelListItem> getFlowForChannel(Channel channel)
|
||||
private ChannelGroup getGroupFromChannel(Channel channel)
|
||||
{
|
||||
switch (channel.Type)
|
||||
{
|
||||
case ChannelType.Public:
|
||||
return publicChannelGroup.ItemFlow;
|
||||
return PublicChannelGroup;
|
||||
|
||||
case ChannelType.PM:
|
||||
return privateChannelGroup.ItemFlow;
|
||||
return PrivateChannelGroup;
|
||||
|
||||
case ChannelType.Announce:
|
||||
return announceChannelGroup.ItemFlow;
|
||||
return AnnounceChannelGroup;
|
||||
|
||||
default:
|
||||
return publicChannelGroup.ItemFlow;
|
||||
return PublicChannelGroup;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateVisibility()
|
||||
{
|
||||
if (announceChannelGroup.ItemFlow.Children.Count == 0)
|
||||
announceChannelGroup.Hide();
|
||||
if (AnnounceChannelGroup.ItemFlow.Children.Count == 0)
|
||||
AnnounceChannelGroup.Hide();
|
||||
else
|
||||
announceChannelGroup.Show();
|
||||
AnnounceChannelGroup.Show();
|
||||
}
|
||||
|
||||
private partial class ChannelGroup : FillFlowContainer
|
||||
public partial class ChannelGroup : FillFlowContainer
|
||||
{
|
||||
public readonly FillFlowContainer<ChannelListItem> ItemFlow;
|
||||
private readonly bool sortByRecent;
|
||||
public readonly ChannelListItemFlow ItemFlow;
|
||||
|
||||
public ChannelGroup(LocalisableString label)
|
||||
public ChannelGroup(LocalisableString label, bool sortByRecent)
|
||||
{
|
||||
this.sortByRecent = sortByRecent;
|
||||
Direction = FillDirection.Vertical;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
@ -180,7 +190,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
Margin = new MarginPadding { Left = 18, Bottom = 5 },
|
||||
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold),
|
||||
},
|
||||
ItemFlow = new FillFlowContainer<ChannelListItem>
|
||||
ItemFlow = new ChannelListItemFlow(sortByRecent)
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@ -188,6 +198,60 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public partial class ChannelListItemFlow : FillFlowContainer<ChannelListItem>
|
||||
{
|
||||
private readonly bool sortByRecent;
|
||||
|
||||
public ChannelListItemFlow(bool sortByRecent)
|
||||
{
|
||||
this.sortByRecent = sortByRecent;
|
||||
}
|
||||
|
||||
public void Reflow() => InvalidateLayout();
|
||||
|
||||
public override IEnumerable<Drawable> FlowingChildren => sortByRecent
|
||||
? base.FlowingChildren.OfType<ChannelListItem>().OrderByDescending(i => i.Channel.LastMessageId ?? long.MinValue)
|
||||
: base.FlowingChildren.OfType<ChannelListItem>().OrderBy(i => i.Channel.Name);
|
||||
}
|
||||
|
||||
public void AddChannel(ChannelListItem item)
|
||||
{
|
||||
ItemFlow.Add(item);
|
||||
|
||||
if (sortByRecent)
|
||||
{
|
||||
item.Channel.NewMessagesArrived += newMessagesArrived;
|
||||
item.Channel.PendingMessageResolved += pendingMessageResolved;
|
||||
}
|
||||
|
||||
ItemFlow.Reflow();
|
||||
}
|
||||
|
||||
public void RemoveChannel(ChannelListItem item)
|
||||
{
|
||||
if (sortByRecent)
|
||||
{
|
||||
item.Channel.NewMessagesArrived -= newMessagesArrived;
|
||||
item.Channel.PendingMessageResolved -= pendingMessageResolved;
|
||||
}
|
||||
|
||||
ItemFlow.Remove(item, true);
|
||||
}
|
||||
|
||||
private void pendingMessageResolved(LocalEchoMessage _, Message __) => ItemFlow.Reflow();
|
||||
private void newMessagesArrived(IEnumerable<Message> _) => ItemFlow.Reflow();
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
foreach (var item in ItemFlow)
|
||||
{
|
||||
item.Channel.NewMessagesArrived -= newMessagesArrived;
|
||||
item.Channel.PendingMessageResolved -= pendingMessageResolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private partial class ChannelSearchTextBox : BasicSearchTextBox
|
||||
|
58
osu.Game/Overlays/DevBuildBanner.cs
Normal file
58
osu.Game/Overlays/DevBuildBanner.cs
Normal file
@ -0,0 +1,58 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public partial class DevBuildBanner : VisibilityContainer
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, TextureStore textures, OsuGameBase game)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Anchor = Anchor.BottomCentre;
|
||||
Origin = Anchor.BottomCentre;
|
||||
|
||||
Alpha = 0;
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Font = OsuFont.Numeric.With(weight: FontWeight.Bold, size: 12),
|
||||
Colour = colours.YellowDark,
|
||||
Text = @"DEVELOPER BUILD",
|
||||
},
|
||||
new Sprite
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Texture = textures.Get(@"Menu/dev-build-footer"),
|
||||
Scale = new Vector2(0.4f, 1),
|
||||
Y = 2,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
this.FadeIn(1400, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
this.FadeOut(500, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
@ -138,34 +138,31 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
topFifty.ValueColour = colourProvider.Content2;
|
||||
}
|
||||
|
||||
// reference: https://github.com/ppy/osu-web/blob/adf1e94754ba9625b85eba795f4a310caf169eec/resources/js/profile-page/daily-challenge.tsx#L13-L47
|
||||
// reference: https://github.com/ppy/osu-web/blob/a97f156014e00ea1aa315140da60542e798a9f06/resources/js/profile-page/daily-challenge.tsx#L13-L47
|
||||
|
||||
// Rounding up is needed here to ensure the overlay shows the same colour as osu-web for the play count.
|
||||
// This is because, for example, 31 / 3 > 10 in JavaScript because floats are used, while here it would
|
||||
// get truncated to 10 with an integer division and show a lower tier.
|
||||
public static RankingTier TierForPlayCount(int playCount) => TierForDaily((int)Math.Ceiling(playCount / 3.0d));
|
||||
public static RankingTier TierForPlayCount(int playCount) => TierForDaily((int)Math.Floor(playCount / 3.0d));
|
||||
|
||||
public static RankingTier TierForDaily(int daily)
|
||||
{
|
||||
if (daily > 360)
|
||||
if (daily >= 360)
|
||||
return RankingTier.Lustrous;
|
||||
|
||||
if (daily > 240)
|
||||
if (daily >= 240)
|
||||
return RankingTier.Radiant;
|
||||
|
||||
if (daily > 120)
|
||||
if (daily >= 120)
|
||||
return RankingTier.Rhodium;
|
||||
|
||||
if (daily > 60)
|
||||
if (daily >= 60)
|
||||
return RankingTier.Platinum;
|
||||
|
||||
if (daily > 30)
|
||||
if (daily >= 30)
|
||||
return RankingTier.Gold;
|
||||
|
||||
if (daily > 10)
|
||||
if (daily >= 10)
|
||||
return RankingTier.Silver;
|
||||
|
||||
if (daily > 5)
|
||||
if (daily >= 5)
|
||||
return RankingTier.Bronze;
|
||||
|
||||
return RankingTier.Iron;
|
||||
|
@ -35,6 +35,7 @@ using osu.Game.Screens.Edit.Components.Menus;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
@ -709,7 +710,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException();
|
||||
|
||||
public IEnumerable<string> HandledExtensions => new[] { ".jpg", ".jpeg", ".png" };
|
||||
public IEnumerable<string> HandledExtensions => SupportedExtensions.IMAGE_EXTENSIONS;
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -1,95 +0,0 @@
|
||||
// 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.Development;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public partial class VersionManager : VisibilityContainer
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, TextureStore textures, OsuGameBase game)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Anchor = Anchor.BottomCentre;
|
||||
Origin = Anchor.BottomCentre;
|
||||
|
||||
Alpha = 0;
|
||||
|
||||
FillFlowContainer mainFill;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
mainFill = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
||||
Text = game.Name
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Colour = DebugUtils.IsDebugBuild ? colours.Red : Color4.White,
|
||||
Text = game.Version
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!game.IsDeployedBuild)
|
||||
{
|
||||
mainFill.AddRange(new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Font = OsuFont.Numeric.With(size: 12),
|
||||
Colour = colours.Yellow,
|
||||
Text = @"Development Build"
|
||||
},
|
||||
new Sprite
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Texture = textures.Get(@"Menu/dev-build-footer"),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
this.FadeIn(1400, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
this.FadeOut(500, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,13 +3,12 @@
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||
{
|
||||
public static class AudioCheckUtils
|
||||
{
|
||||
public static readonly string[] AUDIO_EXTENSIONS = { "mp3", "ogg", "wav" };
|
||||
|
||||
public static bool HasAudioExtension(string filename) => AUDIO_EXTENSIONS.Any(Path.GetExtension(filename).ToLowerInvariant().EndsWith);
|
||||
public static bool HasAudioExtension(string filename) => SupportedExtensions.AUDIO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ namespace osu.Game.Scoring
|
||||
/// Perform a lookup query on available <see cref="ScoreInfo"/>s.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||
/// <returns>The first result for the provided query in its detached form, or null if no results were found.</returns>
|
||||
public ScoreInfo? Query(Expression<Func<ScoreInfo, bool>> query)
|
||||
{
|
||||
return Realm.Run(r => r.All<ScoreInfo>().FirstOrDefault(query)?.Detach());
|
||||
@ -88,8 +88,14 @@ namespace osu.Game.Scoring
|
||||
{
|
||||
ScoreInfo? databasedScoreInfo = null;
|
||||
|
||||
if (originalScoreInfo is ScoreInfo scoreInfo && !string.IsNullOrEmpty(scoreInfo.Hash))
|
||||
databasedScoreInfo = Query(s => s.Hash == scoreInfo.Hash);
|
||||
if (originalScoreInfo is ScoreInfo scoreInfo)
|
||||
{
|
||||
if (scoreInfo.IsManaged)
|
||||
return scoreInfo.Detach();
|
||||
|
||||
if (!string.IsNullOrEmpty(scoreInfo.Hash))
|
||||
databasedScoreInfo = Query(s => s.Hash == scoreInfo.Hash);
|
||||
}
|
||||
|
||||
if (originalScoreInfo.OnlineID > 0)
|
||||
databasedScoreInfo ??= Query(s => s.OnlineID == originalScoreInfo.OnlineID);
|
||||
|
@ -10,6 +10,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Setup
|
||||
{
|
||||
@ -48,12 +49,12 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
backgroundChooser = new FormFileSelector(".jpg", ".jpeg", ".png")
|
||||
backgroundChooser = new FormFileSelector(SupportedExtensions.IMAGE_EXTENSIONS)
|
||||
{
|
||||
Caption = GameplaySettingsStrings.BackgroundHeader,
|
||||
PlaceholderText = EditorSetupStrings.ClickToSelectBackground,
|
||||
},
|
||||
audioTrackChooser = new FormFileSelector(".mp3", ".ogg")
|
||||
audioTrackChooser = new FormFileSelector(SupportedExtensions.AUDIO_EXTENSIONS)
|
||||
{
|
||||
Caption = EditorSetupStrings.AudioTrack,
|
||||
PlaceholderText = EditorSetupStrings.ClickToSelectTrack,
|
||||
|
@ -79,9 +79,6 @@ namespace osu.Game.Screens.Menu
|
||||
[Resolved(canBeNull: true)]
|
||||
private IDialogOverlay dialogOverlay { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private VersionManager versionManager { get; set; }
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault();
|
||||
|
||||
protected override bool PlayExitSound => false;
|
||||
@ -294,16 +291,6 @@ namespace osu.Game.Screens.Menu
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
bottomElementsFlow.Margin = new MarginPadding
|
||||
{
|
||||
Bottom = (versionManager?.DrawHeight + 5) ?? 0
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LogoSuspending(OsuLogo logo)
|
||||
{
|
||||
var seq = logo.FadeOut(300, Easing.InSine)
|
||||
|
@ -345,7 +345,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
private void presentScore(long id)
|
||||
{
|
||||
if (this.IsCurrentScreen())
|
||||
this.Push(new PlaylistItemScoreResultsScreen(room.RoomID!.Value, playlistItem, id));
|
||||
this.Push(new PlaylistItemScoreResultsScreen(id, room.RoomID!.Value, playlistItem));
|
||||
}
|
||||
|
||||
private void onRoomScoreSet(MultiplayerRoomScoreSetEvent e)
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using osu.Framework.Allocation;
|
||||
@ -22,9 +23,14 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using Container = osu.Framework.Graphics.Containers.Container;
|
||||
@ -48,6 +54,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
[Resolved(canBeNull: true)]
|
||||
private LoungeSubScreen? lounge { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IDialogOverlay? dialogOverlay { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
private readonly BindableWithCurrent<Room?> selectedRoom = new BindableWithCurrent<Room?>();
|
||||
private Sample? sampleSelect;
|
||||
private Sample? sampleJoin;
|
||||
@ -144,13 +156,34 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
|
||||
public Popover GetPopover() => new PasswordEntryPopover(Room);
|
||||
|
||||
public MenuItem[] ContextMenuItems => new MenuItem[]
|
||||
public MenuItem[] ContextMenuItems
|
||||
{
|
||||
new OsuMenuItem("Create copy", MenuItemType.Standard, () =>
|
||||
get
|
||||
{
|
||||
lounge?.OpenCopy(Room);
|
||||
})
|
||||
};
|
||||
var items = new List<MenuItem>
|
||||
{
|
||||
new OsuMenuItem("Create copy", MenuItemType.Standard, () =>
|
||||
{
|
||||
lounge?.OpenCopy(Room);
|
||||
})
|
||||
};
|
||||
|
||||
if (Room.Type == MatchType.Playlists && Room.Host?.Id == api.LocalUser.Value.Id && Room.StartDate?.AddMinutes(5) >= DateTimeOffset.Now && Room.Status is not RoomStatusEnded)
|
||||
{
|
||||
items.Add(new OsuMenuItem("Close playlist", MenuItemType.Destructive, () =>
|
||||
{
|
||||
dialogOverlay?.Push(new ClosePlaylistDialog(Room, () =>
|
||||
{
|
||||
var request = new ClosePlaylistRequest(Room.RoomID!.Value);
|
||||
request.Success += () => lounge?.RefreshRooms();
|
||||
api.Queue(request);
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
|
@ -379,6 +379,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
this.Push(CreateRoomSubScreen(room));
|
||||
}
|
||||
|
||||
public void RefreshRooms() => ListingPollingComponent.PollImmediately();
|
||||
|
||||
private void updateLoadingLayer()
|
||||
{
|
||||
if (operationInProgress.Value || !ListingPollingComponent.InitialRoomsReceived.Value)
|
||||
|
@ -71,7 +71,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
protected RulesetStore Rulesets { get; private set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
protected IAPIProvider API { get; private set; } = null!;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
protected OnlinePlayScreen? ParentScreen { get; private set; }
|
||||
@ -80,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
private PreviewTrackManager previewTrackManager { get; set; } = null!;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private IDialogOverlay? dialogOverlay { get; set; }
|
||||
protected IDialogOverlay? DialogOverlay { get; private set; }
|
||||
|
||||
[Cached]
|
||||
private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
|
||||
@ -282,7 +282,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool IsConnected => api.State.Value == APIState.Online;
|
||||
protected virtual bool IsConnected => API.State.Value == APIState.Online;
|
||||
|
||||
public override bool OnBackButton()
|
||||
{
|
||||
@ -361,17 +361,17 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
|
||||
bool hasUnsavedChanges = Room.RoomID == null && Room.Playlist.Count > 0;
|
||||
|
||||
if (dialogOverlay == null || !hasUnsavedChanges)
|
||||
if (DialogOverlay == null || !hasUnsavedChanges)
|
||||
return true;
|
||||
|
||||
// if the dialog is already displayed, block exiting until the user explicitly makes a decision.
|
||||
if (dialogOverlay.CurrentDialog is ConfirmDiscardChangesDialog discardChangesDialog)
|
||||
if (DialogOverlay.CurrentDialog is ConfirmDiscardChangesDialog discardChangesDialog)
|
||||
{
|
||||
discardChangesDialog.Flash();
|
||||
return false;
|
||||
}
|
||||
|
||||
dialogOverlay.Push(new ConfirmDiscardChangesDialog(() =>
|
||||
DialogOverlay.Push(new ConfirmDiscardChangesDialog(() =>
|
||||
{
|
||||
ExitConfirmed = true;
|
||||
settingsOverlay.Hide();
|
||||
|
@ -7,7 +7,7 @@ using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
public partial class MultiplayerResultsScreen : PlaylistItemUserResultsScreen
|
||||
public partial class MultiplayerResultsScreen : PlaylistItemScoreResultsScreen
|
||||
{
|
||||
public MultiplayerResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem)
|
||||
: base(score, roomId, playlistItem)
|
||||
|
19
osu.Game/Screens/OnlinePlay/Playlists/ClosePlaylistDialog.cs
Normal file
19
osu.Game/Screens/OnlinePlay/Playlists/ClosePlaylistDialog.cs
Normal file
@ -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 System;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
public partial class ClosePlaylistDialog : DeletionDialog
|
||||
{
|
||||
public ClosePlaylistDialog(Room room, Action closeAction)
|
||||
{
|
||||
HeaderText = "Are you sure you want to close the following playlist:";
|
||||
BodyText = room.Name;
|
||||
DangerousAction = closeAction;
|
||||
}
|
||||
}
|
||||
}
|
@ -191,8 +191,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
var scoreInfos = scores.Select(s => s.CreateScoreInfo(ScoreManager, Rulesets, PlaylistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray();
|
||||
|
||||
// Invoke callback to add the scores.
|
||||
callback.Invoke(scoreInfos);
|
||||
// Invoke callback to add the scores. Exclude the score provided to this screen since it's added already.
|
||||
callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID));
|
||||
|
||||
return scoreInfos;
|
||||
}
|
||||
|
@ -11,13 +11,19 @@ using osu.Game.Scoring;
|
||||
namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
/// <summary>
|
||||
/// Shows a selected arbitrary score for a playlist item, with scores around included.
|
||||
/// Shows a given score in a playlist item, with scores around included.
|
||||
/// </summary>
|
||||
public partial class PlaylistItemScoreResultsScreen : PlaylistItemResultsScreen
|
||||
{
|
||||
private readonly long scoreId;
|
||||
|
||||
public PlaylistItemScoreResultsScreen(long roomId, PlaylistItem playlistItem, long scoreId)
|
||||
public PlaylistItemScoreResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem)
|
||||
: base(score, roomId, playlistItem)
|
||||
{
|
||||
scoreId = score.OnlineID;
|
||||
}
|
||||
|
||||
public PlaylistItemScoreResultsScreen(long scoreId, long roomId, PlaylistItem playlistItem)
|
||||
: base(null, roomId, playlistItem)
|
||||
{
|
||||
this.scoreId = scoreId;
|
||||
@ -28,9 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
protected override ScoreInfo[] PerformSuccessCallback(Action<IEnumerable<ScoreInfo>> callback, List<MultiplayerScore> scores, MultiplayerScores? pivot = null)
|
||||
{
|
||||
var scoreInfos = base.PerformSuccessCallback(callback, scores, pivot);
|
||||
|
||||
Schedule(() => SelectedScore.Value ??= scoreInfos.SingleOrDefault(score => score.OnlineID == scoreId));
|
||||
|
||||
Schedule(() => SelectedScore.Value ??= scoreInfos.SingleOrDefault(s => s.OnlineID == scoreId));
|
||||
return scoreInfos;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
/// <summary>
|
||||
/// Shows a user's best score in a playlist item, with scores around included.
|
||||
/// </summary>
|
||||
public partial class PlaylistItemUserBestResultsScreen : PlaylistItemResultsScreen
|
||||
{
|
||||
private readonly int userId;
|
||||
|
||||
public PlaylistItemUserBestResultsScreen(long roomId, PlaylistItem playlistItem, int userId)
|
||||
: base(null, roomId, playlistItem)
|
||||
{
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
protected override APIRequest<MultiplayerScore> CreateScoreRequest() => new ShowPlaylistUserScoreRequest(RoomId, PlaylistItem.ID, userId);
|
||||
|
||||
protected override ScoreInfo[] PerformSuccessCallback(Action<IEnumerable<ScoreInfo>> callback, List<MultiplayerScore> scores, MultiplayerScores? pivot = null)
|
||||
{
|
||||
var scoreInfos = base.PerformSuccessCallback(callback, scores, pivot);
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
// Prefer selecting the local user's score, or otherwise default to the first visible score.
|
||||
SelectedScore.Value ??= scoreInfos.FirstOrDefault(s => s.UserID == userId) ?? scoreInfos.FirstOrDefault();
|
||||
});
|
||||
|
||||
return scoreInfos;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
/// <summary>
|
||||
/// Shows the user's best score for a given playlist item, with scores around included.
|
||||
/// </summary>
|
||||
public partial class PlaylistItemUserResultsScreen : PlaylistItemResultsScreen
|
||||
{
|
||||
public PlaylistItemUserResultsScreen(ScoreInfo? score, long roomId, PlaylistItem playlistItem)
|
||||
: base(score, roomId, playlistItem)
|
||||
{
|
||||
}
|
||||
|
||||
protected override APIRequest<MultiplayerScore> CreateScoreRequest() => new ShowPlaylistUserScoreRequest(RoomId, PlaylistItem.ID, API.LocalUser.Value.Id);
|
||||
|
||||
protected override ScoreInfo[] PerformSuccessCallback(Action<IEnumerable<ScoreInfo>> callback, List<MultiplayerScore> scores, MultiplayerScores? pivot = null)
|
||||
{
|
||||
var scoreInfos = scores.Select(s => s.CreateScoreInfo(ScoreManager, Rulesets, PlaylistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray();
|
||||
|
||||
// Select a score if we don't already have one selected.
|
||||
// Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll).
|
||||
if (SelectedScore.Value == null)
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
// Prefer selecting the local user's score, or otherwise default to the first visible score.
|
||||
SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.OnlineID == API.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault();
|
||||
});
|
||||
}
|
||||
|
||||
// Invoke callback to add the scores. Exclude the user's current score which was added previously.
|
||||
callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID));
|
||||
|
||||
return scoreInfos;
|
||||
}
|
||||
}
|
||||
}
|
@ -56,7 +56,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
protected override ResultsScreen CreateResults(ScoreInfo score)
|
||||
{
|
||||
Debug.Assert(Room.RoomID != null);
|
||||
return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value, PlaylistItem)
|
||||
return new PlaylistItemScoreResultsScreen(score, Room.RoomID.Value, PlaylistItem)
|
||||
{
|
||||
AllowRetry = true,
|
||||
ShowUserStatistics = true,
|
||||
|
@ -2,9 +2,14 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
@ -12,22 +17,104 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
public partial class PlaylistsRoomFooter : CompositeDrawable
|
||||
{
|
||||
public Action? OnStart;
|
||||
public Action? OnClose;
|
||||
|
||||
private readonly Room room;
|
||||
private DangerousRoundedButton closeButton = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
public PlaylistsRoomFooter(Room room)
|
||||
{
|
||||
this.room = room;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new[]
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
new PlaylistsReadyButton(room)
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Size = new Vector2(600, 1),
|
||||
Action = () => OnStart?.Invoke()
|
||||
new PlaylistsReadyButton(room)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Size = new Vector2(600, 1),
|
||||
Action = () => OnStart?.Invoke()
|
||||
},
|
||||
closeButton = new DangerousRoundedButton
|
||||
{
|
||||
Text = "Close",
|
||||
Action = () => OnClose?.Invoke(),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(120, 1),
|
||||
Alpha = 0,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
room.PropertyChanged += onRoomChanged;
|
||||
updateState();
|
||||
}
|
||||
|
||||
private void hideCloseButton()
|
||||
{
|
||||
closeButton.ResizeWidthTo(0, 100, Easing.OutQuint)
|
||||
.Then().FadeOut().Expire();
|
||||
}
|
||||
|
||||
private void onRoomChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(Room.Status):
|
||||
case nameof(Room.Host):
|
||||
case nameof(Room.StartDate):
|
||||
updateState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
TimeSpan? deletionGracePeriodRemaining = room.StartDate?.AddMinutes(5) - DateTimeOffset.Now;
|
||||
|
||||
if (room.Host?.Id == api.LocalUser.Value.Id)
|
||||
{
|
||||
if (deletionGracePeriodRemaining > TimeSpan.Zero && room.Status is not RoomStatusEnded)
|
||||
{
|
||||
closeButton.FadeIn();
|
||||
using (BeginDelayedSequence(deletionGracePeriodRemaining.Value.TotalMilliseconds))
|
||||
hideCloseButton();
|
||||
}
|
||||
else if (closeButton.Alpha > 0)
|
||||
hideCloseButton();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
room.PropertyChanged -= onRoomChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
@ -12,7 +13,10 @@ using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
@ -32,6 +36,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
|
||||
private readonly IBindable<bool> isIdle = new BindableBool();
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IdleTracker? idleTracker { get; set; }
|
||||
|
||||
@ -143,7 +150,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
RequestResults = item =>
|
||||
{
|
||||
Debug.Assert(Room.RoomID != null);
|
||||
ParentScreen?.Push(new PlaylistItemUserResultsScreen(null, Room.RoomID.Value, item));
|
||||
ParentScreen?.Push(new PlaylistItemUserBestResultsScreen(Room.RoomID.Value, item, api.LocalUser.Value.Id));
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -255,7 +262,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
|
||||
protected override Drawable CreateFooter() => new PlaylistsRoomFooter(Room)
|
||||
{
|
||||
OnStart = StartPlay
|
||||
OnStart = StartPlay,
|
||||
OnClose = closePlaylist,
|
||||
};
|
||||
|
||||
protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new PlaylistsRoomSettingsOverlay(room)
|
||||
@ -273,6 +281,20 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
Logger.Log($"Polling adjusted (selection: {selectionPollingComponent.TimeBetweenPolls.Value})");
|
||||
}
|
||||
|
||||
private void closePlaylist()
|
||||
{
|
||||
DialogOverlay?.Push(new ClosePlaylistDialog(Room, () =>
|
||||
{
|
||||
var request = new ClosePlaylistRequest(Room.RoomID!.Value);
|
||||
request.Success += () =>
|
||||
{
|
||||
Room.Status = new RoomStatusEnded();
|
||||
Room.EndDate = DateTimeOffset.UtcNow;
|
||||
};
|
||||
API.Queue(request);
|
||||
}));
|
||||
}
|
||||
|
||||
protected override Screen CreateGameplayScreen(PlaylistItem selectedItem)
|
||||
{
|
||||
return new PlayerLoader(() => new PlaylistsPlayer(Room, selectedItem)
|
||||
|
@ -28,6 +28,7 @@ using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Performance;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Play.PlayerSettings;
|
||||
using osu.Game.Skinning;
|
||||
@ -78,6 +79,8 @@ namespace osu.Game.Screens.Play
|
||||
private FillFlowContainer disclaimers = null!;
|
||||
private OsuScrollContainer settingsScroll = null!;
|
||||
|
||||
private Bindable<ScoreInfo?> lastScore = null!;
|
||||
|
||||
private Bindable<bool> showStoryboards = null!;
|
||||
|
||||
private bool backgroundBrightnessReduction;
|
||||
@ -179,6 +182,8 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
muteWarningShownOnce = sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce);
|
||||
batteryWarningShownOnce = sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce);
|
||||
lastScore = sessionStatics.GetBindable<ScoreInfo?>(Static.LastLocalUserScore);
|
||||
|
||||
showStoryboards = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
|
||||
|
||||
const float padding = 25;
|
||||
@ -347,6 +352,8 @@ namespace osu.Game.Screens.Play
|
||||
highPerformanceSession?.Dispose();
|
||||
highPerformanceSession = null;
|
||||
|
||||
lastScore.Value = null;
|
||||
|
||||
return base.OnExiting(e);
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private readonly Func<Task<ScoreInfo>>? importFailedScore;
|
||||
|
||||
private ScoreInfo? importedScore;
|
||||
private Live<ScoreInfo>? importedScore;
|
||||
|
||||
private DownloadButton button = null!;
|
||||
|
||||
@ -55,7 +55,7 @@ namespace osu.Game.Screens.Play
|
||||
switch (state.Value)
|
||||
{
|
||||
case DownloadState.LocallyAvailable:
|
||||
game?.PresentScore(importedScore, ScorePresentType.Gameplay);
|
||||
game?.PresentScore(importedScore?.Value, ScorePresentType.Gameplay);
|
||||
break;
|
||||
|
||||
case DownloadState.NotDownloaded:
|
||||
@ -65,7 +65,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
Task.Run(importFailedScore).ContinueWith(t =>
|
||||
{
|
||||
importedScore = realm.Run(r => r.Find<ScoreInfo>(t.GetResultSafely().ID)?.Detach());
|
||||
importedScore = realm.Run<Live<ScoreInfo>?>(r => r.Find<ScoreInfo>(t.GetResultSafely().ID)?.ToLive(realm));
|
||||
Schedule(() => state.Value = importedScore != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded);
|
||||
}).FireAndForget();
|
||||
}
|
||||
@ -77,7 +77,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
if (player != null)
|
||||
{
|
||||
importedScore = realm.Run(r => r.Find<ScoreInfo>(player.Score.ScoreInfo.ID)?.Detach());
|
||||
importedScore = realm.Run(r => r.Find<ScoreInfo>(player.Score.ScoreInfo.ID)?.ToLive(realm));
|
||||
state.Value = importedScore != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded;
|
||||
}
|
||||
|
||||
@ -137,7 +137,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
if (state.NewValue != DownloadState.LocallyAvailable) return;
|
||||
|
||||
if (importedScore != null) scoreManager.Export(importedScore);
|
||||
if (importedScore != null) scoreManager.Export(importedScore.Value);
|
||||
|
||||
this.state.ValueChanged -= exportWhenReady;
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ using osu.Game.Input.Bindings;
|
||||
using osu.Game.Screens.Select.Carousel;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Screens.Select
|
||||
{
|
||||
@ -207,8 +206,6 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
private CarouselRoot root;
|
||||
|
||||
private IDisposable? subscriptionBeatmaps;
|
||||
|
||||
private readonly DrawablePool<DrawableCarouselBeatmapSet> setPool = new DrawablePool<DrawableCarouselBeatmapSet>(100);
|
||||
|
||||
private Sample? spinSample;
|
||||
@ -258,13 +255,6 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
subscriptionBeatmaps = realm.RegisterForNotifications(r => r.All<BeatmapInfo>().Where(b => !b.Hidden), beatmapsChanged);
|
||||
}
|
||||
|
||||
private readonly HashSet<BeatmapSetInfo> setsRequiringUpdate = new HashSet<BeatmapSetInfo>();
|
||||
private readonly HashSet<BeatmapSetInfo> setsRequiringRemoval = new HashSet<BeatmapSetInfo>();
|
||||
|
||||
@ -366,35 +356,6 @@ namespace osu.Game.Screens.Select
|
||||
BeatmapSetInfo? fetchFromID(Guid id) => realm.Realm.Find<BeatmapSetInfo>(id);
|
||||
}
|
||||
|
||||
private void beatmapsChanged(IRealmCollection<BeatmapInfo> sender, ChangeSet? changes)
|
||||
{
|
||||
// we only care about actual changes in hidden status.
|
||||
if (changes == null)
|
||||
return;
|
||||
|
||||
bool changed = false;
|
||||
|
||||
foreach (int i in changes.InsertedIndices)
|
||||
{
|
||||
var beatmapInfo = sender[i];
|
||||
var beatmapSet = beatmapInfo.BeatmapSet;
|
||||
|
||||
Debug.Assert(beatmapSet != null);
|
||||
|
||||
// Only require to action here if the beatmap is missing.
|
||||
// This avoids processing these events unnecessarily when new beatmaps are imported, for example.
|
||||
if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSets)
|
||||
&& existingSets.SelectMany(s => s.Beatmaps).All(b => b.BeatmapInfo.ID != beatmapInfo.ID))
|
||||
{
|
||||
updateBeatmapSet(beatmapSet.Detach());
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
invalidateAfterChange();
|
||||
}
|
||||
|
||||
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() =>
|
||||
{
|
||||
removeBeatmapSet(beatmapSet.ID);
|
||||
@ -1292,12 +1253,5 @@ namespace osu.Game.Screens.Select
|
||||
return ScrollableExtent * ((scrollbarPosition - top_padding) / (ScrollbarMovementExtent - (top_padding + bottom_padding)));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
subscriptionBeatmaps?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -14,6 +14,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Localisation.SkinComponents;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
@ -93,10 +94,10 @@ namespace osu.Game.Skinning
|
||||
// but that requires further thought.
|
||||
var highestPrioritySkin = getHighestPriorityUserSkin(((SkinnableSprite)SettingSourceObject).source.AllSources) as Skin;
|
||||
|
||||
string[]? availableFiles = highestPrioritySkin?.SkinInfo.PerformRead(s => s.Files
|
||||
.Where(f => f.Filename.EndsWith(".png", StringComparison.Ordinal)
|
||||
|| f.Filename.EndsWith(".jpg", StringComparison.Ordinal))
|
||||
.Select(f => f.Filename).Distinct()).ToArray();
|
||||
string[]? availableFiles = highestPrioritySkin?.SkinInfo.PerformRead(
|
||||
s => s.Files
|
||||
.Where(f => SupportedExtensions.IMAGE_EXTENSIONS.Contains(Path.GetExtension(f.Filename).ToLowerInvariant()))
|
||||
.Select(f => f.Filename).Distinct()).ToArray();
|
||||
|
||||
if (availableFiles?.Length > 0)
|
||||
Items = availableFiles;
|
||||
|
@ -8,6 +8,7 @@ using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
@ -97,8 +98,6 @@ namespace osu.Game.Storyboards
|
||||
public virtual DrawableStoryboard CreateDrawable(IReadOnlyList<Mod>? mods = null) =>
|
||||
new DrawableStoryboard(this, mods);
|
||||
|
||||
private static readonly string[] image_extensions = { @".png", @".jpg" };
|
||||
|
||||
public virtual string? GetStoragePathFromStoryboardPath(string path)
|
||||
{
|
||||
string? resolvedPath = null;
|
||||
@ -110,7 +109,7 @@ namespace osu.Game.Storyboards
|
||||
else
|
||||
{
|
||||
// Some old storyboards don't include a file extension, so let's best guess at one.
|
||||
foreach (string ext in image_extensions)
|
||||
foreach (string ext in SupportedExtensions.IMAGE_EXTENSIONS)
|
||||
{
|
||||
if ((resolvedPath = BeatmapInfo.BeatmapSet?.GetPathForFile($"{path}{ext}")) != null)
|
||||
break;
|
||||
|
19
osu.Game/Utils/SupportedExtensions.cs
Normal file
19
osu.Game/Utils/SupportedExtensions.cs
Normal file
@ -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.
|
||||
|
||||
namespace osu.Game.Utils
|
||||
{
|
||||
public static class SupportedExtensions
|
||||
{
|
||||
public static readonly string[] VIDEO_EXTENSIONS = [@".mp4", @".mov", @".avi", @".flv", @".mpg", @".wmv", @".m4v"];
|
||||
public static readonly string[] AUDIO_EXTENSIONS = [@".mp3", @".ogg", @".wav"];
|
||||
public static readonly string[] IMAGE_EXTENSIONS = [@".jpg", @".jpeg", @".png"];
|
||||
|
||||
public static readonly string[] ALL_EXTENSIONS =
|
||||
[
|
||||
..VIDEO_EXTENSIONS,
|
||||
..AUDIO_EXTENSIONS,
|
||||
..IMAGE_EXTENSIONS
|
||||
];
|
||||
}
|
||||
}
|
@ -35,7 +35,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="11.5.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.1118.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.1128.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.1106.0" />
|
||||
<PackageReference Include="Sentry" Version="4.13.0" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
|
@ -17,6 +17,6 @@
|
||||
<MtouchInterpreter>-all</MtouchInterpreter>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.1118.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.1128.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
Loading…
Reference in New Issue
Block a user