mirror of
https://github.com/ppy/osu.git
synced 2026-05-17 21:13:01 +08:00
Compare commits
220 Commits
2025.316.0
...
2025.401.0
@@ -82,8 +82,18 @@ jobs:
|
||||
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf
|
||||
|
||||
- name: Test
|
||||
run: dotnet test $pwd/**/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx" -- NUnit.ConsoleOut=0
|
||||
shell: pwsh
|
||||
run: >
|
||||
dotnet test
|
||||
osu.Game.Tests/bin/Debug/**/osu.Game.Tests.dll
|
||||
osu.Game.Rulesets.Osu.Tests/bin/Debug/**/osu.Game.Rulesets.Osu.Tests.dll
|
||||
osu.Game.Rulesets.Taiko.Tests/bin/Debug/**/osu.Game.Rulesets.Taiko.Tests.dll
|
||||
osu.Game.Rulesets.Catch.Tests/bin/Debug/**/osu.Game.Rulesets.Catch.Tests.dll
|
||||
osu.Game.Rulesets.Mania.Tests/bin/Debug/**/osu.Game.Rulesets.Mania.Tests.dll
|
||||
osu.Game.Tournament.Tests/bin/Debug/**/osu.Game.Tournament.Tests.dll
|
||||
Templates/**/*.Tests/bin/Debug/**/*.Tests.dll
|
||||
--logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx"
|
||||
--
|
||||
NUnit.ConsoleOut=0
|
||||
|
||||
# Attempt to upload results even if test fails.
|
||||
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
|
||||
@@ -136,4 +146,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.321.0" />
|
||||
</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();
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// 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 System.Collections.Generic;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
@@ -35,21 +36,21 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
[SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")]
|
||||
public BindableBool HardRockOffsets { get; } = new BindableBool();
|
||||
|
||||
public override string SettingDescription
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}";
|
||||
string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}";
|
||||
string spicyPatterns = HardRockOffsets.IsDefault ? string.Empty : "Spicy patterns";
|
||||
if (!CircleSize.IsDefault)
|
||||
yield return ("Circle size", $"{CircleSize.Value:N1}");
|
||||
|
||||
return string.Join(", ", new[]
|
||||
{
|
||||
circleSize,
|
||||
base.SettingDescription,
|
||||
approachRate,
|
||||
spicyPatterns,
|
||||
}.Where(s => !string.IsNullOrEmpty(s)));
|
||||
foreach (var setting in base.SettingDescription)
|
||||
yield return setting;
|
||||
|
||||
if (!ApproachRate.IsDefault)
|
||||
yield return ("Approach rate", $"{ApproachRate.Value:N1}");
|
||||
|
||||
if (!HardRockOffsets.IsDefault)
|
||||
yield return ("Spicy patterns", "On");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,7 +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.Linq;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -36,19 +36,18 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
||||
};
|
||||
|
||||
public override string SettingDescription
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}";
|
||||
string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}";
|
||||
if (!CircleSize.IsDefault)
|
||||
yield return ("Circle size", $"{CircleSize.Value:N1}");
|
||||
|
||||
return string.Join(", ", new[]
|
||||
{
|
||||
circleSize,
|
||||
base.SettingDescription,
|
||||
approachRate
|
||||
}.Where(s => !string.IsNullOrEmpty(s)));
|
||||
foreach (var setting in base.SettingDescription)
|
||||
yield return setting;
|
||||
|
||||
if (!ApproachRate.IsDefault)
|
||||
yield return ("Approach rate", $"{ApproachRate.Value:N1}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,7 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -19,17 +20,15 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
ReadCurrentFromDifficulty = _ => 1,
|
||||
};
|
||||
|
||||
public override string SettingDescription
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
string scrollSpeed = ScrollSpeed.IsDefault ? string.Empty : $"Scroll x{ScrollSpeed.Value:N2}";
|
||||
foreach (var setting in base.SettingDescription)
|
||||
yield return setting;
|
||||
|
||||
return string.Join(", ", new[]
|
||||
{
|
||||
base.SettingDescription,
|
||||
scrollSpeed
|
||||
}.Where(s => !string.IsNullOrEmpty(s)));
|
||||
if (!ScrollSpeed.IsDefault)
|
||||
yield return ("Scroll speed", $"x{ScrollSpeed.Value:N2}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,11 +60,17 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
{
|
||||
const double animation_time = 120;
|
||||
|
||||
(sprite as IFramedAnimation)?.GotoFrame(0);
|
||||
var animation = sprite as IFramedAnimation;
|
||||
|
||||
animation?.GotoFrame(0);
|
||||
(strongSprite as IFramedAnimation)?.GotoFrame(0);
|
||||
|
||||
this.FadeInFromZero(animation_time).Then().FadeOut(animation_time * 1.5);
|
||||
|
||||
// legacy judgements don't play any transforms if they are an animation.
|
||||
if (animation?.FrameCount > 1)
|
||||
return;
|
||||
|
||||
this.ScaleTo(0.6f)
|
||||
.Then().ScaleTo(1.1f, animation_time * 0.8)
|
||||
.Then().ScaleTo(0.9f, animation_time * 0.4)
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,12 +62,11 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("Run background processor", () =>
|
||||
{
|
||||
Add(new TestBackgroundDataStoreProcessor());
|
||||
});
|
||||
TestBackgroundDataStoreProcessor processor = null!;
|
||||
AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor()));
|
||||
AddUntilStep("Wait for completion", () => processor.Completed);
|
||||
|
||||
AddUntilStep("wait for difficulties repopulated", () =>
|
||||
AddAssert("Difficulties repopulated", () =>
|
||||
{
|
||||
return Realm.Run(r =>
|
||||
{
|
||||
@@ -101,13 +100,10 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("Run background processor", () =>
|
||||
{
|
||||
Add(new TestBackgroundDataStoreProcessor());
|
||||
});
|
||||
TestBackgroundDataStoreProcessor processor = null!;
|
||||
AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor()));
|
||||
|
||||
AddWaitStep("wait some", 500);
|
||||
|
||||
AddAssert("Difficulty still not populated", () =>
|
||||
{
|
||||
return Realm.Run(r =>
|
||||
@@ -118,8 +114,9 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
|
||||
AddStep("Set not playing", () => isPlaying.Value = LocalUserPlayingState.NotPlaying);
|
||||
AddUntilStep("Wait for completion", () => processor.Completed);
|
||||
|
||||
AddUntilStep("wait for difficulties repopulated", () =>
|
||||
AddAssert("Difficulties repopulated", () =>
|
||||
{
|
||||
return Realm.Run(r =>
|
||||
{
|
||||
@@ -151,9 +148,11 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor()));
|
||||
TestBackgroundDataStoreProcessor processor = null!;
|
||||
AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor()));
|
||||
AddUntilStep("Wait for completion", () => processor.Completed);
|
||||
|
||||
AddUntilStep("Score version upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(LegacyScoreEncoder.LATEST_VERSION));
|
||||
AddAssert("Score version upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(LegacyScoreEncoder.LATEST_VERSION));
|
||||
AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False);
|
||||
}
|
||||
|
||||
@@ -183,7 +182,7 @@ namespace osu.Game.Tests.Database
|
||||
AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor()));
|
||||
AddUntilStep("Wait for completion", () => processor.Completed);
|
||||
|
||||
AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True);
|
||||
AddAssert("Score marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True);
|
||||
AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(scoreVersion));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
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.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Tests.Online
|
||||
{
|
||||
[HeadlessTest]
|
||||
public partial class TestSceneMultiplayerBeatmapAvailabilityTracker : MultiplayerTestScene
|
||||
{
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
private BeatmapInfo availableBeatmap = null!;
|
||||
private BeatmapInfo unavailableBeatmap = null!;
|
||||
|
||||
private MultiplayerBeatmapAvailabilityTracker tracker = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
{
|
||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
|
||||
var importedSet = beatmapManager.GetAllUsableBeatmapSets().First();
|
||||
availableBeatmap = importedSet.Beatmaps[0];
|
||||
unavailableBeatmap = importedSet.Beatmaps[1];
|
||||
|
||||
Realm.Write(r => r.Remove(r.Find<BeatmapInfo>(unavailableBeatmap.ID)!));
|
||||
}
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("setup tracker", () =>
|
||||
{
|
||||
DummyAPIAccess api = (DummyAPIAccess)API;
|
||||
Func<APIRequest, bool>? defaultRequestHandler = api.HandleRequest;
|
||||
|
||||
api.HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case GetBeatmapsRequest beatmapsReq:
|
||||
var availableApiBeatmap = CreateAPIBeatmap();
|
||||
availableApiBeatmap.OnlineID = availableBeatmap.OnlineID;
|
||||
availableApiBeatmap.OnlineBeatmapSetID = availableBeatmap.BeatmapSet!.OnlineID;
|
||||
availableApiBeatmap.Checksum = availableBeatmap.MD5Hash;
|
||||
availableApiBeatmap.BeatmapSet!.OnlineID = availableBeatmap.BeatmapSet!.OnlineID;
|
||||
|
||||
var unavailableApiBeatmap = CreateAPIBeatmap();
|
||||
unavailableApiBeatmap.OnlineID = unavailableBeatmap.OnlineID;
|
||||
unavailableApiBeatmap.OnlineBeatmapSetID = unavailableBeatmap.BeatmapSet!.OnlineID;
|
||||
unavailableApiBeatmap.Checksum = unavailableBeatmap.MD5Hash;
|
||||
unavailableApiBeatmap.BeatmapSet!.OnlineID = unavailableBeatmap.BeatmapSet!.OnlineID;
|
||||
|
||||
beatmapsReq.TriggerSuccess(new GetBeatmapsResponse
|
||||
{
|
||||
Beatmaps = new List<APIBeatmap>
|
||||
{
|
||||
availableApiBeatmap,
|
||||
unavailableApiBeatmap
|
||||
}
|
||||
});
|
||||
return true;
|
||||
|
||||
default:
|
||||
return defaultRequestHandler?.Invoke(req) ?? false;
|
||||
}
|
||||
};
|
||||
|
||||
Child = tracker = new MultiplayerBeatmapAvailabilityTracker();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEnterRoomWithNotDownloadedBeatmap()
|
||||
{
|
||||
AddStep("join room", () =>
|
||||
{
|
||||
var room = CreateDefaultRoom();
|
||||
room.Playlist = [new PlaylistItem(unavailableBeatmap)];
|
||||
JoinRoom(room);
|
||||
});
|
||||
|
||||
WaitForJoined();
|
||||
|
||||
AddUntilStep("beatmap is not available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.NotDownloaded));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEnterRoomWithLocallyAvailableBeatmap()
|
||||
{
|
||||
AddStep("join room", () =>
|
||||
{
|
||||
var room = CreateDefaultRoom();
|
||||
room.Playlist = [new PlaylistItem(availableBeatmap)];
|
||||
JoinRoom(room);
|
||||
});
|
||||
|
||||
WaitForJoined();
|
||||
|
||||
AddUntilStep("beatmap is available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.LocallyAvailable));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAvailabilityUpdatesOnItemEdit()
|
||||
{
|
||||
AddStep("join room", () =>
|
||||
{
|
||||
var room = CreateDefaultRoom();
|
||||
room.Playlist = [new PlaylistItem(availableBeatmap)];
|
||||
JoinRoom(room);
|
||||
});
|
||||
|
||||
WaitForJoined();
|
||||
|
||||
AddUntilStep("beatmap is available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.LocallyAvailable));
|
||||
|
||||
AddStep("change item to not downloaded beatmap", () =>
|
||||
{
|
||||
PlaylistItem newItem = new PlaylistItem(MultiplayerClient.ClientRoom!.CurrentPlaylistItem).With(beatmap: new Optional<IBeatmapInfo>(unavailableBeatmap));
|
||||
MultiplayerClient.EditPlaylistItem(new MultiplayerPlaylistItem(newItem)).WaitSafely();
|
||||
});
|
||||
|
||||
AddUntilStep("beatmap is not available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.NotDownloaded));
|
||||
|
||||
AddStep("change item to downloaded beatmap", () =>
|
||||
{
|
||||
PlaylistItem newItem = new PlaylistItem(MultiplayerClient.ClientRoom!.CurrentPlaylistItem).With(beatmap: new Optional<IBeatmapInfo>(availableBeatmap));
|
||||
MultiplayerClient.EditPlaylistItem(new MultiplayerPlaylistItem(newItem)).WaitSafely();
|
||||
});
|
||||
|
||||
AddUntilStep("beatmap is available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.LocallyAvailable));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAvailabilityUpdatesOnSettingsChange()
|
||||
{
|
||||
AddStep("join room", () =>
|
||||
{
|
||||
var room = CreateDefaultRoom();
|
||||
room.Playlist = [new PlaylistItem(availableBeatmap), new PlaylistItem(unavailableBeatmap)];
|
||||
JoinRoom(room);
|
||||
});
|
||||
|
||||
WaitForJoined();
|
||||
|
||||
AddUntilStep("beatmap is available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.LocallyAvailable));
|
||||
|
||||
AddStep("change settings to not downloaded beatmap", () => MultiplayerClient.ChangeServerRoomSettings(new MultiplayerRoomSettings(MultiplayerClient.ClientAPIRoom!)
|
||||
{
|
||||
PlaylistItemId = MultiplayerClient.ServerRoom!.Playlist[1].ID
|
||||
}).WaitSafely());
|
||||
|
||||
AddUntilStep("beatmap is not available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.NotDownloaded));
|
||||
|
||||
AddStep("change settings to downloaded beatmap", () => MultiplayerClient.ChangeServerRoomSettings(new MultiplayerRoomSettings(MultiplayerClient.ClientAPIRoom!)
|
||||
{
|
||||
PlaylistItemId = MultiplayerClient.ServerRoom!.Playlist[0].ID
|
||||
}).WaitSafely());
|
||||
|
||||
AddUntilStep("beatmap is available", () => tracker.Availability.Value.State, () => Is.EqualTo(DownloadState.LocallyAvailable));
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
-31
@@ -1,18 +1,14 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using JetBrains.Annotations;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.IO.Stores;
|
||||
@@ -27,31 +23,29 @@ using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Tests.Online
|
||||
{
|
||||
[HeadlessTest]
|
||||
public partial class TestSceneOnlinePlayBeatmapAvailabilityTracker : OsuTestScene
|
||||
public partial class TestScenePlaylistsBeatmapAvailabilityTracker : OsuTestScene
|
||||
{
|
||||
private RulesetStore rulesets;
|
||||
private TestBeatmapManager beatmaps;
|
||||
private TestBeatmapModelDownloader beatmapDownloader;
|
||||
private TestBeatmapManager beatmaps = null!;
|
||||
private TestBeatmapModelDownloader beatmapDownloader = null!;
|
||||
|
||||
private string testBeatmapFile;
|
||||
private BeatmapInfo testBeatmapInfo;
|
||||
private BeatmapSetInfo testBeatmapSet;
|
||||
private string testBeatmapFile = null!;
|
||||
private BeatmapInfo testBeatmapInfo = null!;
|
||||
private BeatmapSetInfo testBeatmapSet = null!;
|
||||
|
||||
private readonly Bindable<PlaylistItem> selectedItem = new Bindable<PlaylistItem>();
|
||||
private OnlinePlayBeatmapAvailabilityTracker availabilityTracker;
|
||||
private OnlinePlayBeatmapAvailabilityTracker availabilityTracker = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, GameHost host)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.CacheAs<BeatmapManager>(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.CacheAs<BeatmapManager>(beatmaps = new TestBeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.CacheAs<BeatmapModelDownloader>(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API));
|
||||
}
|
||||
|
||||
@@ -82,16 +76,11 @@ namespace osu.Game.Tests.Online
|
||||
testBeatmapFile = TestResources.GetQuickTestBeatmapForImport();
|
||||
|
||||
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
|
||||
testBeatmapSet = testBeatmapInfo.BeatmapSet;
|
||||
testBeatmapSet = testBeatmapInfo.BeatmapSet!;
|
||||
|
||||
Realm.Write(r => r.RemoveAll<BeatmapSetInfo>());
|
||||
Realm.Write(r => r.RemoveAll<BeatmapInfo>());
|
||||
|
||||
selectedItem.Value = new PlaylistItem(testBeatmapInfo)
|
||||
{
|
||||
RulesetID = testBeatmapInfo.Ruleset.OnlineID,
|
||||
};
|
||||
|
||||
recreateChildren();
|
||||
});
|
||||
|
||||
@@ -108,9 +97,15 @@ namespace osu.Game.Tests.Online
|
||||
Children = new Drawable[]
|
||||
{
|
||||
beatmapLookupCache,
|
||||
availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
|
||||
availabilityTracker = new PlaylistsBeatmapAvailabilityTracker
|
||||
{
|
||||
SelectedItem = { BindTarget = selectedItem, }
|
||||
PlaylistItem =
|
||||
{
|
||||
Value = new PlaylistItem(testBeatmapInfo)
|
||||
{
|
||||
RulesetID = testBeatmapInfo.Ruleset.OnlineID,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -125,10 +120,10 @@ namespace osu.Game.Tests.Online
|
||||
AddStep("start downloading", () => beatmapDownloader.Download(testBeatmapSet));
|
||||
addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0f));
|
||||
|
||||
AddStep("set progress 40%", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.SetProgress(0.4f));
|
||||
AddStep("set progress 40%", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)!).SetProgress(0.4f));
|
||||
addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4f));
|
||||
|
||||
AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.TriggerSuccess(testBeatmapFile));
|
||||
AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)!).TriggerSuccess(testBeatmapFile));
|
||||
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
|
||||
|
||||
AddStep("allow importing", () => beatmaps.AllowImport.Set());
|
||||
@@ -203,10 +198,10 @@ namespace osu.Game.Tests.Online
|
||||
{
|
||||
public readonly ManualResetEventSlim AllowImport = new ManualResetEventSlim();
|
||||
|
||||
public Live<BeatmapSetInfo> CurrentImport { get; private set; }
|
||||
public Live<BeatmapSetInfo>? CurrentImport { get; private set; }
|
||||
|
||||
public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> resources,
|
||||
GameHost host = null, WorkingBeatmap defaultBeatmap = null)
|
||||
public TestBeatmapManager(Storage storage, RealmAccess realm, IAPIProvider api, AudioManager audioManager, IResourceStore<byte[]> resources,
|
||||
GameHost? host = null, WorkingBeatmap? defaultBeatmap = null)
|
||||
: base(storage, realm, api, audioManager, resources, host, defaultBeatmap)
|
||||
{
|
||||
}
|
||||
@@ -226,12 +221,13 @@ namespace osu.Game.Tests.Online
|
||||
this.testBeatmapManager = testBeatmapManager;
|
||||
}
|
||||
|
||||
public override Live<BeatmapSetInfo> ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default)
|
||||
public override Live<BeatmapSetInfo>? ImportModel(BeatmapSetInfo item, ArchiveReader? archive = null, ImportParameters parameters = default,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!testBeatmapManager.AllowImport.Wait(TimeSpan.FromSeconds(10), cancellationToken))
|
||||
throw new TimeoutException("Timeout waiting for import to be allowed.");
|
||||
|
||||
return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, parameters, cancellationToken));
|
||||
return testBeatmapManager.CurrentImport = base.ImportModel(item, archive, parameters, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,6 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
}
|
||||
|
||||
private void assertCorrectIcon(bool favourited) => AddAssert("icon correct",
|
||||
() => this.ChildrenOfType<SpriteIcon>().Single().Icon.Equals(favourited ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart));
|
||||
() => this.ChildrenOfType<SpriteIcon>().First().Icon.Equals(favourited ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osuTK;
|
||||
@@ -15,16 +13,18 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
{
|
||||
public partial class TestSceneDifficultySpectrumDisplay : OsuTestScene
|
||||
{
|
||||
private DifficultySpectrumDisplay display;
|
||||
private DifficultySpectrumDisplay display = null!;
|
||||
|
||||
private static APIBeatmapSet createBeatmapSetWith(params (int rulesetId, double stars)[] difficulties) => new APIBeatmapSet
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
Beatmaps = difficulties.Select(difficulty => new APIBeatmap
|
||||
AddStep("create spectrum display", () => Child = display = new DifficultySpectrumDisplay
|
||||
{
|
||||
RulesetID = difficulty.rulesetId,
|
||||
StarRating = difficulty.stars
|
||||
}).ToArray()
|
||||
};
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(3)
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSingleRuleset()
|
||||
@@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
(rulesetId: 0, stars: 3.2),
|
||||
(rulesetId: 0, stars: 5.6));
|
||||
|
||||
createDisplay(beatmapSet);
|
||||
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
(rulesetId: 1, stars: 4.3),
|
||||
(rulesetId: 0, stars: 5.6));
|
||||
|
||||
createDisplay(beatmapSet);
|
||||
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -61,52 +61,30 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
(rulesetId: 0, stars: 5.6),
|
||||
(rulesetId: 15, stars: 7.8));
|
||||
|
||||
createDisplay(beatmapSet);
|
||||
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMaximumUncollapsed()
|
||||
{
|
||||
var beatmapSet = createBeatmapSetWith(Enumerable.Range(0, 12).Select(i => (rulesetId: i % 4, stars: 2.5 + i * 0.25)).ToArray());
|
||||
createDisplay(beatmapSet);
|
||||
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMinimumCollapsed()
|
||||
{
|
||||
var beatmapSet = createBeatmapSetWith(Enumerable.Range(0, 13).Select(i => (rulesetId: i % 4, stars: 2.5 + i * 0.25)).ToArray());
|
||||
createDisplay(beatmapSet);
|
||||
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAdjustableDotSize()
|
||||
private static APIBeatmapSet createBeatmapSetWith(params (int rulesetId, double stars)[] difficulties) => new APIBeatmapSet
|
||||
{
|
||||
var beatmapSet = createBeatmapSetWith(
|
||||
(rulesetId: 0, stars: 2.0),
|
||||
(rulesetId: 3, stars: 2.3),
|
||||
(rulesetId: 0, stars: 3.2),
|
||||
(rulesetId: 1, stars: 4.3),
|
||||
(rulesetId: 0, stars: 5.6));
|
||||
|
||||
createDisplay(beatmapSet);
|
||||
|
||||
AddStep("change dot dimensions", () =>
|
||||
Beatmaps = difficulties.Select(difficulty => new APIBeatmap
|
||||
{
|
||||
display.DotSize = new Vector2(8, 12);
|
||||
display.DotSpacing = 2;
|
||||
});
|
||||
AddStep("change dot dimensions back", () =>
|
||||
{
|
||||
display.DotSize = new Vector2(4, 8);
|
||||
display.DotSpacing = 1;
|
||||
});
|
||||
}
|
||||
|
||||
private void createDisplay(IBeatmapSetInfo beatmapSetInfo) => AddStep("create spectrum display", () => Child = display = new DifficultySpectrumDisplay(beatmapSetInfo)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(3)
|
||||
});
|
||||
RulesetID = difficulty.rulesetId,
|
||||
StarRating = difficulty.stars
|
||||
}).ToArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
{
|
||||
var room = new Room
|
||||
{
|
||||
RoomID = 1234,
|
||||
Name = "Daily Challenge: June 4, 2024",
|
||||
Playlist =
|
||||
[
|
||||
@@ -66,7 +65,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
{
|
||||
var room = new Room
|
||||
{
|
||||
RoomID = 1234,
|
||||
Name = "Daily Challenge: June 4, 2024",
|
||||
Playlist =
|
||||
[
|
||||
@@ -99,7 +97,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
{
|
||||
var room = new Room
|
||||
{
|
||||
RoomID = 1234,
|
||||
Name = "Daily Challenge: June 4, 2024",
|
||||
Playlist =
|
||||
[
|
||||
@@ -114,7 +111,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
};
|
||||
|
||||
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
|
||||
AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 });
|
||||
AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = room.RoomID!.Value });
|
||||
|
||||
Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!;
|
||||
AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
|
||||
@@ -128,7 +125,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
{
|
||||
var room = new Room
|
||||
{
|
||||
RoomID = 1234,
|
||||
Name = "Daily Challenge: June 4, 2024",
|
||||
Playlist =
|
||||
[
|
||||
@@ -143,7 +139,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
};
|
||||
|
||||
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
|
||||
AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 });
|
||||
AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = room.RoomID!.Value });
|
||||
|
||||
Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!;
|
||||
AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
|
||||
|
||||
@@ -44,17 +44,17 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
[Test]
|
||||
public void TestDailyChallenge()
|
||||
{
|
||||
startChallenge(1234);
|
||||
startChallenge();
|
||||
AddStep("push screen", () => LoadScreen(new DailyChallengeIntro(room)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayIntroOnceFlag()
|
||||
{
|
||||
startChallenge(1234);
|
||||
startChallenge();
|
||||
AddStep("set intro played flag", () => Dependencies.Get<SessionStatics>().SetValue(Static.DailyChallengeIntroPlayed, true));
|
||||
|
||||
startChallenge(1235);
|
||||
startChallenge();
|
||||
|
||||
AddAssert("intro played flag reset", () => Dependencies.Get<SessionStatics>().Get<bool>(Static.DailyChallengeIntroPlayed), () => Is.False);
|
||||
|
||||
@@ -62,13 +62,12 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
AddUntilStep("intro played flag set", () => Dependencies.Get<SessionStatics>().Get<bool>(Static.DailyChallengeIntroPlayed), () => Is.True);
|
||||
}
|
||||
|
||||
private void startChallenge(int roomId)
|
||||
private void startChallenge()
|
||||
{
|
||||
AddStep("add room", () =>
|
||||
{
|
||||
API.Perform(new CreateRoomRequest(room = new Room
|
||||
{
|
||||
RoomID = roomId,
|
||||
Name = "Daily Challenge: June 4, 2024",
|
||||
Playlist =
|
||||
[
|
||||
@@ -83,7 +82,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
Category = RoomCategory.DailyChallenge
|
||||
}));
|
||||
});
|
||||
AddStep("signal client", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = roomId }));
|
||||
AddStep("signal client", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = room.RoomID!.Value }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.SkinEditor;
|
||||
@@ -458,6 +459,62 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<LegacyDefaultComboCounter>().Count() == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAnchorRadioButtonBehavior()
|
||||
{
|
||||
ISerialisableDrawable? selectedComponent = null;
|
||||
|
||||
AddStep("Select first component", () =>
|
||||
{
|
||||
var blueprint = skinEditor.ChildrenOfType<SkinBlueprint>().First();
|
||||
skinEditor.SelectedComponents.Clear();
|
||||
skinEditor.SelectedComponents.Add(blueprint.Item);
|
||||
selectedComponent = blueprint.Item;
|
||||
});
|
||||
|
||||
AddStep("Right-click to open context menu", () =>
|
||||
{
|
||||
if (selectedComponent != null)
|
||||
InputManager.MoveMouseTo(((Drawable)selectedComponent).ScreenSpaceDrawQuad.Centre);
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
|
||||
AddStep("Click on Anchor menu", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getMenuItemByText("Anchor"));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("Right-click TopLeft anchor", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getMenuItemByText("TopLeft"));
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
|
||||
AddAssert("TopLeft item checked", () => (getMenuItemByText("TopLeft").Item as TernaryStateRadioMenuItem)?.State.Value == TernaryState.True);
|
||||
|
||||
AddStep("Right-click Centre anchor", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getMenuItemByText("Centre"));
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
|
||||
AddAssert("Centre item checked", () => (getMenuItemByText("Centre").Item as TernaryStateRadioMenuItem)?.State.Value == TernaryState.True);
|
||||
AddAssert("TopLeft item unchecked", () => (getMenuItemByText("TopLeft").Item as TernaryStateRadioMenuItem)?.State.Value == TernaryState.False);
|
||||
|
||||
AddStep("Right-click Closest anchor", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getMenuItemByText("Closest"));
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
|
||||
AddAssert("Closest item checked", () => (getMenuItemByText("Closest").Item as TernaryStateRadioMenuItem)?.State.Value == TernaryState.True);
|
||||
AddAssert("Centre item unchecked", () => (getMenuItemByText("Centre").Item as TernaryStateRadioMenuItem)?.State.Value == TernaryState.False);
|
||||
|
||||
Menu.DrawableMenuItem getMenuItemByText(string text)
|
||||
=> this.ChildrenOfType<Menu.DrawableMenuItem>().First(m => m.Item.Text.ToString() == text);
|
||||
}
|
||||
|
||||
private Skin importSkinFromArchives(string filename)
|
||||
{
|
||||
var imported = skins.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely();
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -61,6 +61,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("import beatmap", () =>
|
||||
{
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
Realm.Write(r =>
|
||||
{
|
||||
foreach (var beatmapInfo in r.All<BeatmapInfo>())
|
||||
beatmapInfo.OnlineMD5Hash = beatmapInfo.MD5Hash;
|
||||
});
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||
InitialBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0);
|
||||
OtherBeatmap = importedSet.Beatmaps.Last(b => b.Ruleset.OnlineID == 0);
|
||||
|
||||
@@ -13,6 +13,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
@@ -30,7 +31,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Cached]
|
||||
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
|
||||
|
||||
private DrawableLoungeRoom drawableRoom = null!;
|
||||
private LoungeRoomPanel panel = null!;
|
||||
private SearchTextBox searchTextBox = null!;
|
||||
|
||||
private readonly ManualResetEventSlim allowResponseCallback = new ManualResetEventSlim();
|
||||
@@ -40,14 +41,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
var mockLounge = new Mock<IOnlinePlayLounge>();
|
||||
mockLounge
|
||||
.Setup(l => l.Join(It.IsAny<Room>(), It.IsAny<string>(), It.IsAny<Action<Room>>(), It.IsAny<Action<string>>()))
|
||||
.Callback<Room, string, Action<Room>, Action<string>>((_, _, _, d) =>
|
||||
.Setup(l => l.Join(It.IsAny<Room>(), It.IsAny<string>(), It.IsAny<Action<Room>>(), It.IsAny<Action<string, Exception?>>()))
|
||||
.Callback<Room, string, Action<Room>, Action<string, Exception?>>((_, _, _, d) =>
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
allowResponseCallback.Wait(10000);
|
||||
allowResponseCallback.Reset();
|
||||
Schedule(() => d?.Invoke("Incorrect password"));
|
||||
Schedule(() => d?.Invoke("Incorrect password", new InvalidPasswordException()));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,7 +74,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Width = 500,
|
||||
Depth = float.MaxValue
|
||||
},
|
||||
drawableRoom = new DrawableLoungeRoom(room)
|
||||
panel = new LoungeRoomPanel(room)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@@ -87,16 +88,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestFocusViaKeyboardCommit()
|
||||
{
|
||||
DrawableLoungeRoom.PasswordEntryPopover? popover = null;
|
||||
LoungeRoomPanel.PasswordEntryPopover? popover = null;
|
||||
|
||||
AddAssert("search textbox has focus", () => checkFocus(searchTextBox));
|
||||
AddStep("click room twice", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(drawableRoom);
|
||||
InputManager.MoveMouseTo(panel);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("wait for popover", () => (popover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().SingleOrDefault()) != null);
|
||||
AddUntilStep("wait for popover", () => (popover = InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().SingleOrDefault()) != null);
|
||||
|
||||
AddAssert("textbox has focus", () => checkFocus(popover.ChildrenOfType<OsuPasswordTextBox>().Single()));
|
||||
|
||||
@@ -122,16 +123,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestFocusViaMouseCommit()
|
||||
{
|
||||
DrawableLoungeRoom.PasswordEntryPopover? popover = null;
|
||||
LoungeRoomPanel.PasswordEntryPopover? popover = null;
|
||||
|
||||
AddAssert("search textbox has focus", () => checkFocus(searchTextBox));
|
||||
AddStep("click room twice", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(drawableRoom);
|
||||
InputManager.MoveMouseTo(panel);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("wait for popover", () => (popover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().SingleOrDefault()) != null);
|
||||
AddUntilStep("wait for popover", () => (popover = InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().SingleOrDefault()) != null);
|
||||
|
||||
AddAssert("textbox has focus", () => checkFocus(popover.ChildrenOfType<OsuPasswordTextBox>().Single()));
|
||||
|
||||
|
||||
@@ -101,15 +101,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
PlaylistItem item = null!;
|
||||
|
||||
AddStep("reset state", () =>
|
||||
{
|
||||
multiplayerClient.Invocations.Clear();
|
||||
|
||||
beatmapAvailability.Value = BeatmapAvailability.LocallyAvailable();
|
||||
|
||||
item = new PlaylistItem(Beatmap.Value.BeatmapInfo)
|
||||
PlaylistItem item = new PlaylistItem(Beatmap.Value.BeatmapInfo)
|
||||
{
|
||||
RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID
|
||||
};
|
||||
@@ -127,7 +125,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
multiplayerRoom = new MultiplayerRoom(0)
|
||||
{
|
||||
Playlist = { TestMultiplayerClient.CreateMultiplayerPlaylistItem(item) },
|
||||
Playlist = { new MultiplayerPlaylistItem(item) },
|
||||
Users = { localUser },
|
||||
Host = localUser,
|
||||
};
|
||||
@@ -139,8 +137,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(250, 50),
|
||||
SelectedItem = new Bindable<PlaylistItem?>(item)
|
||||
Size = new Vector2(250, 50)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -81,6 +81,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("import beatmap", () =>
|
||||
{
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
Realm.Write(r =>
|
||||
{
|
||||
foreach (var beatmapInfo in r.All<BeatmapInfo>())
|
||||
beatmapInfo.OnlineMD5Hash = beatmapInfo.MD5Hash;
|
||||
});
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||
});
|
||||
|
||||
@@ -269,7 +274,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<RoomPanel>().Any());
|
||||
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("join room and immediately exit select", () =>
|
||||
@@ -298,7 +303,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<RoomPanel>().Any());
|
||||
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("join room", () => InputManager.Key(Key.Enter));
|
||||
@@ -349,13 +354,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<RoomPanel>().Any());
|
||||
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("join room", () => InputManager.Key(Key.Enter));
|
||||
|
||||
DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
LoungeRoomPanel.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
|
||||
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
|
||||
|
||||
@@ -802,7 +807,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<RoomPanel>().Any());
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
|
||||
AddStep("disable polling", () => this.ChildrenOfType<LoungeListingPoller>().Single().TimeBetweenPolls.Value = 0);
|
||||
@@ -924,7 +929,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 +961,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,
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
|
||||
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().Any());
|
||||
|
||||
AddAssert("textbox has focus", () => InputManager.FocusedDrawable is OsuPasswordTextBox);
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("textbox lost focus", () => InputManager.FocusedDrawable is SearchTextBox);
|
||||
|
||||
AddStep("hit escape", () => InputManager.Key(Key.Escape));
|
||||
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
|
||||
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().Any());
|
||||
|
||||
AddAssert("room not joined", () => !MultiplayerClient.RoomJoined);
|
||||
}
|
||||
@@ -65,9 +65,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
|
||||
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().Any());
|
||||
AddStep("exit screen", () => Stack.Exit());
|
||||
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
|
||||
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().Any());
|
||||
|
||||
AddAssert("room not joined", () => !MultiplayerClient.RoomJoined);
|
||||
}
|
||||
@@ -75,12 +75,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestJoinRoomWithIncorrectPasswordViaButton()
|
||||
{
|
||||
DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
LoungeRoomPanel.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
|
||||
createRooms(GenerateRooms(1, withPassword: true));
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "wrong");
|
||||
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
|
||||
|
||||
@@ -94,12 +94,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestJoinRoomWithIncorrectPasswordViaEnter()
|
||||
{
|
||||
DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
LoungeRoomPanel.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
|
||||
createRooms(GenerateRooms(1, withPassword: true));
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "wrong");
|
||||
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||
|
||||
@@ -113,12 +113,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestJoinRoomWithCorrectPassword()
|
||||
{
|
||||
DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
LoungeRoomPanel.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
|
||||
createRooms(GenerateRooms(1, withPassword: true));
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
|
||||
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
|
||||
|
||||
@@ -128,12 +128,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestJoinRoomWithPasswordViaKeyboardOnly()
|
||||
{
|
||||
DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
LoungeRoomPanel.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
|
||||
createRooms(GenerateRooms(1, withPassword: true));
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
|
||||
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
@@ -18,22 +18,33 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("create footer", () =>
|
||||
{
|
||||
Child = new PopoverContainer
|
||||
MultiplayerBeatmapAvailabilityTracker tracker = new MultiplayerBeatmapAvailabilityTracker();
|
||||
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 50,
|
||||
Child = new MultiplayerMatchFooter
|
||||
CachedDependencies =
|
||||
[
|
||||
(typeof(OnlinePlayBeatmapAvailabilityTracker), tracker)
|
||||
],
|
||||
Children =
|
||||
[
|
||||
tracker,
|
||||
new PopoverContainer
|
||||
{
|
||||
SelectedItem = new Bindable<PlaylistItem?>()
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 50,
|
||||
Child = new MultiplayerMatchFooter()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -330,10 +330,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddUntilStep("wait for join", () => RoomJoined);
|
||||
|
||||
AddUntilStep("button visible", () => this.ChildrenOfType<DrawableMatchRoom>().Single().ChangeSettingsButton?.Alpha, () => Is.GreaterThan(0));
|
||||
AddUntilStep("button visible", () => this.ChildrenOfType<MultiplayerRoomPanel>().Single().ChangeSettingsButton.Alpha, () => Is.GreaterThan(0));
|
||||
AddStep("join other user", void () => MultiplayerClient.AddUser(new APIUser { Id = PLAYER_1_ID }));
|
||||
AddStep("make other user host", () => MultiplayerClient.TransferHost(PLAYER_1_ID));
|
||||
AddAssert("button hidden", () => this.ChildrenOfType<DrawableMatchRoom>().Single().ChangeSettingsButton?.Alpha, () => Is.EqualTo(0));
|
||||
AddAssert("button hidden", () => this.ChildrenOfType<MultiplayerRoomPanel>().Single().ChangeSettingsButton.Alpha, () => Is.EqualTo(0));
|
||||
}
|
||||
|
||||
private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Platform;
|
||||
@@ -32,7 +31,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private BeatmapManager beatmaps = null!;
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
private BeatmapInfo importedBeatmap = null!;
|
||||
private Room room = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
@@ -47,19 +45,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("create room", () => room = CreateDefaultRoom());
|
||||
AddStep("join room", () => JoinRoom(room));
|
||||
AddStep("join room", () => JoinRoom(CreateDefaultRoom()));
|
||||
WaitForJoined();
|
||||
|
||||
AddStep("create list", () =>
|
||||
{
|
||||
Child = list = new MultiplayerPlaylist(room)
|
||||
Child = list = new MultiplayerPlaylist
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.4f, 0.8f),
|
||||
SelectedItem = new Bindable<PlaylistItem?>()
|
||||
Size = new Vector2(0.4f, 0.8f)
|
||||
};
|
||||
});
|
||||
|
||||
@@ -158,37 +154,36 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
assertQueueTabCount(0);
|
||||
}
|
||||
|
||||
[Ignore("Expired items are initially removed from the room.")]
|
||||
[Test]
|
||||
public void TestJoinRoomWithMixedItemsAddedInCorrectLists()
|
||||
{
|
||||
AddStep("leave room", () => MultiplayerClient.LeaveRoom());
|
||||
AddUntilStep("wait for room part", () => !RoomJoined);
|
||||
|
||||
AddStep("join room with items", () =>
|
||||
AddStep("join room with expired items", () =>
|
||||
{
|
||||
API.Queue(new CreateRoomRequest(new Room
|
||||
{
|
||||
Name = "test name",
|
||||
Playlist =
|
||||
[
|
||||
new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo)
|
||||
{
|
||||
RulesetID = Ruleset.Value.OnlineID
|
||||
},
|
||||
new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo)
|
||||
{
|
||||
RulesetID = Ruleset.Value.OnlineID,
|
||||
Expired = true
|
||||
}
|
||||
]
|
||||
}));
|
||||
Room room = CreateDefaultRoom();
|
||||
room.Playlist =
|
||||
[
|
||||
new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo)
|
||||
{
|
||||
RulesetID = Ruleset.Value.OnlineID
|
||||
},
|
||||
new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo)
|
||||
{
|
||||
RulesetID = Ruleset.Value.OnlineID,
|
||||
Expired = true
|
||||
}
|
||||
];
|
||||
|
||||
JoinRoom(room);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for room join", () => RoomJoined);
|
||||
WaitForJoined();
|
||||
|
||||
assertItemInQueueListStep(1, 0);
|
||||
assertItemInHistoryListStep(2, 0);
|
||||
// IDs are offset by 1 because we've joined two rooms in this test.
|
||||
assertItemInQueueListStep(2, 0);
|
||||
assertItemInHistoryListStep(3, 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -220,7 +215,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
|
||||
|
||||
@@ -49,13 +49,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("create playlist", () =>
|
||||
{
|
||||
Child = playlist = new MultiplayerQueueList(room)
|
||||
Child = playlist = new MultiplayerQueueList
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300),
|
||||
Size = new Vector2(500, 300)
|
||||
};
|
||||
|
||||
playlist.Items.ReplaceRange(0, playlist.Items.Count, MultiplayerClient.ClientAPIRoom!.Playlist);
|
||||
|
||||
MultiplayerClient.ClientAPIRoom!.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(Room.Playlist))
|
||||
@@ -132,13 +134,25 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
assertDeleteButtonVisibility(1, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangeExistingItem()
|
||||
{
|
||||
AddStep("change beatmap", () => MultiplayerClient.EditPlaylistItem(new MultiplayerPlaylistItem
|
||||
{
|
||||
ID = playlist.Items[0].ID,
|
||||
BeatmapID = 1337
|
||||
}).WaitSafely());
|
||||
|
||||
AddUntilStep("first playlist item has new beatmap", () => playlist.Items[0].Beatmap.OnlineID, () => Is.EqualTo(1337));
|
||||
}
|
||||
|
||||
private void addPlaylistItem(Func<int> userId)
|
||||
{
|
||||
long itemId = -1;
|
||||
|
||||
AddStep("add playlist item", () =>
|
||||
{
|
||||
MultiplayerPlaylistItem item = TestMultiplayerClient.CreateMultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
|
||||
MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
|
||||
|
||||
MultiplayerClient.AddUserPlaylistItem(userId(), item).WaitSafely();
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@@ -18,6 +17,8 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
@@ -53,36 +54,46 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("create button", () =>
|
||||
{
|
||||
AvailabilityTracker.SelectedItem.Value = room.Playlist.First();
|
||||
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
|
||||
|
||||
Child = new PopoverContainer
|
||||
MultiplayerBeatmapAvailabilityTracker tracker = new MultiplayerBeatmapAvailabilityTracker();
|
||||
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
CachedDependencies =
|
||||
[
|
||||
(typeof(OnlinePlayBeatmapAvailabilityTracker), tracker)
|
||||
],
|
||||
Children =
|
||||
[
|
||||
tracker,
|
||||
new PopoverContainer
|
||||
{
|
||||
spectateButton = new MultiplayerSpectateButton
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200, 50),
|
||||
SelectedItem = new Bindable<PlaylistItem?>(room.Playlist.First())
|
||||
},
|
||||
startControl = new MatchStartControl
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200, 50),
|
||||
SelectedItem = new Bindable<PlaylistItem?>(room.Playlist.First())
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
spectateButton = new MultiplayerSpectateButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200, 50)
|
||||
},
|
||||
startControl = new MatchStartControl
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200, 50)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// 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.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestSceneMultiplayerUserModDisplay : MultiplayerTestScene
|
||||
{
|
||||
private MultiplayerUserModDisplay modDisplay = null!;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("join room", () => JoinRoom(CreateDefaultRoom()));
|
||||
WaitForJoined();
|
||||
|
||||
AddStep("add display", () => Child = modDisplay = new MultiplayerUserModDisplay
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangeMods()
|
||||
{
|
||||
AddStep("set DT", () => MultiplayerClient.ChangeUserMods([new OsuModDoubleTime()]).WaitSafely());
|
||||
AddUntilStep("mod displayed", () => modDisplay.ChildrenOfType<ModIcon>().Count() == 1);
|
||||
|
||||
AddStep("set DT, HR", () => MultiplayerClient.ChangeUserMods([new OsuModDoubleTime(), new OsuModHardRock()]).WaitSafely());
|
||||
AddUntilStep("mods displayed", () => modDisplay.ChildrenOfType<ModIcon>().Count() == 2);
|
||||
|
||||
AddStep("set no mods", () => MultiplayerClient.ChangeUserMods(Enumerable.Empty<APIMod>()).WaitSafely());
|
||||
AddUntilStep("no mods displayed", () => !modDisplay.ChildrenOfType<ModIcon>().Any());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
@@ -199,25 +198,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("add rooms", () => rooms.AddRange(GenerateRooms(3, withPassword: true)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFreestyleBypassesRulesetFilter()
|
||||
{
|
||||
AddStep("apply taiko filter", () => container.Filter.Value = new FilterCriteria { Ruleset = new TaikoRuleset().RulesetInfo });
|
||||
|
||||
AddStep("add osu + freestyle room", () =>
|
||||
{
|
||||
var room = GenerateRooms(1, new OsuRuleset().RulesetInfo)[0];
|
||||
room.Playlist[0].Freestyle = true;
|
||||
room.CurrentPlaylistItem = room.Playlist[0];
|
||||
rooms.Add(room);
|
||||
});
|
||||
|
||||
AddAssert("room visible", () => container.DrawableRooms.Any());
|
||||
}
|
||||
|
||||
private bool checkRoomSelected(Room? room) => selectedRoom.Value == room;
|
||||
|
||||
private Room? getRoomInFlow(int index) =>
|
||||
(container.ChildrenOfType<FillFlowContainer<DrawableLoungeRoom>>().First().FlowingChildren.ElementAt(index) as DrawableRoom)?.Room;
|
||||
(container.ChildrenOfType<FillFlowContainer<LoungeRoomPanel>>().First().FlowingChildren.ElementAt(index) as RoomPanel)?.Room;
|
||||
}
|
||||
}
|
||||
|
||||
+16
-25
@@ -18,13 +18,13 @@ using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestSceneDrawableRoom : OsuTestScene
|
||||
public partial class TestSceneRoomPanel : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||
@@ -129,24 +129,24 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestEnableAndDisablePassword()
|
||||
{
|
||||
DrawableRoom drawableRoom = null!;
|
||||
RoomPanel panel = null!;
|
||||
Room room = null!;
|
||||
|
||||
AddStep("create room", () => Child = drawableRoom = createLoungeRoom(room = new Room
|
||||
AddStep("create room", () => Child = panel = createLoungeRoom(room = new Room
|
||||
{
|
||||
Name = "Room with password",
|
||||
Type = MatchType.HeadToHead,
|
||||
}));
|
||||
|
||||
AddUntilStep("wait for panel load", () => drawableRoom.ChildrenOfType<DrawableRoomParticipantsList>().Any());
|
||||
AddUntilStep("wait for panel load", () => panel.ChildrenOfType<DrawableRoomParticipantsList>().Any());
|
||||
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, panel.ChildrenOfType<RoomPanel.PasswordProtectedIcon>().Single().Alpha));
|
||||
|
||||
AddStep("set password", () => room.Password = "password");
|
||||
AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||
AddAssert("password icon visible", () => Precision.AlmostEquals(1, panel.ChildrenOfType<RoomPanel.PasswordProtectedIcon>().Single().Alpha));
|
||||
|
||||
AddStep("unset password", () => room.Password = string.Empty);
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, panel.ChildrenOfType<RoomPanel.PasswordProtectedIcon>().Single().Alpha));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -160,38 +160,29 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Spacing = new Vector2(5),
|
||||
Children = new[]
|
||||
{
|
||||
new DrawableMatchRoom(new Room
|
||||
new MultiplayerRoomPanel(new Room
|
||||
{
|
||||
Name = "A host-only room",
|
||||
QueueMode = QueueMode.HostOnly,
|
||||
Type = MatchType.HeadToHead,
|
||||
})
|
||||
{
|
||||
SelectedItem = new Bindable<PlaylistItem?>()
|
||||
},
|
||||
new DrawableMatchRoom(new Room
|
||||
}),
|
||||
new MultiplayerRoomPanel(new Room
|
||||
{
|
||||
Name = "An all-players, team-versus room",
|
||||
QueueMode = QueueMode.AllPlayers,
|
||||
Type = MatchType.TeamVersus
|
||||
})
|
||||
{
|
||||
SelectedItem = new Bindable<PlaylistItem?>()
|
||||
},
|
||||
new DrawableMatchRoom(new Room
|
||||
}),
|
||||
new MultiplayerRoomPanel(new Room
|
||||
{
|
||||
Name = "A round-robin room",
|
||||
QueueMode = QueueMode.AllPlayersRoundRobin,
|
||||
Type = MatchType.HeadToHead
|
||||
})
|
||||
{
|
||||
SelectedItem = new Bindable<PlaylistItem?>()
|
||||
},
|
||||
}),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private DrawableRoom createLoungeRoom(Room room)
|
||||
private RoomPanel createLoungeRoom(Room room)
|
||||
{
|
||||
room.Host ??= new APIUser { Username = "peppy", Id = 2 };
|
||||
|
||||
@@ -204,7 +195,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
return new DrawableLoungeRoom(room)
|
||||
return new LoungeRoomPanel(room)
|
||||
{
|
||||
MatchingFilter = true,
|
||||
SelectedRoom = selectedRoom
|
||||
@@ -99,8 +99,35 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
|
||||
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
|
||||
},
|
||||
TopTags =
|
||||
[
|
||||
new APIBeatmapTag { TagId = 4, VoteCount = 1 },
|
||||
new APIBeatmapTag { TagId = 2, VoteCount = 1 },
|
||||
new APIBeatmapTag { TagId = 23, VoteCount = 5 },
|
||||
],
|
||||
},
|
||||
},
|
||||
RelatedTags =
|
||||
[
|
||||
new APITag
|
||||
{
|
||||
Id = 2,
|
||||
Name = "song representation/simple",
|
||||
Description = "Accessible and straightforward map design."
|
||||
},
|
||||
new APITag
|
||||
{
|
||||
Id = 4,
|
||||
Name = "style/clean",
|
||||
Description = "Visually uncluttered and organised patterns, often involving few overlaps and equal visual spacing between objects."
|
||||
},
|
||||
new APITag
|
||||
{
|
||||
Id = 23,
|
||||
Name = "aim/aim control",
|
||||
Description = "Patterns with velocity or direction changes which strongly go against a player's natural movement pattern."
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
@@ -289,12 +316,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 +388,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 +427,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 +443,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();
|
||||
|
||||
@@ -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,52 @@
|
||||
// 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;
|
||||
using osu.Game.Overlays.Comments;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExternalImageLinkInComments()
|
||||
{
|
||||
MarkdownContainer markdown = null!;
|
||||
|
||||
AddStep("load external with proxying", () => Child = markdown = new CommentMarkdownContainer
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@@ -61,9 +61,9 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
AddUntilStep("last room is not masked", () => checkRoomVisible(roomListing.DrawableRooms[^1]));
|
||||
}
|
||||
|
||||
private bool checkRoomVisible(DrawableRoom room) =>
|
||||
private bool checkRoomVisible(RoomPanel panel) =>
|
||||
loungeScreen.ChildrenOfType<OsuScrollContainer>().First().ScreenSpaceDrawQuad
|
||||
.Contains(room.ScreenSpaceDrawQuad.Centre);
|
||||
.Contains(panel.ScreenSpaceDrawQuad.Centre);
|
||||
|
||||
private void createRooms(params Room[] rooms)
|
||||
{
|
||||
|
||||
@@ -176,6 +176,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
|
||||
}
|
||||
];
|
||||
room.EndDate = DateTimeOffset.Now.AddHours(1);
|
||||
});
|
||||
|
||||
AddAssert("match has default beatmap", () => match.Beatmap.IsDefault);
|
||||
@@ -212,6 +213,11 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null);
|
||||
importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet)!.Value.Detach();
|
||||
Realm.Write(r =>
|
||||
{
|
||||
foreach (var beatmapInfo in r.All<BeatmapInfo>())
|
||||
beatmapInfo.OnlineMD5Hash = beatmapInfo.MD5Hash;
|
||||
});
|
||||
});
|
||||
|
||||
private partial class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen
|
||||
|
||||
@@ -65,7 +65,8 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
OnlineID = 1,
|
||||
DifficultyName = "Osu 1",
|
||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = "11111111",
|
||||
OnlineMD5Hash = "11111111",
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
Metadata =
|
||||
{
|
||||
@@ -79,7 +80,8 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
OnlineID = 2,
|
||||
DifficultyName = "Osu 2",
|
||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = "22222222",
|
||||
OnlineMD5Hash = "22222222",
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
Metadata =
|
||||
{
|
||||
@@ -93,7 +95,8 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
OnlineID = 3,
|
||||
DifficultyName = "Taiko 1",
|
||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = "33333333",
|
||||
OnlineMD5Hash = "33333333",
|
||||
Ruleset = new TaikoRuleset().RulesetInfo,
|
||||
Metadata =
|
||||
{
|
||||
@@ -107,7 +110,8 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
OnlineID = 4,
|
||||
DifficultyName = "Taiko 2",
|
||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = "44444444",
|
||||
OnlineMD5Hash = "44444444",
|
||||
Ruleset = new TaikoRuleset().RulesetInfo,
|
||||
Metadata =
|
||||
{
|
||||
|
||||
@@ -105,6 +105,40 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
displayUpdate(statistics, statistics);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFromNothing()
|
||||
{
|
||||
createDisplay();
|
||||
displayUpdate(
|
||||
new UserStatistics(),
|
||||
new UserStatistics
|
||||
{
|
||||
GlobalRank = 12_345,
|
||||
Accuracy = 98.99,
|
||||
MaxCombo = 2_322,
|
||||
RankedScore = 23_123_543_456,
|
||||
TotalScore = 123_123_543_456,
|
||||
PP = 5_072
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToNothing()
|
||||
{
|
||||
createDisplay();
|
||||
displayUpdate(
|
||||
new UserStatistics
|
||||
{
|
||||
GlobalRank = 12_345,
|
||||
Accuracy = 98.99,
|
||||
MaxCombo = 2_322,
|
||||
RankedScore = 23_123_543_456,
|
||||
TotalScore = 123_123_543_456,
|
||||
PP = 5_072
|
||||
},
|
||||
new UserStatistics());
|
||||
}
|
||||
|
||||
private void createDisplay() => AddStep("create display", () =>
|
||||
{
|
||||
statisticsUpdate.Value = null;
|
||||
|
||||
@@ -8,11 +8,15 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
@@ -24,8 +28,10 @@ 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.Mania;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@@ -42,6 +48,22 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||
|
||||
private ScoreManager scoreManager = null!;
|
||||
private RulesetStore rulesetStore = null!;
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm));
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, API));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScoreWithPositionStatistics()
|
||||
{
|
||||
@@ -162,6 +184,24 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
|
||||
setUpTaggingRequests(() => score.BeatmapInfo);
|
||||
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 setUpTaggingRequests(Func<BeatmapInfo> beatmap) =>
|
||||
AddStep("set up network requests", () =>
|
||||
{
|
||||
dummyAPI.HandleRequest = request =>
|
||||
@@ -175,7 +215,11 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
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 = 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.", },
|
||||
]
|
||||
@@ -185,7 +229,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
|
||||
case GetBeatmapSetRequest getBeatmapSetRequest:
|
||||
{
|
||||
var beatmapSet = CreateAPIBeatmapSet(score.BeatmapInfo);
|
||||
var beatmapSet = CreateAPIBeatmapSet(beatmap.Invoke());
|
||||
beatmapSet.Beatmaps.Single().TopTags =
|
||||
[
|
||||
new APIBeatmapTag { TagId = 3, VoteCount = 9 },
|
||||
@@ -205,21 +249,6 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
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()
|
||||
@@ -243,6 +272,100 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTaggingConvert()
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.Ruleset = new ManiaRuleset().RulesetInfo;
|
||||
|
||||
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 TestTaggingInteractionWithLocalScores()
|
||||
{
|
||||
BeatmapInfo beatmapInfo = null!;
|
||||
|
||||
AddStep(@"Import beatmap", () =>
|
||||
{
|
||||
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
|
||||
});
|
||||
|
||||
AddStep("import bad score", () =>
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.BeatmapInfo = beatmapInfo;
|
||||
score.BeatmapHash = beatmapInfo.Hash;
|
||||
score.Ruleset = beatmapInfo.Ruleset;
|
||||
score.Rank = ScoreRank.D;
|
||||
score.User = API.LocalUser.Value;
|
||||
scoreManager.Import(score);
|
||||
});
|
||||
|
||||
AddStep("import score by another user", () =>
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.BeatmapInfo = beatmapInfo;
|
||||
score.BeatmapHash = beatmapInfo.Hash;
|
||||
score.Ruleset = beatmapInfo.Ruleset;
|
||||
score.Rank = ScoreRank.D;
|
||||
score.User = new APIUser { Username = "notme", Id = 5678 };
|
||||
scoreManager.Import(score);
|
||||
});
|
||||
|
||||
AddStep("import convert score", () =>
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.BeatmapInfo = beatmapInfo;
|
||||
score.BeatmapHash = beatmapInfo.Hash;
|
||||
score.Ruleset = new OsuRuleset().RulesetInfo;
|
||||
score.User = API.LocalUser.Value;
|
||||
scoreManager.Import(score);
|
||||
});
|
||||
|
||||
AddStep("import correct score", () =>
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.BeatmapInfo = beatmapInfo;
|
||||
score.BeatmapHash = beatmapInfo.Hash;
|
||||
score.Ruleset = beatmapInfo.Ruleset;
|
||||
score.User = API.LocalUser.Value;
|
||||
scoreManager.Import(score);
|
||||
});
|
||||
|
||||
setUpTaggingRequests(() => beatmapInfo);
|
||||
AddStep("load panel", () =>
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.BeatmapInfo = beatmapInfo;
|
||||
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new StatisticsPanel
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
Score = { Value = score },
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private void loadPanel(ScoreInfo score) => AddStep("load panel", () =>
|
||||
{
|
||||
Child = new StatisticsPanel
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
private BeatmapCarousel carousel = null!;
|
||||
|
||||
private TestSceneOnlinePlayBeatmapAvailabilityTracker.TestBeatmapModelDownloader beatmapDownloader = null!;
|
||||
private TestScenePlaylistsBeatmapAvailabilityTracker.TestBeatmapModelDownloader beatmapDownloader = null!;
|
||||
|
||||
private BeatmapSetInfo testBeatmapSetInfo = null!;
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
var importer = parent.Get<BeatmapManager>();
|
||||
|
||||
dependencies.CacheAs<BeatmapModelDownloader>(beatmapDownloader = new TestSceneOnlinePlayBeatmapAvailabilityTracker.TestBeatmapModelDownloader(importer, API));
|
||||
dependencies.CacheAs<BeatmapModelDownloader>(beatmapDownloader = new TestScenePlaylistsBeatmapAvailabilityTracker.TestBeatmapModelDownloader(importer, API));
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddUntilStep("progress download to completion", () =>
|
||||
{
|
||||
if (downloadRequest is TestSceneOnlinePlayBeatmapAvailabilityTracker.TestDownloadRequest testRequest)
|
||||
if (downloadRequest is TestScenePlaylistsBeatmapAvailabilityTracker.TestDownloadRequest testRequest)
|
||||
{
|
||||
testRequest.SetProgress(testRequest.Progress + 0.1f);
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddUntilStep("progress download to failure", () =>
|
||||
{
|
||||
if (downloadRequest is TestSceneOnlinePlayBeatmapAvailabilityTracker.TestDownloadRequest testRequest)
|
||||
if (downloadRequest is TestScenePlaylistsBeatmapAvailabilityTracker.TestDownloadRequest testRequest)
|
||||
{
|
||||
testRequest.SetProgress(testRequest.Progress + 0.1f);
|
||||
|
||||
@@ -226,7 +226,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddUntilStep("progress download to completion", () =>
|
||||
{
|
||||
if (downloadRequest is TestSceneOnlinePlayBeatmapAvailabilityTracker.TestDownloadRequest testRequest)
|
||||
if (downloadRequest is TestScenePlaylistsBeatmapAvailabilityTracker.TestDownloadRequest testRequest)
|
||||
{
|
||||
testRequest.SetProgress(testRequest.Progress + 0.1f);
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -1003,6 +1003,35 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddAssert("search still not focused", () => !this.ChildrenOfType<ShearedSearchTextBox>().Single().HasFocus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that recreating the mod panels (by setting the global available mods) also refreshes the active states.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestActiveStatesRefreshedOnPanelsCreated()
|
||||
{
|
||||
createScreen();
|
||||
changeRuleset(0);
|
||||
|
||||
Bindable<IReadOnlyList<Mod>> selectedMods = null!;
|
||||
|
||||
AddStep("bind mods to local bindable", () =>
|
||||
{
|
||||
selectedMods = new Bindable<IReadOnlyList<Mod>>([]);
|
||||
|
||||
modSelectOverlay.SelectedMods.UnbindFrom(SelectedMods);
|
||||
modSelectOverlay.SelectedMods.BindTo(selectedMods);
|
||||
});
|
||||
|
||||
AddStep("activate PF", () => selectedMods.Value = [new OsuModPerfect()]);
|
||||
AddAssert("OsuModPerfect panel active", () => getPanelForMod(typeof(OsuModPerfect)).Active.Value);
|
||||
|
||||
changeRuleset(1);
|
||||
AddAssert("TaikoModPerfect panel not active", () => !getPanelForMod(typeof(TaikoModPerfect)).Active.Value);
|
||||
|
||||
changeRuleset(0);
|
||||
AddAssert("OsuModPerfect panel active", () => getPanelForMod(typeof(OsuModPerfect)).Active.Value);
|
||||
}
|
||||
|
||||
private void waitForColumnLoad() => AddUntilStep("all column content loaded", () =>
|
||||
modSelectOverlay.ChildrenOfType<ModColumn>().Any()
|
||||
&& modSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded)
|
||||
@@ -1018,7 +1047,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));
|
||||
}
|
||||
|
||||
@@ -196,6 +196,37 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddAssert("external overlay content still not shown", () => this.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent, () => Is.Not.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonResizedAfterFooterIsDisplayed()
|
||||
{
|
||||
TestShearedOverlayContainer externalOverlay = null!;
|
||||
|
||||
AddStep("add overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
|
||||
AddStep("set buttons", () => screenFooter.SetButtons(new[]
|
||||
{
|
||||
new ScreenFooterButton(externalOverlay)
|
||||
{
|
||||
AccentColour = Dependencies.Get<OsuColour>().Orange1,
|
||||
Icon = FontAwesome.Solid.Toolbox,
|
||||
Text = "One",
|
||||
},
|
||||
new ScreenFooterButton { Text = "Two", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Three", Action = () => { } },
|
||||
}));
|
||||
AddWaitStep("wait for transition", 3);
|
||||
|
||||
AddStep("show overlay", () => externalOverlay.Show());
|
||||
AddAssert("content displayed in footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().Single().IsPresent);
|
||||
AddUntilStep("other buttons hidden", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.Child.Parent!.Y > 0));
|
||||
|
||||
AddStep("resize active button", () => this.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(240, 300, Easing.OutQuint));
|
||||
AddStep("resize active button back", () => this.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(116, 300, Easing.OutQuint));
|
||||
|
||||
AddStep("hide overlay", () => externalOverlay.Hide());
|
||||
AddUntilStep("content hidden from footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent != true);
|
||||
AddUntilStep("other buttons returned", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.ChildrenOfType<Container>().First().Y == 0));
|
||||
}
|
||||
|
||||
private partial class TestShearedOverlayContainer : ShearedOverlayContainer
|
||||
{
|
||||
public TestShearedOverlayContainer()
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneShearedDropdown : ThemeComparisonTestScene
|
||||
{
|
||||
public TestSceneShearedDropdown()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent() => new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Black.Opacity(0.75f),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new ShearedDropdown<string>("Test")
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Y = 300f,
|
||||
Width = 140,
|
||||
Current = new Bindable<string>(),
|
||||
Items = new[] { "Global", "Friends", "Local", "Really lonnnnnnng option" },
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -298,7 +298,21 @@ namespace osu.Game.Beatmaps
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||
public BeatmapInfo? QueryBeatmap(Expression<Func<BeatmapInfo, bool>> query) => Realm.Run(r =>
|
||||
r.All<BeatmapInfo>().Filter($"{nameof(BeatmapInfo.BeatmapSet)}.{nameof(BeatmapSetInfo.DeletePending)} == false").FirstOrDefault(query)?.Detach());
|
||||
r.All<BeatmapInfo>().Filter($@"{nameof(BeatmapInfo.BeatmapSet)}.{nameof(BeatmapSetInfo.DeletePending)} == false").FirstOrDefault(query)?.Detach());
|
||||
|
||||
/// <summary>
|
||||
/// Perform a lookup query on available <see cref="BeatmapInfo"/>s.
|
||||
/// Use this overload instead of <see cref="QueryBeatmap(System.Linq.Expressions.Expression{System.Func{osu.Game.Beatmaps.BeatmapInfo,bool}})"/>
|
||||
/// when Realm is unable to transform an expression to the internal Realm query syntax.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <param name="arguments">The arguments for the query.</param>
|
||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||
public BeatmapInfo? QueryBeatmap(string query, params QueryArgument[] arguments) => Realm.Run(r =>
|
||||
r.All<BeatmapInfo>()
|
||||
.Filter($@"{nameof(BeatmapInfo.BeatmapSet)}.{nameof(BeatmapSetInfo.DeletePending)} == false")
|
||||
.Filter(query, arguments)
|
||||
.FirstOrDefault()?.Detach());
|
||||
|
||||
/// <summary>
|
||||
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
|
||||
|
||||
@@ -36,11 +36,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
Origin = Anchor.CentreLeft,
|
||||
TextSize = 13f
|
||||
},
|
||||
new DifficultySpectrumDisplay(beatmapSet)
|
||||
new DifficultySpectrumDisplay
|
||||
{
|
||||
BeatmapSet = beatmapSet,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
DotSize = new Vector2(5, 10)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -43,61 +43,43 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||
}
|
||||
}
|
||||
|
||||
private float iconSize;
|
||||
protected SpriteIcon Icon { get; private set; } = null!;
|
||||
|
||||
public float IconSize
|
||||
private Container content = null!;
|
||||
private Container hover = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
get => iconSize;
|
||||
set
|
||||
{
|
||||
iconSize = value;
|
||||
Icon.Size = new Vector2(iconSize);
|
||||
}
|
||||
}
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
protected readonly SpriteIcon Icon;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
private readonly Container content;
|
||||
private readonly Box hover;
|
||||
|
||||
protected BeatmapCardIconButton()
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
Anchor = Anchor.Centre;
|
||||
|
||||
base.Content.Add(content = new Container
|
||||
Add(content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
||||
Scale = new Vector2(0.8f),
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
hover = new Box
|
||||
hover = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
||||
Masking = true,
|
||||
Colour = Color4.White.Opacity(0.1f),
|
||||
Blending = BlendingParameters.Additive,
|
||||
Child = new Box { RelativeSizeAxes = Axes.Both, }
|
||||
},
|
||||
Icon = new SpriteIcon
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Scale = new Vector2(1.2f),
|
||||
Size = new Vector2(14),
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
IconSize = 12;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
IdleColour = colourProvider.Light1;
|
||||
HoverColour = colourProvider.Content1;
|
||||
}
|
||||
@@ -127,8 +109,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||
bool isHovered = IsHovered && Enabled.Value;
|
||||
|
||||
hover.FadeTo(isHovered ? 1f : 0f, 500, Easing.OutQuint);
|
||||
content.ScaleTo(isHovered ? 1 : 0.8f, 500, Easing.OutQuint);
|
||||
content.ScaleTo(isHovered ? 0.9f : 0.8f, 500, Easing.OutQuint);
|
||||
Icon.FadeColour(isHovered ? HoverColour : IdleColour, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected void SetLoading(bool isLoading)
|
||||
{
|
||||
Icon.FadeTo(isLoading ? 0.2f : 1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||
Enabled.Value = !isLoading;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,8 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||
{
|
||||
@@ -23,17 +21,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||
|
||||
private Bindable<bool> preferNoVideo = null!;
|
||||
|
||||
private readonly LoadingSpinner spinner;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapModelDownloader beatmaps { get; set; } = null!;
|
||||
|
||||
public DownloadButton(APIBeatmapSet beatmapSet)
|
||||
{
|
||||
Icon.Icon = FontAwesome.Solid.Download;
|
||||
|
||||
Content.Add(spinner = new LoadingSpinner { Size = new Vector2(IconSize) });
|
||||
|
||||
this.beatmapSet = beatmapSet;
|
||||
}
|
||||
|
||||
@@ -41,6 +33,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
preferNoVideo = config.GetBindable<bool>(OsuSetting.PreferNoVideo);
|
||||
Icon.Icon = FontAwesome.Solid.Download;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -64,8 +57,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||
case DownloadState.Importing:
|
||||
Action = null;
|
||||
TooltipText = string.Empty;
|
||||
spinner.Show();
|
||||
Icon.Hide();
|
||||
SetLoading(true);
|
||||
break;
|
||||
|
||||
case DownloadState.LocallyAvailable:
|
||||
@@ -84,8 +76,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||
|
||||
Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value);
|
||||
this.FadeIn(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||
spinner.Hide();
|
||||
Icon.Show();
|
||||
SetLoading(false);
|
||||
|
||||
if (!beatmapSet.HasVideo)
|
||||
TooltipText = BeatmapsetsStrings.PanelDownloadAll;
|
||||
|
||||
@@ -53,19 +53,20 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||
favouriteRequest?.Cancel();
|
||||
favouriteRequest = new PostBeatmapFavouriteRequest(beatmapSet.OnlineID, actionType);
|
||||
|
||||
Enabled.Value = false;
|
||||
SetLoading(true);
|
||||
|
||||
favouriteRequest.Success += () =>
|
||||
{
|
||||
bool favourited = actionType == BeatmapFavouriteAction.Favourite;
|
||||
|
||||
current.Value = new BeatmapSetFavouriteState(favourited, current.Value.FavouriteCount + (favourited ? 1 : -1));
|
||||
|
||||
Enabled.Value = true;
|
||||
SetLoading(false);
|
||||
};
|
||||
favouriteRequest.Failure += e =>
|
||||
{
|
||||
Logger.Error(e, $"Failed to {actionType.ToString().ToLowerInvariant()} beatmap: {e.Message}");
|
||||
Enabled.Value = true;
|
||||
SetLoading(false);
|
||||
};
|
||||
|
||||
api.Queue(favouriteRequest);
|
||||
|
||||
@@ -20,15 +20,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
||||
public GoToBeatmapButton(APIBeatmapSet beatmapSet)
|
||||
{
|
||||
this.beatmapSet = beatmapSet;
|
||||
|
||||
Icon.Icon = FontAwesome.Solid.AngleDoubleRight;
|
||||
TooltipText = "Go to beatmap";
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuGame? game)
|
||||
{
|
||||
Action = () => game?.PresentBeatmap(beatmapSet);
|
||||
Icon.Icon = FontAwesome.Solid.AngleDoubleRight;
|
||||
TooltipText = "Go to beatmap";
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
||||
@@ -95,9 +95,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
Child = buttons = new Container<BeatmapCardIconButton>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
// Padding of 4 avoids touching the card borders when in the expanded (ie. showing difficulties) state.
|
||||
// Left override allows the buttons to visually be wider and look better.
|
||||
Padding = new MarginPadding(4) { Left = 2 },
|
||||
Children = new BeatmapCardIconButton[]
|
||||
{
|
||||
new FavouriteButton(beatmapSet)
|
||||
@@ -106,7 +103,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.48f,
|
||||
Height = 0.5f,
|
||||
},
|
||||
new DownloadButton(beatmapSet)
|
||||
{
|
||||
@@ -114,7 +111,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
Origin = Anchor.BottomCentre,
|
||||
State = { BindTarget = downloadTracker.State },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.48f,
|
||||
Height = 0.5f,
|
||||
},
|
||||
new GoToBeatmapButton(beatmapSet)
|
||||
{
|
||||
@@ -122,7 +119,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
Origin = Anchor.BottomCentre,
|
||||
State = { BindTarget = downloadTracker.State },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.48f,
|
||||
Height = 0.5f,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
@@ -18,34 +17,6 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public partial class DifficultySpectrumDisplay : CompositeDrawable
|
||||
{
|
||||
private Vector2 dotSize = new Vector2(4, 8);
|
||||
|
||||
public Vector2 DotSize
|
||||
{
|
||||
get => dotSize;
|
||||
set
|
||||
{
|
||||
dotSize = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private float dotSpacing = 1;
|
||||
|
||||
public float DotSpacing
|
||||
{
|
||||
get => dotSpacing;
|
||||
set
|
||||
{
|
||||
dotSpacing = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private IBeatmapSetInfo? beatmapSet;
|
||||
|
||||
public IBeatmapSetInfo? BeatmapSet
|
||||
@@ -60,9 +31,12 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
private readonly FillFlowContainer<RulesetDifficultyGroup> flow;
|
||||
private FillFlowContainer<RulesetDifficultyGroup> flow = null!;
|
||||
|
||||
public DifficultySpectrumDisplay(IBeatmapSetInfo? beatmapSet = null)
|
||||
private const int max_difficulties_before_collapsing = 12;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
@@ -72,8 +46,6 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Spacing = new Vector2(10, 0),
|
||||
Direction = FillDirection.Horizontal,
|
||||
};
|
||||
|
||||
BeatmapSet = beatmapSet;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -84,36 +56,70 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
flow.Clear();
|
||||
foreach (var group in flow)
|
||||
group.Alpha = 0;
|
||||
|
||||
if (beatmapSet == null)
|
||||
{
|
||||
foreach (var group in flow)
|
||||
group.Beatmaps = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127
|
||||
bool collapsed = beatmapSet.Beatmaps.Count() > 12;
|
||||
bool collapsed = beatmapSet.Beatmaps.Count() > max_difficulties_before_collapsing;
|
||||
|
||||
foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset).OrderBy(group => group.Key))
|
||||
{
|
||||
flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key.OnlineID, rulesetGrouping, collapsed, dotSize)
|
||||
int rulesetId = rulesetGrouping.Key.OnlineID;
|
||||
|
||||
var group = flow.SingleOrDefault(rg => rg.RulesetId == rulesetId);
|
||||
|
||||
if (group == null)
|
||||
{
|
||||
Spacing = new Vector2(DotSpacing, 0f),
|
||||
});
|
||||
group = new RulesetDifficultyGroup(rulesetId);
|
||||
flow.Add(group);
|
||||
flow.SetLayoutPosition(group, rulesetId);
|
||||
}
|
||||
|
||||
group.Alpha = 1;
|
||||
group.Beatmaps = rulesetGrouping.ToArray();
|
||||
group.Collapsed = collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class RulesetDifficultyGroup : FillFlowContainer
|
||||
{
|
||||
private readonly int rulesetId;
|
||||
private readonly IEnumerable<IBeatmapInfo> beatmapInfos;
|
||||
private readonly bool collapsed;
|
||||
private readonly Vector2 dotSize;
|
||||
public readonly int RulesetId;
|
||||
|
||||
public RulesetDifficultyGroup(int rulesetId, IEnumerable<IBeatmapInfo> beatmapInfos, bool collapsed, Vector2 dotSize)
|
||||
private IBeatmapInfo[] beatmaps = [];
|
||||
|
||||
public IBeatmapInfo[] Beatmaps
|
||||
{
|
||||
this.rulesetId = rulesetId;
|
||||
this.beatmapInfos = beatmapInfos;
|
||||
this.collapsed = collapsed;
|
||||
this.dotSize = dotSize;
|
||||
set
|
||||
{
|
||||
beatmaps = value.OrderBy(bi => bi.StarRating).ToArray();
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private bool collapsed;
|
||||
|
||||
public bool Collapsed
|
||||
{
|
||||
get => collapsed;
|
||||
set
|
||||
{
|
||||
collapsed = value;
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private OsuSpriteText countText = null!;
|
||||
|
||||
public RulesetDifficultyGroup(int rulesetId)
|
||||
{
|
||||
RulesetId = rulesetId;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -123,53 +129,83 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Spacing = new Vector2(1, 0);
|
||||
Direction = FillDirection.Horizontal;
|
||||
|
||||
var icon = rulesets.GetRuleset(rulesetId)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle };
|
||||
var icon = rulesets.GetRuleset(RulesetId)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle };
|
||||
Add(icon.With(i =>
|
||||
{
|
||||
i.Size = new Vector2(14);
|
||||
i.Anchor = i.Origin = Anchor.Centre;
|
||||
}));
|
||||
|
||||
if (!collapsed)
|
||||
for (int i = 0; i < max_difficulties_before_collapsing; i++)
|
||||
Add(new DifficultyDot());
|
||||
|
||||
Add(countText = new OsuSpriteText
|
||||
{
|
||||
foreach (var beatmapInfo in beatmapInfos.OrderBy(bi => bi.StarRating))
|
||||
Add(new DifficultyDot(beatmapInfo.StarRating, dotSize));
|
||||
}
|
||||
else
|
||||
Font = OsuFont.Default.With(size: 12),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Padding = new MarginPadding { Bottom = 1 }
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
countText.Alpha = collapsed ? 1 : 0;
|
||||
countText.Text = beatmaps.Length.ToLocalisableString(@"N0");
|
||||
|
||||
var dots = this.OfType<DifficultyDot>().ToArray();
|
||||
|
||||
for (int i = 0; i < max_difficulties_before_collapsing; i++)
|
||||
{
|
||||
Add(new OsuSpriteText
|
||||
var dot = dots[i];
|
||||
|
||||
if (collapsed || i >= beatmaps.Length)
|
||||
{
|
||||
Text = beatmapInfos.Count().ToLocalisableString(@"N0"),
|
||||
Font = OsuFont.Default.With(size: 12),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Padding = new MarginPadding { Bottom = 1 }
|
||||
});
|
||||
dot.Alpha = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
dot.Alpha = 1;
|
||||
dot.StarDifficulty = beatmaps[i].StarRating;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private partial class DifficultyDot : CircularContainer
|
||||
private partial class DifficultyDot : Circle
|
||||
{
|
||||
private readonly double starDifficulty;
|
||||
private double starDifficulty;
|
||||
|
||||
public DifficultyDot(double starDifficulty, Vector2 dotSize)
|
||||
public double StarDifficulty
|
||||
{
|
||||
this.starDifficulty = starDifficulty;
|
||||
Size = dotSize;
|
||||
get => starDifficulty;
|
||||
set
|
||||
{
|
||||
starDifficulty = value;
|
||||
updateColour();
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Anchor = Origin = Anchor.Centre;
|
||||
Masking = true;
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.ForStarDifficulty(starDifficulty)
|
||||
};
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(5, 10);
|
||||
Anchor = Origin = Anchor.Centre;
|
||||
|
||||
updateColour();
|
||||
}
|
||||
|
||||
private void updateColour()
|
||||
{
|
||||
Colour = colours.ForStarDifficulty(starDifficulty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -127,8 +127,6 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
|
||||
if (CreateNewTriangles)
|
||||
addTriangles(false);
|
||||
|
||||
@@ -138,6 +136,10 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
: 1;
|
||||
|
||||
float elapsedSeconds = (float)Time.Elapsed / 1000;
|
||||
|
||||
if (elapsedSeconds == 0)
|
||||
return;
|
||||
|
||||
// Since position is relative, the velocity needs to scale inversely with DrawHeight.
|
||||
// Since we will later multiply by the scale of individual triangles we normalize by
|
||||
// dividing by triangleScale.
|
||||
@@ -157,6 +159,8 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
if (bottomPos < 0)
|
||||
parts.RemoveAt(i);
|
||||
}
|
||||
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -183,8 +187,13 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
|
||||
int currentCount = parts.Count;
|
||||
|
||||
if (AimCount - currentCount == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < AimCount - currentCount; i++)
|
||||
parts.Add(createTriangle(randomY));
|
||||
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
|
||||
private TriangleParticle createTriangle(bool randomY)
|
||||
|
||||
@@ -91,12 +91,14 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
|
||||
if (CreateNewTriangles)
|
||||
addTriangles(false);
|
||||
|
||||
float elapsedSeconds = (float)Time.Elapsed / 1000;
|
||||
|
||||
if (elapsedSeconds == 0)
|
||||
return;
|
||||
|
||||
// Since position is relative, the velocity needs to scale inversely with DrawHeight.
|
||||
float movedDistance = -elapsedSeconds * Velocity * base_velocity / DrawHeight;
|
||||
|
||||
@@ -112,6 +114,8 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
if (bottomPos < 0)
|
||||
parts.RemoveAt(i);
|
||||
}
|
||||
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -138,8 +142,13 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
|
||||
int currentCount = parts.Count;
|
||||
|
||||
if (AimCount - currentCount == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < AimCount - currentCount; i++)
|
||||
parts.Add(createTriangle(randomY));
|
||||
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
|
||||
private TriangleParticle createTriangle(bool randomY)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace osu.Game.Graphics.Containers.Markdown
|
||||
public LocalisableString TooltipText { get; }
|
||||
|
||||
public OsuMarkdownImage(LinkInline linkInline)
|
||||
: base(linkInline.Url)
|
||||
: base($"https://osu.ppy.sh/media-url?url={linkInline.Url}")
|
||||
{
|
||||
TooltipText = linkInline.Title;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
#region OsuDropdownMenu
|
||||
|
||||
protected partial class OsuDropdownMenu : DropdownMenu
|
||||
public partial class OsuDropdownMenu : DropdownMenu
|
||||
{
|
||||
public override bool HandleNonPositionalInput => State == MenuState.Open;
|
||||
|
||||
@@ -252,6 +252,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Size = new Vector2(8),
|
||||
Alpha = 0,
|
||||
X = chevron_offset,
|
||||
Y = 1,
|
||||
Margin = new MarginPadding { Left = 3, Right = 3 },
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
// 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.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
public partial class ShearedDropdown<T> : Dropdown<T>, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
protected override DropdownHeader CreateHeader() => new ShearedDropdownHeader();
|
||||
|
||||
protected override DropdownMenu CreateMenu() => new ShearedDropdownMenu();
|
||||
|
||||
public ShearedDropdown(LocalisableString label)
|
||||
{
|
||||
if (Header is ShearedDropdownHeader osuHeader)
|
||||
{
|
||||
osuHeader.Dropdown = this;
|
||||
osuHeader.LeftSideLabel = label;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var header = (ShearedDropdownHeader)Header;
|
||||
var menu = (ShearedDropdownMenu)Menu;
|
||||
|
||||
menu.Padding = new MarginPadding { Left = header.LabelContainer.DrawWidth - 10f, Right = 6f };
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Repeat) return false;
|
||||
|
||||
if (e.Action == GlobalAction.Back)
|
||||
return Back();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
protected partial class ShearedDropdownMenu : OsuDropdown<T>.OsuDropdownMenu
|
||||
{
|
||||
private readonly Vector2 shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
|
||||
public new MarginPadding Padding
|
||||
{
|
||||
get => base.Padding;
|
||||
set => base.Padding = value;
|
||||
}
|
||||
|
||||
public ShearedDropdownMenu()
|
||||
{
|
||||
Shear = shear;
|
||||
Margin = new MarginPadding { Top = 5f };
|
||||
}
|
||||
|
||||
protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new ShearedMenuItem(item)
|
||||
{
|
||||
BackgroundColourHover = HoverColour,
|
||||
BackgroundColourSelected = SelectionColour
|
||||
};
|
||||
|
||||
public partial class ShearedMenuItem : DrawableOsuDropdownMenuItem
|
||||
{
|
||||
private readonly Vector2 shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
|
||||
public ShearedMenuItem(MenuItem item)
|
||||
: base(item)
|
||||
{
|
||||
Foreground.Shear = -shear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ShearedDropdownHeader : DropdownHeader
|
||||
{
|
||||
private const float corner_radius = 5f;
|
||||
|
||||
private LocalisableString label;
|
||||
|
||||
protected override LocalisableString Label
|
||||
{
|
||||
get => label;
|
||||
set
|
||||
{
|
||||
label = value;
|
||||
valueText.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
public LocalisableString LeftSideLabel
|
||||
{
|
||||
set => labelText.Text = value;
|
||||
}
|
||||
|
||||
private readonly OsuSpriteText labelText;
|
||||
private readonly OsuSpriteText valueText;
|
||||
private readonly Box labelBox;
|
||||
private readonly SpriteIcon chevron;
|
||||
|
||||
public Container LabelContainer { get; }
|
||||
|
||||
public ShearedDropdown<T> Dropdown = null!;
|
||||
private ShearedDropdownSearchBar searchBar = null!;
|
||||
|
||||
private readonly Vector2 shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public ShearedDropdownHeader()
|
||||
{
|
||||
Shear = shear;
|
||||
CornerRadius = corner_radius;
|
||||
Masking = true;
|
||||
|
||||
Foreground.Children = new Drawable[]
|
||||
{
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension()
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
LabelContainer = new Container
|
||||
{
|
||||
CornerRadius = corner_radius,
|
||||
Masking = true,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
labelBox = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
labelText = new OsuSpriteText
|
||||
{
|
||||
Margin = new MarginPadding { Horizontal = 10f, Vertical = 8f },
|
||||
Font = OsuFont.Torus.With(size: 16.8f, weight: FontWeight.SemiBold),
|
||||
Shear = -shear,
|
||||
},
|
||||
},
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = 10f },
|
||||
Shear = -shear,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
valueText = new TruncatingSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Padding = new MarginPadding { Right = 15f },
|
||||
Font = OsuFont.Torus.With(size: 16.8f, weight: FontWeight.SemiBold),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
chevron = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Y = 1f,
|
||||
Icon = FontAwesome.Solid.ChevronDown,
|
||||
Size = new Vector2(10f),
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
AddInternal(LabelContainer.CreateProxy());
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
labelBox.Colour = colourProvider.Background3;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Dropdown.Menu.StateChanged += _ => updateChevron();
|
||||
SearchBar.State.ValueChanged += _ => updateColour();
|
||||
Enabled.BindValueChanged(_ => updateColour());
|
||||
updateColour();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
searchBar.Padding = new MarginPadding { Left = LabelContainer.DrawWidth };
|
||||
|
||||
// By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it.
|
||||
Background.Padding = new MarginPadding { Left = LabelContainer.DrawWidth - corner_radius };
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateColour();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateColour();
|
||||
}
|
||||
|
||||
private void updateColour()
|
||||
{
|
||||
bool hovered = Enabled.Value && IsHovered;
|
||||
var hoveredColour = colourProvider.Light4;
|
||||
var unhoveredColour = colourProvider.Background5;
|
||||
|
||||
Colour = Color4.White;
|
||||
Alpha = Enabled.Value ? 1 : 0.3f;
|
||||
|
||||
if (SearchBar.State.Value == Visibility.Visible)
|
||||
{
|
||||
chevron.Colour = hovered ? hoveredColour.Lighten(0.5f) : Colour4.White;
|
||||
Background.Colour = unhoveredColour;
|
||||
}
|
||||
else
|
||||
{
|
||||
chevron.Colour = Color4.White;
|
||||
Background.Colour = hovered ? hoveredColour : unhoveredColour;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChevron()
|
||||
{
|
||||
Debug.Assert(Dropdown != null);
|
||||
bool open = Dropdown.Menu.State == MenuState.Open;
|
||||
chevron.ScaleTo(open ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override DropdownSearchBar CreateSearchBar() => searchBar = new ShearedDropdownSearchBar();
|
||||
|
||||
private partial class ShearedDropdownSearchBar : DropdownSearchBar
|
||||
{
|
||||
protected override void PopIn() => this.FadeIn();
|
||||
|
||||
protected override void PopOut() => this.FadeOut();
|
||||
|
||||
protected override TextBox CreateTextBox() => new DropdownSearchTextBox
|
||||
{
|
||||
FontSize = OsuFont.Default.Size,
|
||||
};
|
||||
|
||||
private partial class DropdownSearchTextBox : OsuTextBox
|
||||
{
|
||||
private readonly Vector2 shear = new Vector2(OsuGame.SHEAR, 0);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider? colourProvider)
|
||||
{
|
||||
TextContainer.Shear = -shear;
|
||||
BackgroundUnfocused = colourProvider?.Background5 ?? new Color4(10, 10, 10, 255);
|
||||
BackgroundFocused = colourProvider?.Background5 ?? new Color4(10, 10, 10, 255);
|
||||
}
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
base.OnFocus(e);
|
||||
BorderThickness = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace osu.Game.Online.API.Requests
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var req = base.CreateWebRequest();
|
||||
req.Method = HttpMethod.Post;
|
||||
req.Method = HttpMethod.Put;
|
||||
return req;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"converts")]
|
||||
public APIBeatmap[]? Converts { get; set; }
|
||||
|
||||
[JsonProperty(@"related_tags")]
|
||||
public APITag[]? RelatedTags { get; set; }
|
||||
|
||||
private BeatmapMetadata metadata => new BeatmapMetadata
|
||||
{
|
||||
Title = Title,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@@ -219,7 +220,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
}
|
||||
};
|
||||
|
||||
string description = mod.SettingDescription;
|
||||
string description = string.Join(", ", mod.SettingDescription.Select(svp => $"{svp.setting}: {svp.value}"));
|
||||
|
||||
if (!string.IsNullOrEmpty(description))
|
||||
{
|
||||
@@ -227,7 +228,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold),
|
||||
Text = mod.SettingDescription,
|
||||
Text = description,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Top = 1 },
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -81,6 +81,18 @@ namespace osu.Game.Online.Multiplayer
|
||||
Playlist = room.Playlist.Select(p => new MultiplayerPlaylistItem(p)).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the active <see cref="MultiplayerPlaylistItem"/> as determined by the room's current settings.
|
||||
/// </summary>
|
||||
[IgnoreMember]
|
||||
public MultiplayerPlaylistItem CurrentPlaylistItem => Playlist.Single(item => item.ID == Settings.PlaylistItemId);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a user is able to add playlist items to this room.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to check.</param>
|
||||
public bool CanAddPlaylistItems(MultiplayerRoomUser user) => user.Equals(Host) || Settings.QueueMode != QueueMode.HostOnly;
|
||||
|
||||
public override string ToString() => $"RoomID:{RoomID} Host:{Host?.UserID} Users:{Users.Count} State:{State} Settings: [{Settings}]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ namespace osu.Game.Online
|
||||
(typeof(UserActivity.EditingBeatmap), typeof(UserActivity)),
|
||||
(typeof(UserActivity.ModdingBeatmap), typeof(UserActivity)),
|
||||
(typeof(UserActivity.TestingBeatmap), typeof(UserActivity)),
|
||||
(typeof(UserActivity.InDailyChallengeLobby), typeof(UserActivity)),
|
||||
(typeof(UserActivity.PlayingDailyChallenge), typeof(UserActivity)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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[]
|
||||
{
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// 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;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -9,6 +11,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays.BeatmapListing;
|
||||
@@ -17,26 +20,22 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
public partial class Info : Container
|
||||
{
|
||||
private const float metadata_width = 175;
|
||||
private const float metadata_width = 185;
|
||||
private const float spacing = 20;
|
||||
private const float base_height = 220;
|
||||
private const float base_height = 300;
|
||||
|
||||
private readonly Box successRateBackground;
|
||||
private readonly Box background;
|
||||
private readonly SuccessRate successRate;
|
||||
private readonly MetadataSection<string[]?> userTags;
|
||||
|
||||
public readonly Bindable<APIBeatmapSet> BeatmapSet = new Bindable<APIBeatmapSet>();
|
||||
|
||||
public APIBeatmap? BeatmapInfo
|
||||
{
|
||||
get => successRate.Beatmap;
|
||||
set => successRate.Beatmap = value;
|
||||
}
|
||||
public readonly Bindable<APIBeatmap> Beatmap = new Bindable<APIBeatmap>();
|
||||
|
||||
public Info()
|
||||
{
|
||||
SuccessRate successRate;
|
||||
MetadataSectionNominators nominators;
|
||||
MetadataSection source, tags;
|
||||
MetadataSection source, mapperTags;
|
||||
MetadataSectionGenre genre;
|
||||
MetadataSectionLanguage language;
|
||||
OsuSpriteText notRankedPlaceholder;
|
||||
@@ -66,27 +65,30 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
Child = new MetadataSectionDescription(),
|
||||
},
|
||||
},
|
||||
new Container
|
||||
new OsuScrollContainer
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = metadata_width,
|
||||
Padding = new MarginPadding { Horizontal = 10 },
|
||||
Padding = new MarginPadding { Left = 10 },
|
||||
Margin = new MarginPadding { Right = BeatmapSetOverlay.RIGHT_WIDTH + spacing },
|
||||
Masking = true,
|
||||
ScrollbarOverlapsContent = false,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Full,
|
||||
Padding = new MarginPadding { Right = 5 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
nominators = new MetadataSectionNominators(),
|
||||
source = new MetadataSectionSource(),
|
||||
genre = new MetadataSectionGenre { Width = 0.5f },
|
||||
language = new MetadataSectionLanguage { Width = 0.5f },
|
||||
tags = new MetadataSectionTags(),
|
||||
userTags = new MetadataSectionUserTags(),
|
||||
mapperTags = new MetadataSectionMapperTags(),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -121,18 +123,42 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
},
|
||||
};
|
||||
|
||||
BeatmapSet.ValueChanged += b =>
|
||||
BeatmapSet.BindValueChanged(b =>
|
||||
{
|
||||
nominators.Metadata = (b.NewValue?.CurrentNominations ?? Array.Empty<BeatmapSetOnlineNomination>(), b.NewValue?.RelatedUsers ?? Array.Empty<APIUser>());
|
||||
source.Metadata = b.NewValue?.Source ?? string.Empty;
|
||||
tags.Metadata = b.NewValue?.Tags ?? string.Empty;
|
||||
mapperTags.Metadata = b.NewValue?.Tags ?? string.Empty;
|
||||
updateUserTags();
|
||||
genre.Metadata = b.NewValue?.Genre ?? new BeatmapSetOnlineGenre { Id = (int)SearchGenre.Unspecified };
|
||||
language.Metadata = b.NewValue?.Language ?? new BeatmapSetOnlineLanguage { Id = (int)SearchLanguage.Unspecified };
|
||||
bool setHasLeaderboard = b.NewValue?.Status > 0;
|
||||
successRate.Alpha = setHasLeaderboard ? 1 : 0;
|
||||
notRankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1;
|
||||
Height = setHasLeaderboard ? 270 : base_height;
|
||||
};
|
||||
});
|
||||
Beatmap.BindValueChanged(b =>
|
||||
{
|
||||
successRate.Beatmap = b.NewValue;
|
||||
updateUserTags();
|
||||
});
|
||||
}
|
||||
|
||||
private void updateUserTags()
|
||||
{
|
||||
if (Beatmap.Value?.TopTags == null || Beatmap.Value.TopTags.Length == 0 || BeatmapSet.Value?.RelatedTags == null)
|
||||
{
|
||||
userTags.Metadata = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var tagsById = BeatmapSet.Value.RelatedTags.ToDictionary(t => t.Id);
|
||||
userTags.Metadata = Beatmap.Value.TopTags
|
||||
.Select(t => (topTag: t, relatedTag: tagsById.GetValueOrDefault(t.TagId)))
|
||||
.Where(t => t.relatedTag != null)
|
||||
// see https://github.com/ppy/osu-web/blob/bb3bd2e7c6f84f26066df5ea20a81c77ec9bb60a/resources/js/beatmapsets-show/controller.ts#L103-L106 for sort criteria
|
||||
.OrderByDescending(t => t.topTag.VoteCount)
|
||||
.ThenBy(t => t.relatedTag!.Name)
|
||||
.Select(t => t.relatedTag!.Name)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
||||
+3
-3
@@ -7,10 +7,10 @@ using osu.Game.Online.Chat;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
public partial class MetadataSectionTags : MetadataSection
|
||||
public partial class MetadataSectionMapperTags : MetadataSection
|
||||
{
|
||||
public MetadataSectionTags(Action<string>? searchAction = null)
|
||||
: base(MetadataType.Tags, searchAction)
|
||||
public MetadataSectionMapperTags(Action<string>? searchAction = null)
|
||||
: base(MetadataType.MapperTags, searchAction)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Online.Chat;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
public partial class MetadataSectionUserTags : MetadataSection<string[]?>
|
||||
{
|
||||
private readonly Action<string>? searchAction;
|
||||
|
||||
public MetadataSectionUserTags(Action<string>? searchAction = null)
|
||||
: base(MetadataType.UserTags, null)
|
||||
{
|
||||
this.searchAction = searchAction;
|
||||
}
|
||||
|
||||
protected override void AddMetadata(string[]? tags, LinkFlowContainer loaded)
|
||||
{
|
||||
if (tags == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i <= tags.Length - 1; i++)
|
||||
{
|
||||
string tag = tags[i];
|
||||
|
||||
if (searchAction != null)
|
||||
loaded.AddLink(tag, () => searchAction(tag));
|
||||
else
|
||||
loaded.AddLink(tag, LinkAction.SearchBeatmapSet, $@"tag=""""{tag}""""");
|
||||
|
||||
if (i != tags.Length - 1)
|
||||
loaded.AddText(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.ComponentModel;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
|
||||
@@ -8,8 +9,11 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
public enum MetadataType
|
||||
{
|
||||
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoTags))]
|
||||
Tags,
|
||||
[Description("User Tags")] // TODO: use translated string after osu-resources update
|
||||
UserTags,
|
||||
|
||||
[Description("Mapper Tags")] // TODO: use translated string after osu-resources update
|
||||
MapperTags,
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoSource))]
|
||||
Source,
|
||||
|
||||
@@ -249,6 +249,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
getScoresRequest = null;
|
||||
|
||||
noScoresPlaceholder.Hide();
|
||||
noTeamPlaceholder.Hide();
|
||||
notSupporterPlaceholder.Hide();
|
||||
|
||||
if (Beatmap.Value == null || Beatmap.Value.OnlineID <= 0 || (Beatmap.Value.Status <= BeatmapOnlineStatus.Pending))
|
||||
{
|
||||
@@ -271,9 +273,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
return;
|
||||
}
|
||||
|
||||
noTeamPlaceholder.Hide();
|
||||
notSupporterPlaceholder.Hide();
|
||||
|
||||
Show();
|
||||
loading.Show();
|
||||
|
||||
|
||||
@@ -47,7 +47,10 @@ namespace osu.Game.Overlays
|
||||
Spacing = new Vector2(0, 20),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
info = new Info(),
|
||||
info = new Info
|
||||
{
|
||||
Beatmap = { BindTarget = Header.HeaderContent.Picker.Beatmap }
|
||||
},
|
||||
new ScoresContainer
|
||||
{
|
||||
Beatmap = { BindTarget = Header.HeaderContent.Picker.Beatmap }
|
||||
@@ -60,11 +63,7 @@ namespace osu.Game.Overlays
|
||||
info.BeatmapSet.BindTo(beatmapSet);
|
||||
comments.BeatmapSet.BindTo(beatmapSet);
|
||||
|
||||
Header.HeaderContent.Picker.Beatmap.ValueChanged += b =>
|
||||
{
|
||||
info.BeatmapInfo = b.NewValue;
|
||||
ScrollFlow.ScrollToStart();
|
||||
};
|
||||
Header.HeaderContent.Picker.Beatmap.ValueChanged += b => ScrollFlow.ScrollToStart();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user