mirror of
https://github.com/ppy/osu.git
synced 2026-06-04 09:03:54 +08:00
Merge branch 'master' into playlist-item-copy
This commit is contained in:
@@ -136,4 +136,4 @@ jobs:
|
||||
run: dotnet workload install ios --from-rollback-file https://raw.githubusercontent.com/ppy/osu-framework/refs/heads/master/workloads.json
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c Debug osu.iOS
|
||||
run: dotnet build -c Debug osu.iOS.slnf
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.313.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.318.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
|
||||
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
||||
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(obj.StartTime).SliderVelocity : 1,
|
||||
TickDistanceMultiplier = beatmap.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(obj.StartTime).SliderVelocity : 1,
|
||||
SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1
|
||||
}.Yield();
|
||||
|
||||
|
||||
@@ -759,9 +759,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
BeatmapVersion = LegacyBeatmapEncoder.FIRST_LAZER_VERSION // for correct offset treatment by score encoder
|
||||
},
|
||||
ControlPointInfo = cpi
|
||||
ControlPointInfo = cpi,
|
||||
BeatmapVersion = LegacyBeatmapEncoder.FIRST_LAZER_VERSION // for correct offset treatment by score encoder
|
||||
});
|
||||
playableBeatmap = Beatmap.Value.GetPlayableBeatmap(new OsuRuleset().RulesetInfo);
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
||||
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(original.StartTime).SliderVelocity : 1,
|
||||
TickDistanceMultiplier = beatmap.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(original.StartTime).SliderVelocity : 1,
|
||||
GenerateTicks = generateTicksData?.GenerateTicks ?? true,
|
||||
SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1,
|
||||
}.Yield();
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
foreach (var h in hitObjects)
|
||||
h.StackHeight = 0;
|
||||
|
||||
if (beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
||||
if (beatmap.BeatmapVersion >= 6)
|
||||
applyStacking(beatmap, hitObjects, 0, hitObjects.Count - 1);
|
||||
else
|
||||
applyStackingOld(beatmap, hitObjects);
|
||||
|
||||
@@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
double osuVelocity = taikoVelocity * (1000f / beatLength);
|
||||
|
||||
// osu-stable always uses the speed-adjusted beatlength to determine the osu! velocity, but only uses it for conversion if beatmap version < 8
|
||||
if (beatmap.BeatmapInfo.BeatmapVersion >= 8)
|
||||
if (beatmap.BeatmapVersion >= 8)
|
||||
beatLength = timingPoint.BeatLength;
|
||||
|
||||
// If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.iOS.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0-ios</TargetFramework>
|
||||
@@ -6,11 +7,19 @@
|
||||
<RootNamespace>osu.Game.Tests</RootNamespace>
|
||||
<AssemblyName>osu.Game.Tests.iOS</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\osu.iOS.props" />
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);CA2007</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\osu.Game.Tests\**\*.cs" Exclude="**\obj\**">
|
||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
</Compile>
|
||||
<!-- TargetPath is relative to RootNamespace,
|
||||
and DllResourceStore is relative to AssemblyName. -->
|
||||
<EmbeddedResource Include="..\osu.Game.Tests\**\Resources\**\*">
|
||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
<TargetPath>iOS\%(RecursiveDir)%(Filename)%(Extension)</TargetPath>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Project References">
|
||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||
|
||||
@@ -42,9 +42,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var decoder = Decoder.GetDecoder<Beatmap>(stream);
|
||||
var working = new TestWorkingBeatmap(decoder.Decode(stream));
|
||||
|
||||
Assert.AreEqual(6, working.BeatmapInfo.BeatmapVersion);
|
||||
Assert.AreEqual(6, working.Beatmap.BeatmapInfo.BeatmapVersion);
|
||||
Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>()).BeatmapInfo.BeatmapVersion);
|
||||
Assert.AreEqual(6, working.Beatmap.BeatmapVersion);
|
||||
Assert.That(working.Beatmap.BeatmapInfo.Ruleset.Name, Is.Not.EqualTo("null placeholder ruleset"));
|
||||
Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>()).BeatmapVersion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,9 +59,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
((LegacyBeatmapDecoder)decoder).ApplyOffsets = applyOffsets;
|
||||
var working = new TestWorkingBeatmap(decoder.Decode(stream));
|
||||
|
||||
Assert.AreEqual(4, working.BeatmapInfo.BeatmapVersion);
|
||||
Assert.AreEqual(4, working.Beatmap.BeatmapInfo.BeatmapVersion);
|
||||
Assert.AreEqual(4, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>()).BeatmapInfo.BeatmapVersion);
|
||||
Assert.AreEqual(4, working.Beatmap.BeatmapVersion);
|
||||
Assert.AreEqual(4, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>()).BeatmapVersion);
|
||||
|
||||
Assert.AreEqual(-1, working.BeatmapInfo.Metadata.PreviewTime);
|
||||
}
|
||||
|
||||
@@ -155,10 +155,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
|
||||
var beatmap = new TestBeatmap(ruleset)
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
BeatmapVersion = beatmapVersion
|
||||
}
|
||||
BeatmapVersion = beatmapVersion
|
||||
};
|
||||
|
||||
var score = new Score
|
||||
@@ -633,14 +630,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
MD5Hash = md5Hash,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
Difficulty = new BeatmapDifficulty(),
|
||||
BeatmapVersion = beatmapVersion,
|
||||
},
|
||||
// needs to have at least one objects so that `StandardisedScoreMigrationTools` doesn't die
|
||||
// needs to have at least one object so that `StandardisedScoreMigrationTools` doesn't die
|
||||
// when trying to recompute total score.
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle()
|
||||
}
|
||||
},
|
||||
BeatmapVersion = beatmapVersion,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,5 +208,11 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor, () => Is.EqualTo(7));
|
||||
AddAssert("Correct beat divisor actually active", () => Editor.BeatDivisor, () => Is.EqualTo(7));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapVersionPopulatedCorrectly()
|
||||
{
|
||||
AddAssert("beatmap version is populated", () => EditorBeatmap.BeatmapVersion > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,30 +50,17 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
[Test]
|
||||
public void TestGameplay()
|
||||
{
|
||||
KiaiGameplayFountains fountains = null!;
|
||||
|
||||
AddStep("make fountains", () =>
|
||||
{
|
||||
Children = new[]
|
||||
{
|
||||
new KiaiGameplayFountains.GameplayStarFountain
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
X = 75,
|
||||
},
|
||||
new KiaiGameplayFountains.GameplayStarFountain
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
X = -75,
|
||||
},
|
||||
fountains = new KiaiGameplayFountains(),
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("activate fountains", () =>
|
||||
{
|
||||
((StarFountain)Children[0]).Shoot(1);
|
||||
((StarFountain)Children[1]).Shoot(-1);
|
||||
});
|
||||
AddStep("activate fountains", () => fountains.Shoot());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
multiplayerRoom = new MultiplayerRoom(0)
|
||||
{
|
||||
Playlist = { TestMultiplayerClient.CreateMultiplayerPlaylistItem(item) },
|
||||
Playlist = { new MultiplayerPlaylistItem(item) },
|
||||
Users = { localUser },
|
||||
Host = localUser,
|
||||
};
|
||||
|
||||
@@ -924,7 +924,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
enterGameplay();
|
||||
AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 }));
|
||||
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, TestMultiplayerClient.CreateMultiplayerPlaylistItem(
|
||||
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(
|
||||
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
|
||||
{
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
@@ -956,7 +956,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
enterGameplay();
|
||||
|
||||
AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 }));
|
||||
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, TestMultiplayerClient.CreateMultiplayerPlaylistItem(
|
||||
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(
|
||||
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
|
||||
{
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
|
||||
@@ -220,7 +220,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
/// </summary>
|
||||
private void addItemStep(bool expired = false, int? userId = null) => AddStep("add item", () =>
|
||||
{
|
||||
MultiplayerClient.AddUserPlaylistItem(userId ?? API.LocalUser.Value.OnlineID, TestMultiplayerClient.CreateMultiplayerPlaylistItem(new PlaylistItem(importedBeatmap)
|
||||
MultiplayerClient.AddUserPlaylistItem(userId ?? API.LocalUser.Value.OnlineID, new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap)
|
||||
{
|
||||
Expired = expired,
|
||||
PlayedAt = DateTimeOffset.Now
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("add playlist item", () =>
|
||||
{
|
||||
MultiplayerPlaylistItem item = TestMultiplayerClient.CreateMultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
|
||||
MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
|
||||
|
||||
MultiplayerClient.AddUserPlaylistItem(userId(), item).WaitSafely();
|
||||
|
||||
|
||||
@@ -289,12 +289,37 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
InputManager.MoveMouseTo(overlay.ChildrenOfType<DifficultyIcon>().ElementAt(0));
|
||||
});
|
||||
AddAssert("guest mapper information not shown", () => overlay.ChildrenOfType<BeatmapPicker>().Single().ChildrenOfType<OsuSpriteText>().All(s => s.Text != "BanchoBot"));
|
||||
AddAssert("guest mapper information not shown", () => overlay.ChildrenOfType<BeatmapPicker>().Single().ChildrenOfType<OsuSpriteText>().All(s => s.Text != "BanchoBot0"));
|
||||
AddStep("move mouse to guest difficulty", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(overlay.ChildrenOfType<DifficultyIcon>().ElementAt(1));
|
||||
});
|
||||
AddAssert("guest mapper information shown", () => overlay.ChildrenOfType<BeatmapPicker>().Single().ChildrenOfType<OsuSpriteText>().Any(s => s.Text == "BanchoBot0"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapsetWithALotGuestOwner()
|
||||
{
|
||||
AddStep("show map with 2 mapper", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty(2)));
|
||||
AddStep("move mouse to guest difficulty", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(overlay.ChildrenOfType<DifficultyIcon>().ElementAt(1));
|
||||
});
|
||||
AddStep("show map with 3 mapper", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty(3)));
|
||||
AddStep("move mouse to guest difficulty", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(overlay.ChildrenOfType<DifficultyIcon>().ElementAt(1));
|
||||
});
|
||||
AddStep("show map with 10 mapper", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty(10)));
|
||||
AddStep("move mouse to guest difficulty", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(overlay.ChildrenOfType<DifficultyIcon>().ElementAt(1));
|
||||
});
|
||||
AddStep("show map with 20 mapper", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty(20)));
|
||||
AddStep("move mouse to guest difficulty", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(overlay.ChildrenOfType<DifficultyIcon>().ElementAt(1));
|
||||
});
|
||||
AddAssert("guest mapper information shown", () => overlay.ChildrenOfType<BeatmapPicker>().Single().ChildrenOfType<OsuSpriteText>().Any(s => s.Text == "BanchoBot"));
|
||||
}
|
||||
|
||||
private APIBeatmapSet createManyDifficultiesBeatmapSet()
|
||||
@@ -336,22 +361,31 @@ namespace osu.Game.Tests.Visual.Online
|
||||
return beatmapSet;
|
||||
}
|
||||
|
||||
private APIBeatmapSet createBeatmapSetWithGuestDifficulty()
|
||||
private APIBeatmapSet createBeatmapSetWithGuestDifficulty(int guestCount = 1)
|
||||
{
|
||||
var set = getBeatmapSet();
|
||||
|
||||
var beatmaps = new List<APIBeatmap>();
|
||||
var beatmapOwners = new List<APIBeatmap.BeatmapOwner>();
|
||||
var ownersAPIUser = new List<APIUser>();
|
||||
|
||||
var guestUser = new APIUser
|
||||
for (int i = 0; i < guestCount; i++)
|
||||
{
|
||||
Username = @"BanchoBot",
|
||||
Id = 3,
|
||||
};
|
||||
var guestUser = new APIUser
|
||||
{
|
||||
Username = @$"BanchoBot{i}",
|
||||
Id = i + 3,
|
||||
};
|
||||
|
||||
set.RelatedUsers = new[]
|
||||
{
|
||||
set.Author, guestUser
|
||||
};
|
||||
beatmapOwners.Add(new APIBeatmap.BeatmapOwner
|
||||
{
|
||||
Username = @$"BanchoBot{i}",
|
||||
Id = i + 3,
|
||||
});
|
||||
ownersAPIUser.Add(guestUser);
|
||||
}
|
||||
|
||||
set.RelatedUsers = new[] { set.Author }.Concat(ownersAPIUser).ToArray();
|
||||
|
||||
beatmaps.Add(new APIBeatmap
|
||||
{
|
||||
@@ -366,7 +400,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(),
|
||||
Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(),
|
||||
},
|
||||
Status = BeatmapOnlineStatus.Graveyard
|
||||
Status = BeatmapOnlineStatus.Graveyard,
|
||||
});
|
||||
|
||||
beatmaps.Add(new APIBeatmap
|
||||
@@ -382,7 +416,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(),
|
||||
Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(),
|
||||
},
|
||||
Status = BeatmapOnlineStatus.Graveyard
|
||||
Status = BeatmapOnlineStatus.Graveyard,
|
||||
BeatmapOwners = beatmapOwners.ToArray(),
|
||||
});
|
||||
|
||||
set.Beatmaps = beatmaps.ToArray();
|
||||
|
||||
@@ -72,10 +72,10 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == 2);
|
||||
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
|
||||
|
||||
AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0));
|
||||
AddStep("User began playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.InSoloGame() }));
|
||||
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.True);
|
||||
|
||||
AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id));
|
||||
AddStep("User finished playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
|
||||
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
|
||||
|
||||
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
|
||||
@@ -88,13 +88,12 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
IDisposable token = null!;
|
||||
|
||||
AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0));
|
||||
AddStep("Begin watching user presence", () => token = metadataClient.BeginWatchingUserPresence());
|
||||
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
|
||||
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == 2);
|
||||
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.InSoloGame() }));
|
||||
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == streamingUser.Id);
|
||||
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.True);
|
||||
|
||||
AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id));
|
||||
AddStep("User finished playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
|
||||
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
|
||||
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
|
||||
AddStep("End watching user presence", () => token.Dispose());
|
||||
|
||||
@@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
Username = "flyte",
|
||||
Id = 3103765,
|
||||
IsOnline = true,
|
||||
WasRecentlyOnline = true,
|
||||
Statistics = new UserStatistics { GlobalRank = 1111 },
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
|
||||
@@ -243,7 +243,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
Username = "peppy",
|
||||
Id = 2,
|
||||
IsOnline = false,
|
||||
WasRecentlyOnline = false,
|
||||
Statistics = new UserStatistics { GlobalRank = 2222 },
|
||||
CountryCode = CountryCode.AU,
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
@@ -256,7 +256,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Id = 8195163,
|
||||
CountryCode = CountryCode.BY,
|
||||
CoverUrl = "https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
|
||||
IsOnline = false,
|
||||
WasRecentlyOnline = false,
|
||||
LastVisit = DateTimeOffset.Now
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.Containers.Markdown;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public partial class TestSceneImageProxying : OsuTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestExternalImageLink()
|
||||
{
|
||||
MarkdownContainer markdown = null!;
|
||||
|
||||
// use base MarkdownContainer as a method of directly attempting to load an image without proxying logic.
|
||||
AddStep("load external without proxying", () => Child = markdown = new MarkdownContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Text = "",
|
||||
});
|
||||
AddWaitStep("wait", 5);
|
||||
AddAssert("image not loaded", () => markdown.ChildrenOfType<Sprite>().SingleOrDefault()?.Texture == null);
|
||||
|
||||
AddStep("load external with proxying", () => Child = markdown = new OsuMarkdownContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Text = "",
|
||||
});
|
||||
AddUntilStep("image loaded", () => markdown.ChildrenOfType<Sprite>().SingleOrDefault()?.Texture != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
CountryCode = countryCode,
|
||||
CoverUrl = cover,
|
||||
Colour = color ?? "000000",
|
||||
IsOnline = true
|
||||
WasRecentlyOnline = true
|
||||
};
|
||||
|
||||
return new ClickableAvatar(user, showPanel)
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Id = 3103765,
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
|
||||
IsOnline = true
|
||||
WasRecentlyOnline = true
|
||||
}) { Width = 300 },
|
||||
new UserGridPanel(new APIUser
|
||||
{
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Id = 1001,
|
||||
Username = "IAmOnline",
|
||||
LastVisit = DateTimeOffset.Now,
|
||||
IsOnline = true,
|
||||
WasRecentlyOnline = true,
|
||||
}, new OsuRuleset().RulesetInfo));
|
||||
|
||||
AddStep("Show offline user", () => header.User.Value = new UserProfileData(new APIUser
|
||||
@@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Id = 1002,
|
||||
Username = "IAmOffline",
|
||||
LastVisit = DateTimeOffset.Now.AddDays(-10),
|
||||
IsOnline = false,
|
||||
WasRecentlyOnline = false,
|
||||
}, new OsuRuleset().RulesetInfo));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Scoring;
|
||||
@@ -12,7 +13,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public partial class TestSceneOverallRanking : OsuTestScene
|
||||
{
|
||||
private OverallRanking overallRanking = null!;
|
||||
private readonly Bindable<ScoreBasedUserStatisticsUpdate?> statisticsUpdate = new Bindable<ScoreBasedUserStatisticsUpdate?>();
|
||||
|
||||
[Test]
|
||||
public void TestUpdatePending()
|
||||
@@ -104,14 +105,19 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
displayUpdate(statistics, statistics);
|
||||
}
|
||||
|
||||
private void createDisplay() => AddStep("create display", () => Child = overallRanking = new OverallRanking
|
||||
private void createDisplay() => AddStep("create display", () =>
|
||||
{
|
||||
Width = 400,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
statisticsUpdate.Value = null;
|
||||
Child = new OverallRanking(new ScoreInfo())
|
||||
{
|
||||
Width = 400,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
DisplayedUpdate = { BindTarget = statisticsUpdate }
|
||||
};
|
||||
});
|
||||
|
||||
private void displayUpdate(UserStatistics before, UserStatistics after) =>
|
||||
AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new ScoreBasedUserStatisticsUpdate(new ScoreInfo(), before, after));
|
||||
AddStep("display update", () => statisticsUpdate.Value = new ScoreBasedUserStatisticsUpdate(new ScoreInfo(), before, after));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
: base(score)
|
||||
{
|
||||
AllowRetry = true;
|
||||
ShowUserStatistics = true;
|
||||
IsLocalPlay = true;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
||||
@@ -11,6 +11,7 @@ using NUnit.Framework;
|
||||
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.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -18,6 +19,9 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -36,6 +40,8 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public partial class TestSceneStatisticsPanel : OsuTestScene
|
||||
{
|
||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||
|
||||
[Test]
|
||||
public void TestScoreWithPositionStatistics()
|
||||
{
|
||||
@@ -137,62 +143,114 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
CachedDependencies = [(typeof(UserStatisticsWatcher), userStatisticsWatcher)],
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new UserStatisticsPanel(score)
|
||||
Child = new StatisticsPanel
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
Score = { Value = score, }
|
||||
Score = { Value = score, },
|
||||
AchievedScore = score,
|
||||
}
|
||||
});
|
||||
AddUntilStep("overall ranking present", () => this.ChildrenOfType<OverallRanking>().Any());
|
||||
AddUntilStep("loading spinner not visible", () => this.ChildrenOfType<LoadingLayer>().All(l => l.State.Value == Visibility.Hidden));
|
||||
AddUntilStep("loading spinner not visible",
|
||||
() => this.ChildrenOfType<OverallRanking>().Single()
|
||||
.ChildrenOfType<LoadingLayer>().All(l => l.State.Value == Visibility.Hidden));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTagging()
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
|
||||
AddStep("set up network requests", () =>
|
||||
{
|
||||
dummyAPI.HandleRequest = request =>
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case ListTagsRequest listTagsRequest:
|
||||
{
|
||||
Scheduler.AddDelayed(() => listTagsRequest.TriggerSuccess(new APITagCollection
|
||||
{
|
||||
Tags =
|
||||
[
|
||||
new APITag { Id = 1, Name = "tech", Description = "Tests uncommon skills.", },
|
||||
new APITag { Id = 2, Name = "alt", Description = "Colloquial term for maps which use rhythms that encourage the player to alternate notes. Typically distinct from burst or stream maps.", },
|
||||
new APITag { Id = 3, Name = "aim", Description = "Category for difficulty relating to cursor movement.", },
|
||||
new APITag { Id = 4, Name = "tap", Description = "Category for difficulty relating to tapping input.", },
|
||||
]
|
||||
}), 500);
|
||||
return true;
|
||||
}
|
||||
|
||||
case GetBeatmapSetRequest getBeatmapSetRequest:
|
||||
{
|
||||
var beatmapSet = CreateAPIBeatmapSet(score.BeatmapInfo);
|
||||
beatmapSet.Beatmaps.Single().TopTags =
|
||||
[
|
||||
new APIBeatmapTag { TagId = 3, VoteCount = 9 },
|
||||
];
|
||||
Scheduler.AddDelayed(() => getBeatmapSetRequest.TriggerSuccess(beatmapSet), 500);
|
||||
return true;
|
||||
}
|
||||
|
||||
case AddBeatmapTagRequest:
|
||||
case RemoveBeatmapTagRequest:
|
||||
{
|
||||
Scheduler.AddDelayed(request.TriggerSuccess, 500);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
});
|
||||
AddStep("load panel", () =>
|
||||
{
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new StatisticsPanel
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
Score = { Value = score },
|
||||
AchievedScore = score,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTaggingWhenRankTooLow()
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.Rank = ScoreRank.D;
|
||||
|
||||
AddStep("load panel", () =>
|
||||
{
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new StatisticsPanel
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
Score = { Value = score },
|
||||
AchievedScore = score,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private void loadPanel(ScoreInfo score) => AddStep("load panel", () =>
|
||||
{
|
||||
Child = new UserStatisticsPanel(score)
|
||||
Child = new StatisticsPanel
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
Score = { Value = score },
|
||||
DisplayedUserStatisticsUpdate =
|
||||
{
|
||||
Value = new ScoreBasedUserStatisticsUpdate(score, new UserStatistics
|
||||
{
|
||||
Level = new UserStatistics.LevelInfo
|
||||
{
|
||||
Current = 5,
|
||||
Progress = 20,
|
||||
},
|
||||
GlobalRank = 38000,
|
||||
CountryRank = 12006,
|
||||
PP = 2134,
|
||||
RankedScore = 21123849,
|
||||
Accuracy = 0.985,
|
||||
PlayCount = 13375,
|
||||
PlayTime = 354490,
|
||||
TotalScore = 128749597,
|
||||
TotalHits = 0,
|
||||
MaxCombo = 1233,
|
||||
}, new UserStatistics
|
||||
{
|
||||
Level = new UserStatistics.LevelInfo
|
||||
{
|
||||
Current = 5,
|
||||
Progress = 30,
|
||||
},
|
||||
GlobalRank = 36000,
|
||||
CountryRank = 12000,
|
||||
PP = (decimal)2134.5,
|
||||
RankedScore = 23897015,
|
||||
Accuracy = 0.984,
|
||||
PlayCount = 13376,
|
||||
PlayTime = 35789,
|
||||
TotalScore = 132218497,
|
||||
TotalHits = 0,
|
||||
MaxCombo = 1233,
|
||||
})
|
||||
}
|
||||
AchievedScore = score,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new UserTagControl
|
||||
Child = new UserTagControl(Beatmap.Value.BeatmapInfo)
|
||||
{
|
||||
Width = 500,
|
||||
Anchor = Anchor.Centre,
|
||||
|
||||
@@ -993,7 +993,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddAssert("column not scrolled", () => modSelectOverlay.ChildrenOfType<ModSelectOverlay.ColumnScrollContainer>().Single().IsScrolledToStart());
|
||||
|
||||
AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.Zero));
|
||||
AddAssert("customisation panel closed",
|
||||
AddUntilStep("customisation panel closed",
|
||||
() => this.ChildrenOfType<ModCustomisationPanel>().Single().ExpandedState.Value,
|
||||
() => Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
|
||||
|
||||
@@ -1018,7 +1018,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private void assertCustomisationToggleState(bool disabled, bool active)
|
||||
{
|
||||
AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single().Enabled.Value == !disabled);
|
||||
AddAssert($"customisation panel is {(active ? "" : "not ")}active",
|
||||
AddUntilStep($"customisation panel is {(active ? "" : "not ")}active",
|
||||
() => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single().ExpandedState.Value,
|
||||
() => active ? Is.Not.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed) : Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online;
|
||||
|
||||
namespace osu.Game.Audio
|
||||
{
|
||||
@@ -30,7 +30,7 @@ namespace osu.Game.Audio
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audioManager)
|
||||
{
|
||||
trackStore = audioManager.GetTrackStore(new OnlineStore());
|
||||
trackStore = audioManager.GetTrackStore(new TrustedDomainOnlineStore());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Linq;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.IO.Serialization.Converters;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
@@ -141,6 +142,8 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public int[] Bookmarks { get; set; } = Array.Empty<int>();
|
||||
|
||||
public int BeatmapVersion { get; set; } = LegacyBeatmapEncoder.FIRST_LAZER_VERSION;
|
||||
|
||||
IBeatmap IBeatmap.Clone() => Clone();
|
||||
|
||||
public Beatmap<T> Clone() => (Beatmap<T>)MemberwiseClone();
|
||||
|
||||
@@ -86,6 +86,7 @@ namespace osu.Game.Beatmaps
|
||||
beatmap.Countdown = original.Countdown;
|
||||
beatmap.CountdownOffset = original.CountdownOffset;
|
||||
beatmap.Bookmarks = original.Bookmarks;
|
||||
beatmap.BeatmapVersion = original.BeatmapVersion;
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
@@ -125,9 +125,10 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// Reset any fetched online linking information (and history).
|
||||
/// </summary>
|
||||
public void ResetOnlineInfo()
|
||||
public void ResetOnlineInfo(bool resetOnlineId = true)
|
||||
{
|
||||
OnlineID = -1;
|
||||
if (resetOnlineId)
|
||||
OnlineID = -1;
|
||||
LastOnlineUpdate = null;
|
||||
OnlineMD5Hash = string.Empty;
|
||||
if (Status != BeatmapOnlineStatus.LocallyModified)
|
||||
@@ -231,8 +232,6 @@ namespace osu.Game.Beatmaps
|
||||
[Obsolete("Use ScoreManager.GetMaximumAchievableComboAsync instead.")]
|
||||
public int? MaxCombo { get; set; }
|
||||
|
||||
public int BeatmapVersion;
|
||||
|
||||
public BeatmapInfo Clone() => (BeatmapInfo)this.Detach().MemberwiseClone();
|
||||
|
||||
public override string ToString() => this.GetDisplayTitle();
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
protected override void ParseStreamInto(LineBufferedReader stream, Beatmap beatmap)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion;
|
||||
this.beatmap.BeatmapVersion = FormatVersion;
|
||||
parser = new ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
||||
|
||||
ApplyLegacyDefaults(this.beatmap);
|
||||
@@ -193,6 +193,10 @@ namespace osu.Game.Beatmaps.Formats
|
||||
internal static void ApplyLegacyDefaults(Beatmap beatmap)
|
||||
{
|
||||
beatmap.WidescreenStoryboard = false;
|
||||
// in a perfect world this would throw if osu! ruleset couldn't be found,
|
||||
// but unfortunately there are "legitimate" cases where it's not there (i.e. ruleset test projects),
|
||||
// so attempt to trudge on with whatever it is that's in `BeatmapInfo` if the lookup fails.
|
||||
beatmap.BeatmapInfo.Ruleset = RulesetStore?.GetRuleset(0) ?? beatmap.BeatmapInfo.Ruleset;
|
||||
}
|
||||
|
||||
protected override void ParseLine(Beatmap beatmap, Section section, string line)
|
||||
|
||||
@@ -109,6 +109,8 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
int[] Bookmarks { get; internal set; }
|
||||
|
||||
int BeatmapVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a shallow-clone of this beatmap and returns it.
|
||||
/// </summary>
|
||||
|
||||
@@ -97,8 +97,9 @@ namespace osu.Game.Database
|
||||
/// 45 2024-12-23 Change beat snap divisor adjust defaults to be Ctrl+Scroll instead of Ctrl+Shift+Scroll, if not already changed by user.
|
||||
/// 46 2024-12-26 Change beat snap divisor bindings to match stable directionality ¯\_(ツ)_/¯.
|
||||
/// 47 2025-01-21 Remove right mouse button binding for absolute scroll. Never use mouse buttons (or scroll) for global actions.
|
||||
/// 48 2025-03-19 Clear online status for all qualified beatmaps (some were stuck in a qualified state due to local caching issues).
|
||||
/// </summary>
|
||||
private const int schema_version = 47;
|
||||
private const int schema_version = 48;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||
@@ -1245,6 +1246,15 @@ namespace osu.Game.Database
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 48:
|
||||
const int qualified = (int)BeatmapOnlineStatus.Qualified;
|
||||
|
||||
var beatmaps = migration.NewRealm.All<BeatmapInfo>().Where(b => b.StatusInt == qualified);
|
||||
|
||||
foreach (var beatmap in beatmaps)
|
||||
beatmap.ResetOnlineInfo(resetOnlineId: false);
|
||||
break;
|
||||
}
|
||||
|
||||
Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms");
|
||||
|
||||
@@ -17,5 +17,8 @@ namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
TooltipText = linkInline.Title;
|
||||
}
|
||||
|
||||
protected override ImageContainer CreateImageContainer(string url)
|
||||
=> base.CreateImageContainer($@"https://osu.ppy.sh/media-url?url={url}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
private partial class ArbitraryDrawableWrapper : Container, IHasLineBaseHeight
|
||||
{
|
||||
public float LineBaseHeight => DrawHeight;
|
||||
public float LineBaseHeight => (Child as IHasLineBaseHeight)?.LineBaseHeight ?? DrawHeight;
|
||||
|
||||
public ArbitraryDrawableWrapper()
|
||||
{
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
if (Link == null) return;
|
||||
|
||||
game?.CopyUrlToClipboard(Link);
|
||||
game?.CopyToClipboard(Link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,13 @@ namespace osu.Game.Localisation.SkinComponents
|
||||
/// <summary>
|
||||
/// "Whether to show extended information for each mod."
|
||||
/// </summary>
|
||||
public static LocalisableString ShowExtendedInformationDescription => new TranslatableString(getKey(@"whether_to_show_extended_information"), @"Whether to show extended information for each mod.");
|
||||
public static LocalisableString ShowExtendedInformationDescription =>
|
||||
new TranslatableString(getKey(@"whether_to_show_extended_information"), @"Whether to show extended information for each mod.");
|
||||
|
||||
/// <summary>
|
||||
/// "Display direction"
|
||||
/// </summary>
|
||||
public static LocalisableString DisplayDirection => new TranslatableString(getKey(@"display_direction"), "Display direction");
|
||||
|
||||
/// <summary>
|
||||
/// "Expansion mode"
|
||||
|
||||
@@ -45,9 +45,9 @@ namespace osu.Game.Localisation
|
||||
public static LocalisableString SkinSaved => new TranslatableString(getKey(@"skin_saved"), @"Skin saved");
|
||||
|
||||
/// <summary>
|
||||
/// "Link copied to clipboard"
|
||||
/// "Copied to clipboard"
|
||||
/// </summary>
|
||||
public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"Link copied to clipboard");
|
||||
public static LocalisableString CopiedToClipboard => new TranslatableString(getKey(@"copied_to_clipboard"), @"Copied to clipboard");
|
||||
|
||||
/// <summary>
|
||||
/// "Speed changed to {0:N2}x"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using osu.Framework.IO.Network;
|
||||
|
||||
@@ -21,11 +20,10 @@ namespace osu.Game.Online.API.Requests
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var req = base.CreateWebRequest();
|
||||
req.Method = HttpMethod.Post;
|
||||
req.AddParameter(@"tag_id", TagID.ToString(CultureInfo.InvariantCulture), RequestParameterType.Query);
|
||||
req.Method = HttpMethod.Put;
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => $@"beatmaps/{BeatmapID}/tags";
|
||||
protected override string Target => $@"beatmaps/{BeatmapID}/tags/{TagID}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
|
||||
public double BPM { get; set; }
|
||||
|
||||
[JsonProperty(@"owners")]
|
||||
public BeatmapOwner[] BeatmapOwners { get; set; } = Array.Empty<BeatmapOwner>();
|
||||
|
||||
#region Implementation of IBeatmapInfo
|
||||
|
||||
public IBeatmapMetadataInfo Metadata => (BeatmapSet as IBeatmapSetInfo)?.Metadata ?? new BeatmapMetadata();
|
||||
@@ -177,5 +180,14 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
// ReSharper disable once NonReadonlyMemberInGetHashCode
|
||||
public override int GetHashCode() => OnlineID;
|
||||
}
|
||||
|
||||
public class BeatmapOwner
|
||||
{
|
||||
[JsonProperty(@"id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty(@"username")]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API.Requests.Responses
|
||||
@@ -111,8 +112,13 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"is_active")]
|
||||
public bool Active;
|
||||
|
||||
/// <summary>
|
||||
/// From osu-web's perspective, whether a user was recently online.
|
||||
/// This doesn't imply the user is online in a lazer client (may be updated from stable or web browser).
|
||||
/// Use <see cref="MetadataClient.GetPresence"/> for real-time lazer online status checks.
|
||||
/// </summary>
|
||||
[JsonProperty(@"is_online")]
|
||||
public bool IsOnline;
|
||||
public bool WasRecentlyOnline;
|
||||
|
||||
[JsonProperty(@"pm_friends_only")]
|
||||
public bool PMFriendsOnly;
|
||||
|
||||
@@ -57,6 +57,9 @@ namespace osu.Game.Online.Metadata
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the presence of a user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will return data if the client is currently receiving presence data. See <see cref="BeginWatchingUserPresence"/>.
|
||||
/// </remarks>
|
||||
/// <param name="userId">The user ID.</param>
|
||||
/// <returns>The user presence, or null if not available or the user's offline.</returns>
|
||||
public UserPresence? GetPresence(int userId)
|
||||
|
||||
@@ -47,11 +47,6 @@ namespace osu.Game.Online.Spectator
|
||||
/// </summary>
|
||||
public IBindableList<SpectatorUser> WatchingUsers => watchingUsers;
|
||||
|
||||
/// <summary>
|
||||
/// A global list of all players currently playing.
|
||||
/// </summary>
|
||||
public IBindableList<int> PlayingUsers => playingUsers;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the local user is playing.
|
||||
/// </summary>
|
||||
@@ -91,7 +86,6 @@ namespace osu.Game.Online.Spectator
|
||||
private readonly BindableDictionary<int, SpectatorState> watchedUserStates = new BindableDictionary<int, SpectatorState>();
|
||||
|
||||
private readonly BindableList<SpectatorUser> watchingUsers = new BindableList<SpectatorUser>();
|
||||
private readonly BindableList<int> playingUsers = new BindableList<int>();
|
||||
private readonly SpectatorState currentState = new SpectatorState();
|
||||
|
||||
private IBeatmap? currentBeatmap;
|
||||
@@ -134,7 +128,6 @@ namespace osu.Game.Online.Spectator
|
||||
}
|
||||
else
|
||||
{
|
||||
playingUsers.Clear();
|
||||
watchedUserStates.Clear();
|
||||
watchingUsers.Clear();
|
||||
}
|
||||
@@ -145,9 +138,6 @@ namespace osu.Game.Online.Spectator
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (!playingUsers.Contains(userId))
|
||||
playingUsers.Add(userId);
|
||||
|
||||
if (watchedUsersRefCounts.ContainsKey(userId))
|
||||
watchedUserStates[userId] = state;
|
||||
|
||||
@@ -161,8 +151,6 @@ namespace osu.Game.Online.Spectator
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
playingUsers.Remove(userId);
|
||||
|
||||
if (watchedUsersRefCounts.ContainsKey(userId))
|
||||
watchedUserStates[userId] = state;
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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.IO.Stores;
|
||||
using osu.Framework.Logging;
|
||||
|
||||
namespace osu.Game.Online
|
||||
{
|
||||
public sealed class TrustedDomainOnlineStore : OnlineStore
|
||||
{
|
||||
protected override string GetLookupUrl(string url)
|
||||
{
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) || !uri.Host.EndsWith(@".ppy.sh", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.Log($@"Blocking resource lookup from external website: {url}", LoggingTarget.Network, LogLevel.Important);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -519,10 +519,10 @@ namespace osu.Game
|
||||
}
|
||||
});
|
||||
|
||||
public void CopyUrlToClipboard(string url) => waitForReady(() => onScreenDisplay, _ =>
|
||||
public void CopyToClipboard(string value) => waitForReady(() => onScreenDisplay, _ =>
|
||||
{
|
||||
dependencies.Get<Clipboard>().SetText(url);
|
||||
onScreenDisplay.Display(new CopyUrlToast());
|
||||
dependencies.Get<Clipboard>().SetText(value);
|
||||
onScreenDisplay.Display(new CopiedToClipboardToast());
|
||||
});
|
||||
|
||||
public void OpenUrlExternally(string url, LinkWarnMode warnMode = LinkWarnMode.Default) => waitForReady(() => externalLinkOpener, _ => externalLinkOpener.OpenUrlExternally(url, warnMode));
|
||||
|
||||
@@ -108,6 +108,8 @@ namespace osu.Game
|
||||
public virtual EndpointConfiguration CreateEndpoints() =>
|
||||
UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();
|
||||
|
||||
protected override OnlineStore CreateOnlineStore() => new TrustedDomainOnlineStore();
|
||||
|
||||
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();
|
||||
|
||||
/// <summary>
|
||||
@@ -278,7 +280,7 @@ namespace osu.Game
|
||||
dependencies.CacheAs(Storage);
|
||||
|
||||
var largeStore = new LargeTextureStore(Host.Renderer, Host.CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures")));
|
||||
largeStore.AddTextureSource(Host.CreateTextureLoaderStore(new OnlineStore()));
|
||||
largeStore.AddTextureSource(Host.CreateTextureLoaderStore(CreateOnlineStore()));
|
||||
dependencies.Cache(largeStore);
|
||||
|
||||
dependencies.CacheAs(LocalConfig);
|
||||
|
||||
@@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Extensions;
|
||||
@@ -31,9 +32,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
private const float tile_icon_padding = 7;
|
||||
private const float tile_spacing = 2;
|
||||
|
||||
private readonly OsuSpriteText version, starRating, starRatingText;
|
||||
private readonly LinkFlowContainer guestMapperContainer;
|
||||
private readonly FillFlowContainer starRatingContainer;
|
||||
private readonly LinkFlowContainer infoContainer;
|
||||
private readonly Statistic plays, favourites;
|
||||
|
||||
public readonly DifficultiesContainer Difficulties;
|
||||
@@ -53,6 +52,9 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public BeatmapPicker()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
@@ -72,59 +74,13 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Left = -(tile_icon_padding + tile_spacing / 2), Bottom = 10 },
|
||||
OnLostHover = () =>
|
||||
{
|
||||
showBeatmap(Beatmap.Value);
|
||||
starRatingContainer.FadeOut(100);
|
||||
},
|
||||
OnLostHover = () => showBeatmap(Beatmap.Value, withStarRating: false),
|
||||
},
|
||||
new FillFlowContainer
|
||||
infoContainer = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 11))
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
version = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold)
|
||||
},
|
||||
guestMapperContainer = new LinkFlowContainer(s =>
|
||||
s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 11))
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Bottom = 1 },
|
||||
},
|
||||
starRatingContainer = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Alpha = 0,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(2f, 0),
|
||||
Margin = new MarginPadding { Bottom = 1 },
|
||||
Children = new[]
|
||||
{
|
||||
starRatingText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold),
|
||||
Text = BeatmapsetsStrings.ShowStatsStars,
|
||||
},
|
||||
starRating = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold),
|
||||
Text = string.Empty,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
TextAnchor = Anchor.BottomLeft,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
@@ -144,7 +100,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
|
||||
Beatmap.ValueChanged += b =>
|
||||
{
|
||||
showBeatmap(b.NewValue);
|
||||
showBeatmap(b.NewValue, withStarRating: Difficulties.Any(d => d.IsHovered));
|
||||
updateDifficultyButtons();
|
||||
};
|
||||
}
|
||||
@@ -153,10 +109,8 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
private void load()
|
||||
{
|
||||
starRating.Colour = colours.Yellow;
|
||||
starRatingText.Colour = colours.Yellow;
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
@@ -185,16 +139,12 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
State = DifficultySelectorState.NotSelected,
|
||||
OnHovered = beatmap =>
|
||||
{
|
||||
showBeatmap(beatmap);
|
||||
starRating.Text = beatmap.StarRating.FormatStarRating();
|
||||
starRatingContainer.FadeIn(100);
|
||||
showBeatmap(beatmap, withStarRating: true);
|
||||
},
|
||||
OnClicked = beatmap => { Beatmap.Value = beatmap; },
|
||||
});
|
||||
}
|
||||
|
||||
starRatingContainer.FadeOut(100);
|
||||
|
||||
// If a selection is already made, try and maintain it.
|
||||
if (Beatmap.Value != null)
|
||||
Beatmap.Value = Difficulties.FirstOrDefault(b => b.Beatmap.OnlineID == Beatmap.Value.OnlineID)?.Beatmap;
|
||||
@@ -208,22 +158,68 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
updateDifficultyButtons();
|
||||
}
|
||||
|
||||
private void showBeatmap(APIBeatmap? beatmapInfo)
|
||||
private void showBeatmap(APIBeatmap? beatmapInfo, bool withStarRating)
|
||||
{
|
||||
guestMapperContainer.Clear();
|
||||
infoContainer.Clear();
|
||||
|
||||
if (beatmapInfo?.AuthorID != BeatmapSet?.AuthorID)
|
||||
infoContainer.AddText(beatmapInfo?.DifficultyName ?? string.Empty, s => s.Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold));
|
||||
infoContainer.AddArbitraryDrawable(Empty().With(e => e.Width = 5));
|
||||
|
||||
var beatmapOwners = beatmapInfo?.BeatmapOwners;
|
||||
bool isHostDifficulty = beatmapOwners?.Length == 1 && beatmapOwners.First().Id == beatmapSet?.AuthorID;
|
||||
|
||||
if (beatmapOwners != null && !isHostDifficulty)
|
||||
{
|
||||
APIUser? user = BeatmapSet?.RelatedUsers?.SingleOrDefault(u => u.OnlineID == beatmapInfo?.AuthorID);
|
||||
APIUser[] users = BeatmapSet?.RelatedUsers?.Where(u => beatmapOwners.Any(o => o.Id == u.OnlineID)).ToArray() ?? [];
|
||||
int count = users.Length;
|
||||
|
||||
if (user != null)
|
||||
switch (count)
|
||||
{
|
||||
guestMapperContainer.AddText("mapped by ");
|
||||
guestMapperContainer.AddUserLink(user);
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case 1:
|
||||
infoContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty));
|
||||
infoContainer.AddUserLink(users[0]);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
infoContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty));
|
||||
infoContainer.AddUserLink(users[0]);
|
||||
infoContainer.AddText(CommonStrings.ArrayAndTwoWordsConnector);
|
||||
infoContainer.AddUserLink(users[1]);
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
infoContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty));
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
infoContainer.AddUserLink(users[i]);
|
||||
|
||||
if (i < count - 2)
|
||||
infoContainer.AddText(CommonStrings.ArrayAndWordsConnector);
|
||||
else if (i == count - 2)
|
||||
infoContainer.AddText(CommonStrings.ArrayAndLastWordConnector);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
version.Text = beatmapInfo?.DifficultyName ?? string.Empty;
|
||||
if (withStarRating)
|
||||
{
|
||||
infoContainer.AddArbitraryDrawable(Empty().With(e => e.Width = 5));
|
||||
infoContainer.AddText(
|
||||
LocalisableString.Interpolate($"{BeatmapsetsStrings.ShowStatsStars} {beatmapInfo?.StarRating.FormatStarRating()}"),
|
||||
t =>
|
||||
{
|
||||
t.Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold);
|
||||
t.Colour = colours.Yellow;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDifficultyButtons()
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
Vertical = BeatmapSetOverlay.Y_PADDING,
|
||||
Left = WaveOverlayContainer.HORIZONTAL_PADDING,
|
||||
Right = WaveOverlayContainer.HORIZONTAL_PADDING + BeatmapSetOverlay.RIGHT_WIDTH,
|
||||
Right = WaveOverlayContainer.HORIZONTAL_PADDING + BeatmapSetOverlay.RIGHT_WIDTH + 10,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@@ -14,6 +15,7 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@@ -22,7 +24,10 @@ using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Play;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using ChatStrings = osu.Game.Localisation.ChatStrings;
|
||||
@@ -69,6 +74,12 @@ namespace osu.Game.Overlays.Chat
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerClient? multiplayerClient { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IPerformFromScreenRunner? performer { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private ChannelManager? chatManager { get; set; }
|
||||
|
||||
@@ -161,13 +172,10 @@ namespace osu.Game.Overlays.Chat
|
||||
if (user.Equals(APIUser.SYSTEM_USER))
|
||||
return Array.Empty<MenuItem>();
|
||||
|
||||
List<MenuItem> items = new List<MenuItem>
|
||||
{
|
||||
new OsuMenuItem(ContextMenuStrings.ViewProfile, MenuItemType.Highlighted, openUserProfile)
|
||||
};
|
||||
if (user.Equals(api.LocalUser.Value))
|
||||
return Array.Empty<MenuItem>();
|
||||
|
||||
if (!user.Equals(api.LocalUser.Value))
|
||||
items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel));
|
||||
List<MenuItem> items = new List<MenuItem>();
|
||||
|
||||
if (currentChannel?.Value != null)
|
||||
{
|
||||
@@ -177,8 +185,29 @@ namespace osu.Game.Overlays.Chat
|
||||
}));
|
||||
}
|
||||
|
||||
if (!user.Equals(api.LocalUser.Value))
|
||||
items.Add(new OsuMenuItem(UsersStrings.ReportButtonText, MenuItemType.Destructive, ReportRequested));
|
||||
items.Add(new OsuMenuItem(ContextMenuStrings.ViewProfile, MenuItemType.Highlighted, openUserProfile));
|
||||
|
||||
items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel));
|
||||
|
||||
// We should probably be checking against an online state here.
|
||||
// But we can't use MetadataClient.GetPresence because we may not be requesting/receiving presences.
|
||||
// This isn't really too bad – worst case scenario the client will open spectator view and show the user as "offline".
|
||||
{
|
||||
items.Add(new OsuMenuItemSpacer());
|
||||
|
||||
items.Add(new OsuMenuItem(ContextMenuStrings.SpectatePlayer, MenuItemType.Standard, () =>
|
||||
{
|
||||
performer?.PerformFromScreen(s => s.Push(new SoloSpectatorScreen(user)));
|
||||
}));
|
||||
|
||||
if (multiplayerClient?.Room?.Users.All(u => u.UserID != user.Id) == true)
|
||||
{
|
||||
items.Add(new OsuMenuItem(ContextMenuStrings.InvitePlayer, MenuItemType.Standard, () => multiplayerClient.InvitePlayer(user.Id)));
|
||||
}
|
||||
}
|
||||
|
||||
items.Add(new OsuMenuItemSpacer());
|
||||
items.Add(new OsuMenuItem(UsersStrings.ReportButtonText, MenuItemType.Destructive, ReportRequested));
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@@ -19,7 +22,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Comments
|
||||
{
|
||||
public partial class CommentReportButton : CompositeDrawable, IHasPopover
|
||||
public partial class CommentReportButton : CompositeDrawable, IHasPopover, IHasLineBaseHeight
|
||||
{
|
||||
private readonly Comment comment;
|
||||
|
||||
@@ -88,5 +91,7 @@ namespace osu.Game.Overlays.Comments
|
||||
|
||||
api.Queue(request);
|
||||
}
|
||||
|
||||
public float LineBaseHeight => link.ChildrenOfType<IHasLineBaseHeight>().FirstOrDefault()?.LineBaseHeight ?? DrawHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,7 +420,7 @@ namespace osu.Game.Overlays.Comments
|
||||
private void copyUrl()
|
||||
{
|
||||
clipboard.SetText($@"{api.Endpoints.APIUrl}/comments/{Comment.Id}");
|
||||
onScreenDisplay?.Display(new CopyUrlToast());
|
||||
onScreenDisplay?.Display(new CopiedToClipboardToast());
|
||||
}
|
||||
|
||||
private void toggleReply()
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
@@ -16,10 +14,8 @@ using osu.Framework.Localisation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
@@ -34,19 +30,12 @@ namespace osu.Game.Overlays.Dashboard
|
||||
private const float search_textbox_height = 40;
|
||||
private const float padding = 10;
|
||||
|
||||
private readonly IBindableList<int> playingUsers = new BindableList<int>();
|
||||
private readonly IBindableDictionary<int, UserPresence> onlineUserPresences = new BindableDictionary<int, UserPresence>();
|
||||
private readonly Dictionary<int, OnlineUserPanel> userPanels = new Dictionary<int, OnlineUserPanel>();
|
||||
|
||||
private SearchContainer<OnlineUserPanel> userFlow = null!;
|
||||
private BasicSearchTextBox searchTextBox = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private SpectatorClient spectatorClient { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private MetadataClient metadataClient { get; set; } = null!;
|
||||
|
||||
@@ -106,9 +95,6 @@ namespace osu.Game.Overlays.Dashboard
|
||||
|
||||
onlineUserPresences.BindTo(metadataClient.UserPresences);
|
||||
onlineUserPresences.BindCollectionChanged(onUserPresenceUpdated, true);
|
||||
|
||||
playingUsers.BindTo(spectatorClient.PlayingUsers);
|
||||
playingUsers.BindCollectionChanged(onPlayingUsersChanged, true);
|
||||
}
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
@@ -152,53 +138,27 @@ namespace osu.Game.Overlays.Dashboard
|
||||
}
|
||||
});
|
||||
|
||||
private void onPlayingUsersChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
Debug.Assert(e.NewItems != null);
|
||||
|
||||
foreach (int userId in e.NewItems)
|
||||
{
|
||||
if (userPanels.TryGetValue(userId, out var panel))
|
||||
panel.CanSpectate.Value = userId != api.LocalUser.Value.Id;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
Debug.Assert(e.OldItems != null);
|
||||
|
||||
foreach (int userId in e.OldItems)
|
||||
{
|
||||
if (userPanels.TryGetValue(userId, out var panel))
|
||||
panel.CanSpectate.Value = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private OnlineUserPanel createUserPanel(APIUser user) =>
|
||||
new OnlineUserPanel(user).With(panel =>
|
||||
{
|
||||
panel.Anchor = Anchor.TopCentre;
|
||||
panel.Origin = Anchor.TopCentre;
|
||||
panel.CanSpectate.Value = playingUsers.Contains(user.Id);
|
||||
});
|
||||
|
||||
public partial class OnlineUserPanel : CompositeDrawable, IFilterable
|
||||
{
|
||||
public readonly APIUser User;
|
||||
|
||||
public BindableBool CanSpectate { get; } = new BindableBool();
|
||||
private PurpleRoundedButton spectateButton = null!;
|
||||
|
||||
public IEnumerable<LocalisableString> FilterTerms { get; }
|
||||
|
||||
[Resolved]
|
||||
private IPerformFromScreenRunner? performer { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private MetadataClient? metadataClient { get; set; }
|
||||
|
||||
public bool FilteringActive { set; get; }
|
||||
|
||||
public bool MatchingFilter
|
||||
@@ -221,6 +181,27 @@ namespace osu.Game.Overlays.Dashboard
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// TODO: we probably don't want to do this every frame.
|
||||
var activity = metadataClient?.GetPresence(User.Id)?.Activity;
|
||||
|
||||
switch (activity)
|
||||
{
|
||||
default:
|
||||
spectateButton.Enabled.Value = false;
|
||||
break;
|
||||
|
||||
case UserActivity.InSoloGame:
|
||||
case UserActivity.InMultiplayerGame:
|
||||
case UserActivity.InPlaylistGame:
|
||||
spectateButton.Enabled.Value = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@@ -240,14 +221,13 @@ namespace osu.Game.Overlays.Dashboard
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre
|
||||
},
|
||||
new PurpleRoundedButton
|
||||
spectateButton = new PurpleRoundedButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Text = "Spectate",
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Action = () => performer?.PerformFromScreen(s => s.Push(new SoloSpectatorScreen(User))),
|
||||
Enabled = { BindTarget = CanSpectate }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -107,12 +107,7 @@ namespace osu.Game.Overlays.MedalSplash
|
||||
},
|
||||
};
|
||||
|
||||
description.AddText(medal.Description, s =>
|
||||
{
|
||||
s.Anchor = Anchor.TopCentre;
|
||||
s.Origin = Anchor.TopCentre;
|
||||
s.Font = s.Font.With(size: 16);
|
||||
});
|
||||
description.AddText(medal.Description, s => s.Font = s.Font.With(size: 16));
|
||||
|
||||
medalContainer.OnLoadComplete += _ =>
|
||||
{
|
||||
|
||||
@@ -177,15 +177,18 @@ namespace osu.Game.Overlays.Mods
|
||||
bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate);
|
||||
|
||||
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty);
|
||||
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(originalDifficulty);
|
||||
|
||||
foreach (var mod in Mods.Value.OfType<IApplicableToDifficulty>())
|
||||
mod.ApplyToDifficulty(originalDifficulty);
|
||||
mod.ApplyToDifficulty(adjustedDifficulty);
|
||||
|
||||
Ruleset ruleset = GameRuleset.Value.CreateInstance();
|
||||
BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate);
|
||||
adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(adjustedDifficulty, rate);
|
||||
|
||||
TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty);
|
||||
|
||||
circleSizeDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.CircleSize, adjustedDifficulty.CircleSize);
|
||||
drainRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.DrainRate, adjustedDifficulty.DrainRate);
|
||||
approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate);
|
||||
overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty);
|
||||
|
||||
|
||||
@@ -223,15 +223,28 @@ namespace osu.Game.Overlays.Mods
|
||||
inputManager = GetContainingInputManager()!;
|
||||
}
|
||||
|
||||
private double timeUntilCollapse;
|
||||
|
||||
private const double collapse_grace_time = 180;
|
||||
private const float collapse_grace_position = 40;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (ExpandedState.Value == ModCustomisationPanelState.Expanded
|
||||
&& !ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position)
|
||||
&& inputManager.DraggedDrawable == null)
|
||||
if (ExpandedState.Value == ModCustomisationPanelState.Expanded)
|
||||
{
|
||||
ExpandedState.Value = ModCustomisationPanelState.Collapsed;
|
||||
bool canCollapse = !DrawRectangle.Inflate(new Vector2(collapse_grace_position)).Contains(ToLocalSpace(inputManager.CurrentState.Mouse.Position))
|
||||
&& inputManager.DraggedDrawable == null;
|
||||
|
||||
if (canCollapse)
|
||||
{
|
||||
if (timeUntilCollapse <= 0)
|
||||
ExpandedState.Value = ModCustomisationPanelState.Collapsed;
|
||||
timeUntilCollapse -= Time.Elapsed;
|
||||
}
|
||||
else
|
||||
timeUntilCollapse = collapse_grace_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -5,10 +5,10 @@ using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Game.Overlays.OSD
|
||||
{
|
||||
public partial class CopyUrlToast : Toast
|
||||
public partial class CopiedToClipboardToast : Toast
|
||||
{
|
||||
public CopyUrlToast()
|
||||
: base(CommonStrings.General, ToastStrings.UrlCopied, "")
|
||||
public CopiedToClipboardToast()
|
||||
: base(CommonStrings.General, ToastStrings.CopiedToClipboard, "")
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
|
||||
addSpacer(topLinkContainer);
|
||||
|
||||
if (user.IsOnline)
|
||||
if (user.WasRecentlyOnline)
|
||||
{
|
||||
topLinkContainer.AddText(UsersStrings.ShowLastvisitOnline);
|
||||
addSpacer(topLinkContainer);
|
||||
|
||||
@@ -5,6 +5,8 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@@ -76,13 +78,16 @@ namespace osu.Game.Overlays.Settings
|
||||
}
|
||||
}
|
||||
|
||||
private partial class BuildDisplay : OsuAnimatedButton
|
||||
private partial class BuildDisplay : OsuAnimatedButton, IHasContextMenu
|
||||
{
|
||||
private readonly string version;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuGame? game { get; set; }
|
||||
|
||||
public BuildDisplay(string version)
|
||||
{
|
||||
this.version = version;
|
||||
@@ -108,6 +113,11 @@ namespace osu.Game.Overlays.Settings
|
||||
Colour = DebugUtils.IsDebugBuild ? colours.Red : Color4.White,
|
||||
});
|
||||
}
|
||||
|
||||
public MenuItem[] ContextMenuItems => new MenuItem[]
|
||||
{
|
||||
new OsuMenuItem("Copy version", MenuItemType.Standard, () => game?.CopyToClipboard(version))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.Settings.Sections;
|
||||
@@ -56,7 +57,13 @@ namespace osu.Game.Overlays
|
||||
private SettingsSubPanel lastOpenedSubPanel;
|
||||
|
||||
protected override Drawable CreateHeader() => new SettingsHeader(Title, Description);
|
||||
protected override Drawable CreateFooter() => new SettingsFooter();
|
||||
|
||||
protected override Drawable CreateFooter() => new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = new SettingsFooter()
|
||||
};
|
||||
|
||||
public SettingsOverlay()
|
||||
: base(false)
|
||||
|
||||
@@ -344,6 +344,7 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
public double TotalBreakTime => baseBeatmap.TotalBreakTime;
|
||||
public IEnumerable<BeatmapStatistic> GetStatistics() => baseBeatmap.GetStatistics();
|
||||
public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength();
|
||||
public int BeatmapVersion => baseBeatmap.BeatmapVersion;
|
||||
public IBeatmap Clone() => new ProgressiveCalculationBeatmap(baseBeatmap.Clone());
|
||||
|
||||
public double AudioLeadIn
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace osu.Game.Scoring.Legacy
|
||||
scoreInfo.BeatmapInfo = currentBeatmap.BeatmapInfo;
|
||||
|
||||
// As this is baked into hitobject timing (see `LegacyBeatmapDecoder`) we also need to apply this to replay frame timing.
|
||||
beatmapOffset = currentBeatmap.BeatmapInfo.BeatmapVersion < 5 ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0;
|
||||
beatmapOffset = currentBeatmap.BeatmapVersion < 5 ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0;
|
||||
|
||||
/* score.HpGraphString = */
|
||||
sr.ReadString();
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace osu.Game.Scoring.Legacy
|
||||
StringBuilder replayData = new StringBuilder();
|
||||
|
||||
// As this is baked into hitobject timing (see `LegacyBeatmapDecoder`) we also need to apply this to replay frame timing.
|
||||
double offset = beatmap?.BeatmapInfo.BeatmapVersion < 5 ? -LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0;
|
||||
double offset = beatmap?.BeatmapVersion < 5 ? -LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0;
|
||||
|
||||
int lastTime = 0;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@@ -133,6 +134,8 @@ namespace osu.Game.Screens.Edit
|
||||
BeatmapInfo.Metadata.PreviewTime = s.NewValue;
|
||||
EndChange();
|
||||
});
|
||||
|
||||
BeatmapVersion = PlayableBeatmap.BeatmapVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -286,6 +289,8 @@ namespace osu.Game.Screens.Edit
|
||||
set => PlayableBeatmap.Bookmarks = value;
|
||||
}
|
||||
|
||||
public int BeatmapVersion { get; set; }
|
||||
|
||||
public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone();
|
||||
|
||||
private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects;
|
||||
@@ -456,6 +461,10 @@ namespace osu.Game.Screens.Edit
|
||||
if (batchPendingUpdates.Count == 0 && batchPendingDeletes.Count == 0 && batchPendingInserts.Count == 0)
|
||||
return;
|
||||
|
||||
// if the user is doing edits to this beatmaps via this flow, we better bump the beatmap version
|
||||
// because the beatmap encoder can only output this specific beatmap version anyway,
|
||||
// so *not* bumping it could lead to results that look misleading at best.
|
||||
BeatmapVersion = LegacyBeatmapEncoder.FIRST_LAZER_VERSION;
|
||||
beatmapProcessor.PreProcess();
|
||||
|
||||
foreach (var h in batchPendingDeletes) processHitObject(h);
|
||||
|
||||
@@ -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.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
@@ -13,7 +14,9 @@ using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Screens.Edit.GameplayTest
|
||||
@@ -228,5 +231,7 @@ namespace osu.Game.Screens.Edit.GameplayTest
|
||||
editor.RestoreState(editorState);
|
||||
return base.OnExiting(e);
|
||||
}
|
||||
|
||||
protected override ResultsScreen CreateResults(ScoreInfo score) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Screens.Menu
|
||||
{
|
||||
@@ -20,7 +18,7 @@ namespace osu.Game.Screens.Menu
|
||||
[Resolved]
|
||||
private GameHost host { get; set; } = null!;
|
||||
|
||||
private SkinnableSound? sample;
|
||||
private StarFountainSounds sounds = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@@ -41,7 +39,7 @@ namespace osu.Game.Screens.Menu
|
||||
Origin = Anchor.BottomRight,
|
||||
X = -250,
|
||||
},
|
||||
sample = new SkinnableSound(new SampleInfo("Gameplay/fountain-shoot"))
|
||||
sounds = new StarFountainSounds()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,9 +81,9 @@ namespace osu.Game.Screens.Menu
|
||||
break;
|
||||
}
|
||||
|
||||
// Don't play SFX when game is in background as it can be a bit noisy.
|
||||
// Don't play SFX when game is in background, as it can be a bit noisy.
|
||||
if (host.IsActive.Value)
|
||||
sample?.Play();
|
||||
sounds.Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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.Threading;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Screens.Menu
|
||||
{
|
||||
public partial class StarFountainSounds : CompositeComponent
|
||||
{
|
||||
private const int shoot_retrigger_delay = 500;
|
||||
private const int loop_fade_duration = 500;
|
||||
|
||||
private double? lastPlayback;
|
||||
|
||||
private SkinnableSound shootSample = null!;
|
||||
private PausableSkinnableSound loopSample = null!;
|
||||
|
||||
private ScheduledDelegate? loopFadeDelegate;
|
||||
private ScheduledDelegate? loopStopDelegate;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
shootSample = new SkinnableSound(new SampleInfo("Gameplay/fountain-shoot")),
|
||||
loopSample = new PausableSkinnableSound(new SampleInfo("Gameplay/fountain-loop")) { Looping = true },
|
||||
};
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
loopFadeDelegate?.Cancel();
|
||||
loopStopDelegate?.Cancel();
|
||||
|
||||
try
|
||||
{
|
||||
// Only play 'shootSample' if enough time has passed since last `Play()` call.
|
||||
if (lastPlayback == null || Time.Current - lastPlayback > shoot_retrigger_delay)
|
||||
{
|
||||
loopSample.Stop();
|
||||
shootSample.Play();
|
||||
return;
|
||||
}
|
||||
|
||||
// Only call `Play()` if `loopSample` is not already playing, to prevent restarting the sample each time.
|
||||
if (!loopSample.RequestedPlaying)
|
||||
{
|
||||
this.TransformBindableTo(loopSample.Volume, 1);
|
||||
loopSample.Play();
|
||||
}
|
||||
|
||||
// Schedule a volume fadeout, followed by a `Stop()`.
|
||||
loopFadeDelegate = Scheduler.AddDelayed(() =>
|
||||
{
|
||||
this.TransformBindableTo(loopSample.Volume, 0, loop_fade_duration);
|
||||
loopStopDelegate = Scheduler.AddDelayed(() => loopSample.Stop(), loop_fade_duration);
|
||||
}, shoot_retrigger_delay);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lastPlayback = Time.Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,6 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
t.Padding = new MarginPadding { Left = 5, Top = 1 };
|
||||
t.Font = t.Font.With(size: font_size);
|
||||
t.Origin = Anchor.Centre;
|
||||
t.Colour = colours.Pink;
|
||||
|
||||
Schedule(() =>
|
||||
|
||||
@@ -355,7 +355,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
items.AddRange([
|
||||
new OsuMenuItem("View in browser", MenuItemType.Standard, () => game?.OpenUrlExternally(formatRoomUrl(Room.RoomID.Value))),
|
||||
new OsuMenuItem("Copy link", MenuItemType.Standard, () => game?.CopyUrlToClipboard(formatRoomUrl(Room.RoomID.Value)))
|
||||
new OsuMenuItem("Copy link", MenuItemType.Standard, () => game?.CopyToClipboard(formatRoomUrl(Room.RoomID.Value)))
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -351,7 +351,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
{
|
||||
joiningRoomOperation?.Dispose();
|
||||
joiningRoomOperation = null;
|
||||
onFailure?.Invoke(error);
|
||||
|
||||
if (onFailure != null)
|
||||
onFailure(error);
|
||||
else
|
||||
Logger.Log(error, level: LogLevel.Error);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -88,12 +88,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
if (exception?.GetHubExceptionMessage() is string message)
|
||||
onFailure(message);
|
||||
else
|
||||
{
|
||||
const string generic_failure_message = "Failed to join multiplayer room.";
|
||||
if (result.Exception != null)
|
||||
Logger.Error(result.Exception, generic_failure_message);
|
||||
onFailure(generic_failure_message);
|
||||
}
|
||||
onFailure($"Failed to join multiplayer room: {exception?.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -198,11 +198,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
return multiplayerLeaderboard.TeamScores.Count == 2
|
||||
? new MultiplayerTeamResultsScreen(score, Room.RoomID.Value, PlaylistItem, multiplayerLeaderboard.TeamScores)
|
||||
{
|
||||
ShowUserStatistics = true,
|
||||
IsLocalPlay = true,
|
||||
}
|
||||
: new MultiplayerResultsScreen(score, Room.RoomID.Value, PlaylistItem)
|
||||
{
|
||||
ShowUserStatistics = true
|
||||
IsLocalPlay = true,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
return new PlaylistItemScoreResultsScreen(score, Room.RoomID.Value, PlaylistItem)
|
||||
{
|
||||
AllowRetry = true,
|
||||
ShowUserStatistics = true,
|
||||
IsLocalPlay = true,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,12 @@ namespace osu.Game.Screens.Play.HUD
|
||||
}
|
||||
}
|
||||
|
||||
public FillDirection FillDirection
|
||||
{
|
||||
get => iconsContainer.Direction;
|
||||
set => iconsContainer.Direction = value;
|
||||
}
|
||||
|
||||
private readonly FillFlowContainer<ModIcon> iconsContainer;
|
||||
|
||||
public ModDisplay(bool showExtendedInformation = true)
|
||||
@@ -122,13 +128,13 @@ namespace osu.Game.Screens.Play.HUD
|
||||
private void expand(double duration = 500)
|
||||
{
|
||||
if (ExpansionMode != ExpansionMode.AlwaysContracted)
|
||||
iconsContainer.TransformSpacingTo(new Vector2(5, 0), duration, Easing.OutQuint);
|
||||
iconsContainer.TransformSpacingTo(new Vector2(5, -10), duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private void contract(double duration = 500)
|
||||
{
|
||||
if (ExpansionMode != ExpansionMode.AlwaysExpanded)
|
||||
iconsContainer.TransformSpacingTo(new Vector2(-25, 0), duration, Easing.OutQuint);
|
||||
iconsContainer.TransformSpacingTo(new Vector2(-25), duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
|
||||
@@ -30,6 +30,9 @@ namespace osu.Game.Screens.Play.HUD
|
||||
[SettingSource(typeof(SkinnableModDisplayStrings), nameof(SkinnableModDisplayStrings.ExpansionMode), nameof(SkinnableModDisplayStrings.ExpansionModeDescription))]
|
||||
public Bindable<ExpansionMode> ExpansionModeSetting { get; } = new Bindable<ExpansionMode>();
|
||||
|
||||
[SettingSource(typeof(SkinnableModDisplayStrings), nameof(SkinnableModDisplayStrings.DisplayDirection))]
|
||||
public Bindable<Direction> Direction { get; } = new Bindable<Direction>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@@ -50,6 +53,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
ShowExtendedInformation.BindValueChanged(_ => modDisplay.ShowExtendedInformation = ShowExtendedInformation.Value, true);
|
||||
ExpansionModeSetting.BindValueChanged(_ => modDisplay.ExpansionMode = ExpansionModeSetting.Value, true);
|
||||
Direction.BindValueChanged(_ => modDisplay.FillDirection = Direction.Value == Framework.Graphics.Direction.Horizontal ? FillDirection.Horizontal : FillDirection.Vertical, true);
|
||||
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
@@ -147,6 +147,9 @@ namespace osu.Game.Screens.Play
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
// This display is potentially a duplicate of users with a local ModDisplay in their skins.
|
||||
// It would be very nice to remove this, but the version here has special logic with regards to replays
|
||||
// and initial states, so needs a bit of thought before doing so.
|
||||
ModDisplay = CreateModsContainer(),
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,11 +6,9 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
@@ -21,7 +19,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private Bindable<bool> kiaiStarFountains = null!;
|
||||
|
||||
private SkinnableSound? sample;
|
||||
private StarFountainSounds sounds = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
@@ -44,7 +42,7 @@ namespace osu.Game.Screens.Play
|
||||
Origin = Anchor.BottomRight,
|
||||
X = -75,
|
||||
},
|
||||
sample = new SkinnableSound(new SampleInfo("Gameplay/fountain-shoot"))
|
||||
sounds = new StarFountainSounds(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -72,7 +70,7 @@ namespace osu.Game.Screens.Play
|
||||
leftFountain.Shoot(1);
|
||||
rightFountain.Shoot(-1);
|
||||
|
||||
sample?.Play();
|
||||
sounds.Play();
|
||||
}
|
||||
|
||||
public partial class GameplayStarFountain : StarFountain
|
||||
|
||||
@@ -5,9 +5,12 @@ using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Localisation;
|
||||
@@ -31,14 +34,29 @@ namespace osu.Game.Screens.Play
|
||||
OnResume?.Invoke();
|
||||
};
|
||||
|
||||
private readonly IBindable<bool> windowActive = new Bindable<bool>(true);
|
||||
|
||||
private float targetVolume => windowActive.Value && State.Value == Visibility.Visible ? 1.0f : 0;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(GameHost? host)
|
||||
{
|
||||
AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("Gameplay/pause-loop"))
|
||||
{
|
||||
Looping = true,
|
||||
Volume = { Value = 0 }
|
||||
});
|
||||
|
||||
if (host != null)
|
||||
windowActive.BindTo(host.IsActive);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
// Schedule required because host.IsActive doesn't seem to always run on the update thread.
|
||||
windowActive.BindValueChanged(_ => Schedule(() => pauseLoop.VolumeTo(targetVolume, 1000, Easing.Out)));
|
||||
}
|
||||
|
||||
public void StopAllSamples()
|
||||
@@ -53,7 +71,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
base.PopIn();
|
||||
|
||||
pauseLoop.VolumeTo(1.0f, TRANSITION_DURATION, Easing.InQuint);
|
||||
pauseLoop.VolumeTo(targetVolume, TRANSITION_DURATION, Easing.InQuint);
|
||||
pauseLoop.Play();
|
||||
}
|
||||
|
||||
@@ -61,7 +79,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
base.PopOut();
|
||||
|
||||
pauseLoop.VolumeTo(0, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop());
|
||||
pauseLoop.VolumeTo(targetVolume, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop());
|
||||
}
|
||||
|
||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
|
||||
@@ -1276,11 +1276,7 @@ namespace osu.Game.Screens.Play
|
||||
/// </summary>
|
||||
/// <param name="score">The <see cref="ScoreInfo"/> to be displayed in the results screen.</param>
|
||||
/// <returns>The <see cref="ResultsScreen"/>.</returns>
|
||||
protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score)
|
||||
{
|
||||
AllowRetry = true,
|
||||
ShowUserStatistics = true,
|
||||
};
|
||||
protected abstract ResultsScreen CreateResults(ScoreInfo score);
|
||||
|
||||
private void fadeOut()
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
@@ -323,5 +324,11 @@ namespace osu.Game.Screens.Play
|
||||
api.Queue(request);
|
||||
return scoreSubmissionSource.Task;
|
||||
}
|
||||
|
||||
protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score)
|
||||
{
|
||||
AllowRetry = true,
|
||||
IsLocalPlay = true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,11 +79,10 @@ namespace osu.Game.Screens.Ranking
|
||||
public bool AllowWatchingReplay { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user's personal statistics should be shown on the extended statistics panel
|
||||
/// after clicking the score panel associated with the <see cref="Score"/> being presented.
|
||||
/// Requires <see cref="Score"/> to be present.
|
||||
/// Whether the provided score is for a local user's play.
|
||||
/// This will trigger elements like the user's ranking to display.
|
||||
/// </summary>
|
||||
public bool ShowUserStatistics { get; init; }
|
||||
public bool IsLocalPlay { get; init; }
|
||||
|
||||
private Sample? popInSample;
|
||||
|
||||
@@ -121,11 +120,12 @@ namespace osu.Game.Screens.Ranking
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new GlobalScrollAdjustsVolume(),
|
||||
StatisticsPanel = createStatisticsPanel().With(panel =>
|
||||
StatisticsPanel = new StatisticsPanel
|
||||
{
|
||||
panel.RelativeSizeAxes = Axes.Both;
|
||||
panel.Score.BindTarget = SelectedScore;
|
||||
}),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Score = { BindTarget = SelectedScore },
|
||||
AchievedScore = IsLocalPlay && Score != null ? Score : null,
|
||||
},
|
||||
ScorePanelList = new ScorePanelList
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@@ -353,16 +353,6 @@ namespace osu.Game.Screens.Ranking
|
||||
/// <param name="direction">The fetch direction. -1 to fetch scores greater than the current start of the list, and 1 to fetch scores lower than the current end of the list.</param>
|
||||
protected virtual Task<ScoreInfo[]> FetchNextPage(int direction) => Task.FromResult<ScoreInfo[]>([]);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the <see cref="Statistics.StatisticsPanel"/> to be used to display extended information about scores.
|
||||
/// </summary>
|
||||
private StatisticsPanel createStatisticsPanel()
|
||||
{
|
||||
return ShowUserStatistics && Score != null
|
||||
? new UserStatisticsPanel(Score)
|
||||
: new StatisticsPanel();
|
||||
}
|
||||
|
||||
private Task addScores(ScoreInfo[] scores)
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
|
||||
@@ -14,10 +14,13 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Placeholders;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking.Statistics.User;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Ranking.Statistics
|
||||
@@ -28,11 +31,21 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
|
||||
public readonly Bindable<ScoreInfo?> Score = new Bindable<ScoreInfo?>();
|
||||
|
||||
/// <summary>
|
||||
/// The score which was achieved by the local user.
|
||||
/// If this is set to a non-null score, an <see cref="OverallRanking"/> component will be displayed showing changes to the local user's ranking and statistics
|
||||
/// when a statistics update related to this score is received from spectator server.
|
||||
/// </summary>
|
||||
public ScoreInfo? AchievedScore { get; init; }
|
||||
|
||||
protected override bool StartHidden => true;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmapManager { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
private readonly Container content;
|
||||
private readonly LoadingSpinner spinner;
|
||||
|
||||
@@ -97,7 +110,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
bool hitEventsAvailable = newScore.HitEvents.Count != 0;
|
||||
Container<Drawable> container;
|
||||
|
||||
var statisticItems = CreateStatisticItems(newScore, task.GetResultSafely());
|
||||
var statisticItems = CreateStatisticItems(newScore, task.GetResultSafely()).ToArray();
|
||||
|
||||
if (!hitEventsAvailable && statisticItems.All(c => c.RequiresHitEvents))
|
||||
{
|
||||
@@ -199,8 +212,53 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
/// </summary>
|
||||
/// <param name="newScore">The score to create the rows for.</param>
|
||||
/// <param name="playableBeatmap">The beatmap on which the score was set.</param>
|
||||
protected virtual ICollection<StatisticItem> CreateStatisticItems(ScoreInfo newScore, IBeatmap playableBeatmap)
|
||||
=> newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap);
|
||||
protected virtual IEnumerable<StatisticItem> CreateStatisticItems(ScoreInfo newScore, IBeatmap playableBeatmap)
|
||||
{
|
||||
foreach (var statistic in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap))
|
||||
yield return statistic;
|
||||
|
||||
if (AchievedScore != null
|
||||
&& newScore.UserID > 1
|
||||
&& newScore.UserID == AchievedScore.UserID
|
||||
&& newScore.OnlineID > 0
|
||||
&& newScore.OnlineID == AchievedScore.OnlineID)
|
||||
{
|
||||
yield return new StatisticItem("Overall Ranking", () => new OverallRanking(newScore)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
}
|
||||
|
||||
if (AchievedScore != null
|
||||
&& newScore.BeatmapInfo!.OnlineID > 0
|
||||
&& api.IsLoggedIn)
|
||||
{
|
||||
if (
|
||||
// We may want to iterate on this condition
|
||||
AchievedScore.Rank >= ScoreRank.C
|
||||
)
|
||||
{
|
||||
yield return new StatisticItem("Tag the beatmap!", () => new UserTagControl(newScore.BeatmapInfo)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new StatisticItem("Tag the beatmap!", () => new OsuTextFlowContainer(cp => cp.Font = OsuFont.GetFont(size: StatisticItem.FONT_SIZE, weight: FontWeight.SemiBold))
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
TextAnchor = Anchor.Centre,
|
||||
Text = "Set a better score to contribute to beatmap tags!",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
|
||||
@@ -5,8 +5,10 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Screens.Ranking.Statistics.User
|
||||
{
|
||||
@@ -14,13 +16,21 @@ namespace osu.Game.Screens.Ranking.Statistics.User
|
||||
{
|
||||
private const float transition_duration = 300;
|
||||
|
||||
public Bindable<ScoreBasedUserStatisticsUpdate?> StatisticsUpdate { get; } = new Bindable<ScoreBasedUserStatisticsUpdate?>();
|
||||
public Bindable<ScoreBasedUserStatisticsUpdate?> DisplayedUpdate { get; } = new Bindable<ScoreBasedUserStatisticsUpdate?>();
|
||||
private readonly IBindable<ScoreBasedUserStatisticsUpdate?> latestGlobalStatisticsUpdate = new Bindable<ScoreBasedUserStatisticsUpdate?>();
|
||||
|
||||
private readonly ScoreInfo scoreInfo;
|
||||
|
||||
private LoadingLayer loadingLayer = null!;
|
||||
private GridContainer content = null!;
|
||||
|
||||
public OverallRanking(ScoreInfo scoreInfo)
|
||||
{
|
||||
this.scoreInfo = scoreInfo;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(UserStatisticsWatcher? userStatisticsWatcher)
|
||||
{
|
||||
AutoSizeAxes = Axes.Y;
|
||||
AutoSizeEasing = Easing.OutQuint;
|
||||
@@ -55,34 +65,44 @@ namespace osu.Game.Screens.Ranking.Statistics.User
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new GlobalRankChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
|
||||
new GlobalRankChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } },
|
||||
new SimpleStatisticTable.Spacer(),
|
||||
new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
|
||||
new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } },
|
||||
},
|
||||
[],
|
||||
new Drawable[]
|
||||
{
|
||||
new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
|
||||
new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } },
|
||||
new SimpleStatisticTable.Spacer(),
|
||||
new AccuracyChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
|
||||
new AccuracyChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } },
|
||||
},
|
||||
[],
|
||||
new Drawable[]
|
||||
{
|
||||
new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
|
||||
new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } },
|
||||
new SimpleStatisticTable.Spacer(),
|
||||
new TotalScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
|
||||
new TotalScoreChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } },
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (userStatisticsWatcher != null)
|
||||
{
|
||||
latestGlobalStatisticsUpdate.BindTo(userStatisticsWatcher.LatestUpdate);
|
||||
latestGlobalStatisticsUpdate.BindValueChanged(update =>
|
||||
{
|
||||
if (update.NewValue?.Score.MatchesOnlineID(scoreInfo) == true)
|
||||
DisplayedUpdate.Value = update.NewValue;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
StatisticsUpdate.BindValueChanged(onUpdateReceived, true);
|
||||
DisplayedUpdate.BindValueChanged(onUpdateReceived, true);
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,65 +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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking.Statistics.User;
|
||||
|
||||
namespace osu.Game.Screens.Ranking.Statistics
|
||||
{
|
||||
public partial class UserStatisticsPanel : StatisticsPanel
|
||||
{
|
||||
private readonly ScoreInfo achievedScore;
|
||||
|
||||
internal readonly Bindable<ScoreBasedUserStatisticsUpdate?> DisplayedUserStatisticsUpdate = new Bindable<ScoreBasedUserStatisticsUpdate?>();
|
||||
|
||||
private IBindable<ScoreBasedUserStatisticsUpdate?> latestGlobalStatisticsUpdate = null!;
|
||||
|
||||
public UserStatisticsPanel(ScoreInfo achievedScore)
|
||||
{
|
||||
this.achievedScore = achievedScore;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(UserStatisticsWatcher? userStatisticsWatcher)
|
||||
{
|
||||
if (userStatisticsWatcher != null)
|
||||
{
|
||||
latestGlobalStatisticsUpdate = userStatisticsWatcher.LatestUpdate.GetBoundCopy();
|
||||
latestGlobalStatisticsUpdate.BindValueChanged(update =>
|
||||
{
|
||||
if (update.NewValue?.Score.MatchesOnlineID(achievedScore) == true)
|
||||
DisplayedUserStatisticsUpdate.Value = update.NewValue;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override ICollection<StatisticItem> CreateStatisticItems(ScoreInfo newScore, IBeatmap playableBeatmap)
|
||||
{
|
||||
var items = base.CreateStatisticItems(newScore, playableBeatmap);
|
||||
|
||||
if (newScore.UserID > 1
|
||||
&& newScore.UserID == achievedScore.UserID
|
||||
&& newScore.OnlineID > 0
|
||||
&& newScore.OnlineID == achievedScore.OnlineID)
|
||||
{
|
||||
items = items.Append(new StatisticItem("Overall Ranking", () => new OverallRanking
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
StatisticsUpdate = { BindTarget = DisplayedUserStatisticsUpdate }
|
||||
})).ToArray();
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,11 @@ using osu.Game.Graphics.Containers;
|
||||
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.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@@ -37,6 +39,8 @@ namespace osu.Game.Screens.Ranking
|
||||
{
|
||||
public partial class UserTagControl : CompositeDrawable
|
||||
{
|
||||
private readonly BeatmapInfo beatmapInfo;
|
||||
|
||||
public override bool HandlePositionalInput => true;
|
||||
|
||||
private readonly Cached layout = new Cached();
|
||||
@@ -53,8 +57,10 @@ namespace osu.Game.Screens.Ranking
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Bindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||
public UserTagControl(BeatmapInfo beatmapInfo)
|
||||
{
|
||||
this.beatmapInfo = beatmapInfo;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(SessionStatics sessionStatics)
|
||||
@@ -104,8 +110,8 @@ namespace osu.Game.Screens.Ranking
|
||||
api.Queue(listTagsRequest);
|
||||
}
|
||||
|
||||
var getBeatmapSetRequest = new GetBeatmapSetRequest(beatmap.Value.BeatmapInfo.BeatmapSet!.OnlineID);
|
||||
getBeatmapSetRequest.Success += set => apiBeatmap.Value = set.Beatmaps.SingleOrDefault(b => b.MatchesOnlineID(beatmap.Value.BeatmapInfo));
|
||||
var getBeatmapSetRequest = new GetBeatmapSetRequest(beatmapInfo.BeatmapSet!.OnlineID);
|
||||
getBeatmapSetRequest.Success += set => apiBeatmap.Value = set.Beatmaps.SingleOrDefault(b => b.MatchesOnlineID(beatmapInfo));
|
||||
api.Queue(getBeatmapSetRequest);
|
||||
}
|
||||
|
||||
@@ -114,7 +120,7 @@ namespace osu.Game.Screens.Ranking
|
||||
loadingLayer.Show();
|
||||
extraTags.Remove(tag);
|
||||
|
||||
var req = new AddBeatmapTagRequest(beatmap.Value.BeatmapInfo.OnlineID, tag.Id);
|
||||
var req = new AddBeatmapTagRequest(beatmapInfo.OnlineID, tag.Id);
|
||||
req.Success += () =>
|
||||
{
|
||||
tag.Voted.Value = true;
|
||||
@@ -495,21 +501,45 @@ namespace osu.Game.Screens.Ranking
|
||||
searchBox.Current.BindValueChanged(_ => searchContainer.SearchTerm = searchBox.Current.Value, true);
|
||||
}
|
||||
|
||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (base.OnPressed(e))
|
||||
return true;
|
||||
|
||||
if (e.Repeat)
|
||||
return false;
|
||||
|
||||
if (State.Value == Visibility.Hidden)
|
||||
return false;
|
||||
|
||||
if (e.Action == GlobalAction.Select)
|
||||
{
|
||||
attemptSelect();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
var visibleItems = searchContainer.OfType<DrawableAddableTag>().Where(d => d.IsPresent).ToArray();
|
||||
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
if (visibleItems.Length == 1)
|
||||
select(visibleItems.Single().Tag);
|
||||
|
||||
attemptSelect();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
private void attemptSelect()
|
||||
{
|
||||
var visibleItems = searchContainer.OfType<DrawableAddableTag>().Where(d => d.IsPresent).ToArray();
|
||||
|
||||
if (visibleItems.Length == 1)
|
||||
select(visibleItems.Single().Tag);
|
||||
}
|
||||
|
||||
private void select(UserTag tag)
|
||||
{
|
||||
OnSelected?.Invoke(tag);
|
||||
@@ -530,14 +560,14 @@ namespace osu.Game.Screens.Ranking
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
private void load(OsuColour colours, OverlayColourProvider? colourProvider)
|
||||
{
|
||||
Content.AddRange(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.GreySeaFoamDark,
|
||||
Colour = colourProvider?.Background3 ?? colours.GreySeaFoamDark,
|
||||
Depth = float.MaxValue,
|
||||
},
|
||||
new FillFlowContainer
|
||||
|
||||
@@ -301,7 +301,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
items.Add(new OsuMenuItem("Collections") { Items = collectionItems });
|
||||
|
||||
if (beatmapInfo.GetOnlineURL(api, ruleset.Value) is string url)
|
||||
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyUrlToClipboard(url)));
|
||||
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyToClipboard(url)));
|
||||
|
||||
if (manager != null)
|
||||
items.Add(new OsuMenuItem("Mark as played", MenuItemType.Standard, () => manager.MarkPlayed(beatmapInfo)));
|
||||
|
||||
@@ -301,7 +301,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet)));
|
||||
|
||||
if (beatmapSet.GetOnlineURL(api, ruleset.Value) is string url)
|
||||
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyUrlToClipboard(url)));
|
||||
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyToClipboard(url)));
|
||||
|
||||
if (dialogOverlay != null)
|
||||
items.Add(new OsuMenuItem("Delete...", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet))));
|
||||
|
||||
@@ -238,7 +238,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
QueueMode = ServerAPIRoom.QueueMode,
|
||||
AutoStartDuration = ServerAPIRoom.AutoStartDuration
|
||||
},
|
||||
Playlist = ServerAPIRoom.Playlist.Select(CreateMultiplayerPlaylistItem).ToList(),
|
||||
Playlist = ServerAPIRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item)).ToList(),
|
||||
Users = { localUser },
|
||||
Host = localUser
|
||||
};
|
||||
@@ -687,21 +687,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
return MessagePackSerializer.Deserialize<T>(serialized, SignalRUnionWorkaroundResolver.OPTIONS);
|
||||
}
|
||||
|
||||
public static MultiplayerPlaylistItem CreateMultiplayerPlaylistItem(PlaylistItem item) => new MultiplayerPlaylistItem
|
||||
{
|
||||
ID = item.ID,
|
||||
OwnerID = item.OwnerID,
|
||||
BeatmapID = item.Beatmap.OnlineID,
|
||||
BeatmapChecksum = item.Beatmap.MD5Hash,
|
||||
RulesetID = item.RulesetID,
|
||||
RequiredMods = item.RequiredMods.ToArray(),
|
||||
AllowedMods = item.AllowedMods.ToArray(),
|
||||
Expired = item.Expired,
|
||||
PlaylistOrder = item.PlaylistOrder ?? 0,
|
||||
PlayedAt = item.PlayedAt,
|
||||
StarRating = item.Beatmap.StarRating,
|
||||
};
|
||||
|
||||
public override Task DisconnectInternal()
|
||||
{
|
||||
isConnected.Value = false;
|
||||
|
||||
@@ -90,6 +90,7 @@ namespace osu.Game.Users
|
||||
|
||||
private void updatePresence()
|
||||
{
|
||||
// TODO: we probably don't want to do this every frame.
|
||||
UserPresence? presence = metadata?.GetPresence(User.OnlineID);
|
||||
UserStatus status = presence?.Status ?? UserStatus.Offline;
|
||||
UserActivity? activity = presence?.Activity;
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="20.1.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2025.313.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2025.313.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2025.318.1" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2025.318.0" />
|
||||
<PackageReference Include="Sentry" Version="5.1.1" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
<PackageReference Include="SharpCompress" Version="0.39.0" />
|
||||
|
||||
+1
-1
@@ -17,6 +17,6 @@
|
||||
<MtouchInterpreter>-all</MtouchInterpreter>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2025.313.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2025.318.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user