1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 02:42:54 +08:00

Merge branch 'master' into migrate-country-rank

This commit is contained in:
Dean Herbert 2021-03-08 11:07:25 +09:00 committed by GitHub
commit 2b1ab3576b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 564 additions and 197 deletions

View File

@ -13,4 +13,6 @@ about: Issues regarding encountered bugs.
*please attach logs here, which are located at:* *please attach logs here, which are located at:*
- `%AppData%/osu/logs` *(on Windows),* - `%AppData%/osu/logs` *(on Windows),*
- `~/.local/share/osu/logs` *(on Linux & macOS).* - `~/.local/share/osu/logs` *(on Linux & macOS).*
- `Android/Data/sh.ppy.osulazer/logs` *(on Android)*,
- on iOS they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer)
--> -->

View File

@ -13,6 +13,8 @@ about: Issues regarding crashes or permanent freezes.
*please attach logs here, which are located at:* *please attach logs here, which are located at:*
- `%AppData%/osu/logs` *(on Windows),* - `%AppData%/osu/logs` *(on Windows),*
- `~/.local/share/osu/logs` *(on Linux & macOS).* - `~/.local/share/osu/logs` *(on Linux & macOS).*
- `Android/Data/sh.ppy.osulazer/logs` *(on Android)*,
- on iOS they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer)
--> -->
**Computer Specifications:** **Computer Specifications:**

View File

@ -52,6 +52,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.226.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.302.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
} }
} }
protected override Skill[] CreateSkills(IBeatmap beatmap) protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods)
{ {
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f; halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
return new Skill[] return new Skill[]
{ {
new Movement(halfCatcherWidth), new Movement(mods, halfCatcherWidth),
}; };
} }

View File

@ -5,6 +5,7 @@ using System;
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty.Skills namespace osu.Game.Rulesets.Catch.Difficulty.Skills
{ {
@ -25,7 +26,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
private float lastDistanceMoved; private float lastDistanceMoved;
private double lastStrainTime; private double lastStrainTime;
public Movement(float halfCatcherWidth) public Movement(Mod[] mods, float halfCatcherWidth)
: base(mods)
{ {
HalfCatcherWidth = halfCatcherWidth; HalfCatcherWidth = halfCatcherWidth;
} }

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
// Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required. // Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required.
protected override IEnumerable<DifficultyHitObject> SortObjects(IEnumerable<DifficultyHitObject> input) => input; protected override IEnumerable<DifficultyHitObject> SortObjects(IEnumerable<DifficultyHitObject> input) => input;
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
{ {
new Strain(((ManiaBeatmap)beatmap).TotalColumns) new Strain(mods, ((ManiaBeatmap)beatmap).TotalColumns)
}; };
protected override Mod[] DifficultyAdjustmentMods protected override Mod[] DifficultyAdjustmentMods

View File

@ -6,6 +6,7 @@ using osu.Framework.Utils;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills namespace osu.Game.Rulesets.Mania.Difficulty.Skills
@ -24,7 +25,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
private double individualStrain; private double individualStrain;
private double overallStrain; private double overallStrain;
public Strain(int totalColumns) public Strain(Mod[] mods, int totalColumns)
: base(mods)
{ {
holdEndTimes = new double[totalColumns]; holdEndTimes = new double[totalColumns];
individualStrains = new double[totalColumns]; individualStrains = new double[totalColumns];

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -79,10 +79,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
} }
} }
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
{ {
new Aim(), new Aim(mods),
new Speed() new Speed(mods)
}; };
protected override Mod[] DifficultyAdjustmentMods => new Mod[] protected override Mod[] DifficultyAdjustmentMods => new Mod[]

View File

@ -4,6 +4,7 @@
using System; using System;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -17,6 +18,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private const double angle_bonus_begin = Math.PI / 3; private const double angle_bonus_begin = Math.PI / 3;
private const double timing_threshold = 107; private const double timing_threshold = 107;
public Aim(Mod[] mods)
: base(mods)
{
}
protected override double SkillMultiplier => 26.25; protected override double SkillMultiplier => 26.25;
protected override double StrainDecayBase => 0.15; protected override double StrainDecayBase => 0.15;

View File

@ -4,6 +4,7 @@
using System; using System;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -27,6 +28,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private const double max_speed_bonus = 45; // ~330BPM private const double max_speed_bonus = 45; // ~330BPM
private const double speed_balancing_factor = 40; private const double speed_balancing_factor = 40;
public Speed(Mod[] mods)
: base(mods)
{
}
protected override double StrainValueOf(DifficultyHitObject current) protected override double StrainValueOf(DifficultyHitObject current)
{ {
if (current.BaseObject is Spinner) if (current.BaseObject is Spinner)

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -5,6 +5,7 @@ using System;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -39,6 +40,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// </summary> /// </summary>
private int currentMonoLength; private int currentMonoLength;
public Colour(Mod[] mods)
: base(mods)
{
}
protected override double StrainValueOf(DifficultyHitObject current) protected override double StrainValueOf(DifficultyHitObject current)
{ {
// changing from/to a drum roll or a swell does not constitute a colour change. // changing from/to a drum roll or a swell does not constitute a colour change.

View File

@ -5,6 +5,7 @@ using System;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -47,6 +48,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// </summary> /// </summary>
private int notesSinceRhythmChange; private int notesSinceRhythmChange;
public Rhythm(Mod[] mods)
: base(mods)
{
}
protected override double StrainValueOf(DifficultyHitObject current) protected override double StrainValueOf(DifficultyHitObject current)
{ {
// drum rolls and swells are exempt. // drum rolls and swells are exempt.

View File

@ -5,6 +5,7 @@ using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -48,8 +49,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// <summary> /// <summary>
/// Creates a <see cref="Stamina"/> skill. /// Creates a <see cref="Stamina"/> skill.
/// </summary> /// </summary>
/// <param name="mods">Mods for use in skill calculations.</param>
/// <param name="rightHand">Whether this instance is performing calculations for the right hand.</param> /// <param name="rightHand">Whether this instance is performing calculations for the right hand.</param>
public Stamina(bool rightHand) public Stamina(Mod[] mods, bool rightHand)
: base(mods)
{ {
hand = rightHand ? 1 : 0; hand = rightHand ? 1 : 0;
} }

View File

@ -29,12 +29,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{ {
} }
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
{ {
new Colour(), new Colour(mods),
new Rhythm(), new Rhythm(mods),
new Stamina(true), new Stamina(mods, true),
new Stamina(false), new Stamina(mods, false),
}; };
protected override Mod[] DifficultyAdjustmentMods => new Mod[] protected override Mod[] DifficultyAdjustmentMods => new Mod[]

View File

@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
{ {
// if a taiko skin is providing explosion sprites, hide the judgements completely // if a taiko skin is providing explosion sprites, hide the judgements completely
if (hasExplosion.Value) if (hasExplosion.Value)
return Drawable.Empty(); return Drawable.Empty().With(d => d.Expire());
} }
if (!(component is TaikoSkinComponent taikoComponent)) if (!(component is TaikoSkinComponent taikoComponent))
@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
// suppress the default kiai explosion if the skin brings its own sprites. // suppress the default kiai explosion if the skin brings its own sprites.
// the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield. // the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield.
if (hasExplosion.Value) if (hasExplosion.Value)
return Drawable.Empty().With(d => d.LifetimeEnd = double.MinValue); return Drawable.Empty().With(d => d.Expire());
return null; return null;

View File

@ -71,7 +71,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="DeepEqual" Version="2.0.0" /> <PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Moq" Version="4.16.0" /> <PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project> </Project>

View File

@ -45,7 +45,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="DeepEqual" Version="2.0.0" /> <PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Moq" Version="4.16.0" /> <PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
</Project> </Project>

View File

@ -212,7 +212,7 @@ namespace osu.Game.Tests.NonVisual
throw new NotImplementedException(); throw new NotImplementedException();
} }
protected override Skill[] CreateSkills(IBeatmap beatmap) protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View File

@ -0,0 +1,35 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Tests.Visual.Multiplayer;
using osu.Game.Users;
namespace osu.Game.Tests.OnlinePlay
{
[HeadlessTest]
public class StatefulMultiplayerClientTest : MultiplayerTestScene
{
[Test]
public void TestUserAddedOnJoin()
{
var user = new User { Id = 33 };
AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3);
AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
}
[Test]
public void TestUserRemovedOnLeave()
{
var user = new User { Id = 44 };
AddStep("add user", () => Client.AddUser(user));
AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3);
AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1);
}
}
}

View File

@ -1,46 +1,41 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using NUnit.Framework; using osu.Framework.Allocation;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer namespace osu.Game.Tests.Visual.Multiplayer
{ {
public class TestSceneMultiplayer : MultiplayerTestScene public class TestSceneMultiplayer : ScreenTestScene
{ {
private TestMultiplayer multiplayerScreen;
public TestSceneMultiplayer() public TestSceneMultiplayer()
{ {
var multi = new TestMultiplayer(); AddStep("show", () =>
{
multiplayerScreen = new TestMultiplayer();
AddStep("show", () => LoadScreen(multi)); // Needs to be added at a higher level since the multiplayer screen becomes non-current.
AddUntilStep("wait for loaded", () => multi.IsLoaded); Child = multiplayerScreen.Client;
}
[Test] LoadScreen(multiplayerScreen);
public void TestOneUserJoinedMultipleTimes() });
{
var user = new User { Id = 33 };
AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3); AddUntilStep("wait for loaded", () => multiplayerScreen.IsLoaded);
AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
}
[Test]
public void TestOneUserLeftMultipleTimes()
{
var user = new User { Id = 44 };
AddStep("add user", () => Client.AddUser(user));
AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3);
AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1);
} }
private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
{ {
[Cached(typeof(StatefulMultiplayerClient))]
public readonly TestMultiplayerClient Client;
public TestMultiplayer()
{
Client = new TestMultiplayerClient((TestMultiplayerRoomManager)RoomManager);
}
protected override RoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); protected override RoomManager CreateRoomManager() => new TestMultiplayerRoomManager();
} }
} }

View File

@ -3,12 +3,10 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
@ -20,9 +18,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
private MultiplayerMatchSubScreen screen; private MultiplayerMatchSubScreen screen;
[Cached]
private OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker();
public TestSceneMultiplayerMatchSubScreen() public TestSceneMultiplayerMatchSubScreen()
: base(false) : base(false)
{ {

View File

@ -1,10 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual.Multiplayer namespace osu.Game.Tests.Visual.Multiplayer
{ {
@ -21,15 +24,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createRoomManager().With(d => d.OnLoadComplete += _ => createRoomManager().With(d => d.OnLoadComplete += _ =>
{ {
roomManager.CreateRoom(new Room { Name = { Value = "1" } }); roomManager.CreateRoom(createRoom(r => r.Name.Value = "1"));
roomManager.PartRoom(); roomManager.PartRoom();
roomManager.CreateRoom(new Room { Name = { Value = "2" } }); roomManager.CreateRoom(createRoom(r => r.Name.Value = "2"));
roomManager.PartRoom(); roomManager.PartRoom();
roomManager.ClearRooms(); roomManager.ClearRooms();
}); });
}); });
AddAssert("manager polled for rooms", () => roomManager.Rooms.Count == 2); AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2);
AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value); AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value);
} }
@ -40,16 +43,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createRoomManager().With(d => d.OnLoadComplete += _ => createRoomManager().With(d => d.OnLoadComplete += _ =>
{ {
roomManager.CreateRoom(new Room()); roomManager.CreateRoom(createRoom());
roomManager.PartRoom(); roomManager.PartRoom();
roomManager.CreateRoom(new Room()); roomManager.CreateRoom(createRoom());
roomManager.PartRoom(); roomManager.PartRoom();
}); });
}); });
AddStep("disconnect", () => roomContainer.Client.Disconnect()); AddStep("disconnect", () => roomContainer.Client.Disconnect());
AddAssert("rooms cleared", () => roomManager.Rooms.Count == 0); AddAssert("rooms cleared", () => ((RoomManager)roomManager).Rooms.Count == 0);
AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value); AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value);
} }
@ -60,9 +63,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createRoomManager().With(d => d.OnLoadComplete += _ => createRoomManager().With(d => d.OnLoadComplete += _ =>
{ {
roomManager.CreateRoom(new Room()); roomManager.CreateRoom(createRoom());
roomManager.PartRoom(); roomManager.PartRoom();
roomManager.CreateRoom(new Room()); roomManager.CreateRoom(createRoom());
roomManager.PartRoom(); roomManager.PartRoom();
}); });
}); });
@ -70,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("disconnect", () => roomContainer.Client.Disconnect()); AddStep("disconnect", () => roomContainer.Client.Disconnect());
AddStep("connect", () => roomContainer.Client.Connect()); AddStep("connect", () => roomContainer.Client.Connect());
AddAssert("manager polled for rooms", () => roomManager.Rooms.Count == 2); AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2);
AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value); AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value);
} }
@ -81,12 +84,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createRoomManager().With(d => d.OnLoadComplete += _ => createRoomManager().With(d => d.OnLoadComplete += _ =>
{ {
roomManager.CreateRoom(new Room()); roomManager.CreateRoom(createRoom());
roomManager.ClearRooms(); roomManager.ClearRooms();
}); });
}); });
AddAssert("manager not polled for rooms", () => roomManager.Rooms.Count == 0); AddAssert("manager not polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 0);
AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value); AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value);
} }
@ -97,7 +100,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createRoomManager().With(d => d.OnLoadComplete += _ => createRoomManager().With(d => d.OnLoadComplete += _ =>
{ {
roomManager.CreateRoom(new Room()); roomManager.CreateRoom(createRoom());
}); });
}); });
@ -111,7 +114,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createRoomManager().With(d => d.OnLoadComplete += _ => createRoomManager().With(d => d.OnLoadComplete += _ =>
{ {
roomManager.CreateRoom(new Room()); roomManager.CreateRoom(createRoom());
roomManager.PartRoom(); roomManager.PartRoom();
}); });
}); });
@ -126,7 +129,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
createRoomManager().With(d => d.OnLoadComplete += _ => createRoomManager().With(d => d.OnLoadComplete += _ =>
{ {
var r = new Room(); var r = createRoom();
roomManager.CreateRoom(r); roomManager.CreateRoom(r);
roomManager.PartRoom(); roomManager.PartRoom();
roomManager.JoinRoom(r); roomManager.JoinRoom(r);
@ -136,6 +139,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null); AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null);
} }
private Room createRoom(Action<Room> initFunc = null)
{
var room = new Room();
room.Name.Value = "test room";
room.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
Ruleset = { Value = Ruleset.Value }
});
initFunc?.Invoke(room);
return room;
}
private TestMultiplayerRoomManager createRoomManager() private TestMultiplayerRoomManager createRoomManager()
{ {
Child = roomContainer = new TestMultiplayerRoomContainer Child = roomContainer = new TestMultiplayerRoomContainer

View File

@ -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 System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Overlays.Settings;
namespace osu.Game.Tests.Visual.Settings
{
[TestFixture]
public class TestSceneSettingsItem : OsuTestScene
{
[Test]
public void TestRestoreDefaultValueButtonVisibility()
{
TestSettingsTextBox textBox = null;
AddStep("create settings item", () => Child = textBox = new TestSettingsTextBox
{
Current = new Bindable<string>
{
Default = "test",
Value = "test"
}
});
AddAssert("restore button hidden", () => textBox.RestoreDefaultValueButton.Alpha == 0);
AddStep("change value from default", () => textBox.Current.Value = "non-default");
AddUntilStep("restore button shown", () => textBox.RestoreDefaultValueButton.Alpha > 0);
AddStep("restore default", () => textBox.Current.SetDefault());
AddUntilStep("restore button hidden", () => textBox.RestoreDefaultValueButton.Alpha == 0);
}
private class TestSettingsTextBox : SettingsTextBox
{
public new Drawable RestoreDefaultValueButton => this.ChildrenOfType<RestoreDefaultValueButton>().Single();
}
}
}

View File

@ -3,11 +3,11 @@
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="DeepEqual" Version="2.0.0" /> <PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
<PackageReference Include="Moq" Version="4.16.0" /> <PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>

View File

@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
</ItemGroup> </ItemGroup>

View File

@ -20,6 +20,7 @@ using osu.Framework.IO.Stores;
using osu.Framework.Lists; using osu.Framework.Lists;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Statistics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Database; using osu.Game.Database;
@ -311,6 +312,9 @@ namespace osu.Game.Beatmaps
workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this)); workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this));
// best effort; may be higher than expected.
GlobalStatistics.Get<int>(nameof(Beatmaps), $"Cached {nameof(WorkingBeatmap)}s").Value = workingCache.Count();
return working; return working;
} }
} }

View File

@ -12,7 +12,6 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Statistics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -34,8 +33,6 @@ namespace osu.Game.Beatmaps
protected AudioManager AudioManager { get; } protected AudioManager AudioManager { get; }
private static readonly GlobalStatistic<int> total_count = GlobalStatistics.Get<int>(nameof(Beatmaps), $"Total {nameof(WorkingBeatmap)}s");
protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager) protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager)
{ {
AudioManager = audioManager; AudioManager = audioManager;
@ -47,8 +44,6 @@ namespace osu.Game.Beatmaps
waveform = new RecyclableLazy<Waveform>(GetWaveform); waveform = new RecyclableLazy<Waveform>(GetWaveform);
storyboard = new RecyclableLazy<Storyboard>(GetStoryboard); storyboard = new RecyclableLazy<Storyboard>(GetStoryboard);
skin = new RecyclableLazy<ISkin>(GetSkin); skin = new RecyclableLazy<ISkin>(GetSkin);
total_count.Value++;
} }
protected virtual Track GetVirtualTrack(double emptyLength = 0) protected virtual Track GetVirtualTrack(double emptyLength = 0)
@ -331,11 +326,6 @@ namespace osu.Game.Beatmaps
protected virtual ISkin GetSkin() => new DefaultSkin(); protected virtual ISkin GetSkin() => new DefaultSkin();
private readonly RecyclableLazy<ISkin> skin; private readonly RecyclableLazy<ISkin> skin;
~WorkingBeatmap()
{
total_count.Value--;
}
public class RecyclableLazy<T> public class RecyclableLazy<T>
{ {
private Lazy<T> lazy; private Lazy<T> lazy;

View File

@ -54,10 +54,5 @@ namespace osu.Game.Database
Dispose(true); Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
~DatabaseWriteUsage()
{
Dispose(false);
}
} }
} }

View File

@ -9,7 +9,9 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
namespace osu.Game.Online.Multiplayer namespace osu.Game.Online.Multiplayer
@ -121,6 +123,29 @@ namespace osu.Game.Online.Multiplayer
return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch)); return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch));
} }
protected override Task<BeatmapSetInfo> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource<BeatmapSetInfo>();
var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId);
req.Success += res =>
{
if (cancellationToken.IsCancellationRequested)
{
tcs.SetCanceled();
return;
}
tcs.SetResult(res.ToBeatmapSet(Rulesets));
};
req.Failure += e => tcs.SetException(e);
API.Queue(req);
return tcs.Task;
}
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);

View File

@ -17,8 +17,6 @@ using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Online.API; 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.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -71,7 +69,7 @@ namespace osu.Game.Online.Multiplayer
/// <summary> /// <summary>
/// The <see cref="MultiplayerRoomUser"/> corresponding to the local player, if available. /// The <see cref="MultiplayerRoomUser"/> corresponding to the local player, if available.
/// </summary> /// </summary>
public MultiplayerRoomUser? LocalUser => Room?.Users.SingleOrDefault(u => u.User?.Id == api.LocalUser.Value.Id); public MultiplayerRoomUser? LocalUser => Room?.Users.SingleOrDefault(u => u.User?.Id == API.LocalUser.Value.Id);
/// <summary> /// <summary>
/// Whether the <see cref="LocalUser"/> is the host in <see cref="Room"/>. /// Whether the <see cref="LocalUser"/> is the host in <see cref="Room"/>.
@ -85,15 +83,15 @@ namespace osu.Game.Online.Multiplayer
} }
} }
[Resolved]
protected IAPIProvider API { get; private set; } = null!;
[Resolved]
protected RulesetStore Rulesets { get; private set; } = null!;
[Resolved] [Resolved]
private UserLookupCache userLookupCache { get; set; } = null!; private UserLookupCache userLookupCache { get; set; } = null!;
[Resolved]
private IAPIProvider api { get; set; } = null!;
[Resolved]
private RulesetStore rulesets { get; set; } = null!;
// Only exists for compatibility with old osu-server-spectator build. // Only exists for compatibility with old osu-server-spectator build.
// Todo: Can be removed on 2021/02/26. // Todo: Can be removed on 2021/02/26.
private long defaultPlaylistItemId; private long defaultPlaylistItemId;
@ -515,30 +513,26 @@ namespace osu.Game.Online.Multiplayer
RoomUpdated?.Invoke(); RoomUpdated?.Invoke();
var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId); GetOnlineBeatmapSet(settings.BeatmapID, cancellationToken).ContinueWith(set => Schedule(() =>
req.Success += res =>
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
return; return;
updatePlaylist(settings, res); updatePlaylist(settings, set.Result);
}; }), TaskContinuationOptions.OnlyOnRanToCompletion);
api.Queue(req);
}, cancellationToken); }, cancellationToken);
private void updatePlaylist(MultiplayerRoomSettings settings, APIBeatmapSet onlineSet) private void updatePlaylist(MultiplayerRoomSettings settings, BeatmapSetInfo beatmapSet)
{ {
if (Room == null || !Room.Settings.Equals(settings)) if (Room == null || !Room.Settings.Equals(settings))
return; return;
Debug.Assert(apiRoom != null); Debug.Assert(apiRoom != null);
var beatmapSet = onlineSet.ToBeatmapSet(rulesets);
var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineBeatmapID == settings.BeatmapID); var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineBeatmapID == settings.BeatmapID);
beatmap.MD5Hash = settings.BeatmapChecksum; beatmap.MD5Hash = settings.BeatmapChecksum;
var ruleset = rulesets.GetRuleset(settings.RulesetID).CreateInstance(); var ruleset = Rulesets.GetRuleset(settings.RulesetID).CreateInstance();
var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset)); var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset));
var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset));
@ -568,6 +562,14 @@ namespace osu.Game.Online.Multiplayer
} }
} }
/// <summary>
/// Retrieves a <see cref="BeatmapSetInfo"/> from an online source.
/// </summary>
/// <param name="beatmapId">The beatmap set ID.</param>
/// <param name="cancellationToken">A token to cancel the request.</param>
/// <returns>The <see cref="BeatmapSetInfo"/> retrieval task.</returns>
protected abstract Task<BeatmapSetInfo> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// For the provided user ID, update whether the user is included in <see cref="CurrentMatchPlayingUserIds"/>. /// For the provided user ID, update whether the user is included in <see cref="CurrentMatchPlayingUserIds"/>.
/// </summary> /// </summary>

View File

@ -361,14 +361,6 @@ namespace osu.Game
PerformFromScreen(screen => PerformFromScreen(screen =>
{ {
// we might already be at song select, so a check is required before performing the load to solo.
if (screen is MainMenu)
menuScreen.LoadToSolo();
// we might even already be at the song
if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash && (difficultyCriteria?.Invoke(Beatmap.Value.BeatmapInfo) ?? true))
return;
// Find beatmaps that match our predicate. // Find beatmaps that match our predicate.
var beatmaps = databasedSet.Beatmaps.Where(b => difficultyCriteria?.Invoke(b) ?? true).ToList(); var beatmaps = databasedSet.Beatmaps.Where(b => difficultyCriteria?.Invoke(b) ?? true).ToList();
@ -381,9 +373,16 @@ namespace osu.Game
?? beatmaps.FirstOrDefault(b => b.Ruleset.Equals(Ruleset.Value)) ?? beatmaps.FirstOrDefault(b => b.Ruleset.Equals(Ruleset.Value))
?? beatmaps.First(); ?? beatmaps.First();
Ruleset.Value = selection.Ruleset; if (screen is IHandlePresentBeatmap presentableScreen)
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection); {
}, validScreens: new[] { typeof(SongSelect) }); presentableScreen.PresentBeatmap(BeatmapManager.GetWorkingBeatmap(selection), selection.Ruleset);
}
else
{
Ruleset.Value = selection.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection);
}
}, validScreens: new[] { typeof(SongSelect), typeof(IHandlePresentBeatmap) });
} }
/// <summary> /// <summary>
@ -881,13 +880,8 @@ namespace osu.Game
switch (action) switch (action)
{ {
case GlobalAction.ResetInputSettings: case GlobalAction.ResetInputSettings:
var sensitivity = frameworkConfig.GetBindable<double>(FrameworkSetting.CursorSensitivity); frameworkConfig.GetBindable<string>(FrameworkSetting.IgnoredInputHandlers).SetDefault();
frameworkConfig.GetBindable<double>(FrameworkSetting.CursorSensitivity).SetDefault();
sensitivity.Disabled = false;
sensitivity.Value = 1;
sensitivity.Disabled = true;
frameworkConfig.Set(FrameworkSetting.IgnoredInputHandlers, string.Empty);
frameworkConfig.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode).SetDefault(); frameworkConfig.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode).SetDefault();
return true; return true;

View File

@ -0,0 +1,42 @@
// 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.Graphics.Sprites;
namespace osu.Game.Overlays.Dialog
{
/// <summary>
/// A dialog which confirms a user action.
/// </summary>
public class ConfirmDialog : PopupDialog
{
/// <summary>
/// Construct a new confirmation dialog.
/// </summary>
/// <param name="message">The description of the action to be displayed to the user.</param>
/// <param name="onConfirm">An action to perform on confirmation.</param>
/// <param name="onCancel">An optional action to perform on cancel.</param>
public ConfirmDialog(string message, Action onConfirm, Action onCancel = null)
{
HeaderText = message;
BodyText = "Last chance to turn back";
Icon = FontAwesome.Solid.ExclamationTriangle;
Buttons = new PopupDialogButton[]
{
new PopupDialogOkButton
{
Text = @"Yes",
Action = onConfirm
},
new PopupDialogCancelButton
{
Text = @"Cancel",
Action = onCancel
},
};
}
}
}

View File

@ -17,7 +17,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input
protected override string Header => "Mouse"; protected override string Header => "Mouse";
private readonly BindableBool rawInputToggle = new BindableBool(); private readonly BindableBool rawInputToggle = new BindableBool();
private Bindable<double> sensitivityBindable = new BindableDouble();
private Bindable<double> configSensitivity;
private Bindable<double> localSensitivity;
private Bindable<string> ignoredInputHandlers; private Bindable<string> ignoredInputHandlers;
private Bindable<WindowMode> windowMode; private Bindable<WindowMode> windowMode;
@ -26,12 +30,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager osuConfig, FrameworkConfigManager config) private void load(OsuConfigManager osuConfig, FrameworkConfigManager config)
{ {
var configSensitivity = config.GetBindable<double>(FrameworkSetting.CursorSensitivity);
// use local bindable to avoid changing enabled state of game host's bindable. // use local bindable to avoid changing enabled state of game host's bindable.
sensitivityBindable = configSensitivity.GetUnboundCopy(); configSensitivity = config.GetBindable<double>(FrameworkSetting.CursorSensitivity);
configSensitivity.BindValueChanged(val => sensitivityBindable.Value = val.NewValue); localSensitivity = configSensitivity.GetUnboundCopy();
sensitivityBindable.BindValueChanged(val => configSensitivity.Value = val.NewValue);
windowMode = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
ignoredInputHandlers = config.GetBindable<string>(FrameworkSetting.IgnoredInputHandlers);
Children = new Drawable[] Children = new Drawable[]
{ {
@ -43,7 +47,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
new SensitivitySetting new SensitivitySetting
{ {
LabelText = "Cursor sensitivity", LabelText = "Cursor sensitivity",
Current = sensitivityBindable Current = localSensitivity
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
@ -66,14 +70,43 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons) Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons)
}, },
}; };
}
windowMode = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode); protected override void LoadComplete()
windowMode.BindValueChanged(mode => confineMouseModeSetting.Alpha = mode.NewValue == WindowMode.Fullscreen ? 0 : 1, true); {
base.LoadComplete();
configSensitivity.BindValueChanged(val =>
{
var disabled = localSensitivity.Disabled;
localSensitivity.Disabled = false;
localSensitivity.Value = val.NewValue;
localSensitivity.Disabled = disabled;
}, true);
localSensitivity.BindValueChanged(val => configSensitivity.Value = val.NewValue);
windowMode.BindValueChanged(mode =>
{
var isFullscreen = mode.NewValue == WindowMode.Fullscreen;
if (isFullscreen)
{
confineMouseModeSetting.Current.Disabled = true;
confineMouseModeSetting.TooltipText = "Not applicable in full screen mode";
}
else
{
confineMouseModeSetting.Current.Disabled = false;
confineMouseModeSetting.TooltipText = string.Empty;
}
}, true);
if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
{ {
rawInputToggle.Disabled = true; rawInputToggle.Disabled = true;
sensitivityBindable.Disabled = true; localSensitivity.Disabled = true;
} }
else else
{ {
@ -86,12 +119,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input
ignoredInputHandlers.Value = enabled.NewValue ? standard_mouse_handlers : raw_mouse_handler; ignoredInputHandlers.Value = enabled.NewValue ? standard_mouse_handlers : raw_mouse_handler;
}; };
ignoredInputHandlers = config.GetBindable<string>(FrameworkSetting.IgnoredInputHandlers);
ignoredInputHandlers.ValueChanged += handler => ignoredInputHandlers.ValueChanged += handler =>
{ {
bool raw = !handler.NewValue.Contains("Raw"); bool raw = !handler.NewValue.Contains("Raw");
rawInputToggle.Value = raw; rawInputToggle.Value = raw;
sensitivityBindable.Disabled = !raw; localSensitivity.Disabled = !raw;
}; };
ignoredInputHandlers.TriggerChange(); ignoredInputHandlers.TriggerChange();

View File

@ -121,7 +121,7 @@ namespace osu.Game.Overlays.Settings
labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1; labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1;
} }
private class RestoreDefaultValueButton : Container, IHasTooltip protected internal class RestoreDefaultValueButton : Container, IHasTooltip
{ {
private Bindable<T> bindable; private Bindable<T> bindable;
@ -147,6 +147,7 @@ namespace osu.Game.Overlays.Settings
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
Width = SettingsPanel.CONTENT_MARGINS; Width = SettingsPanel.CONTENT_MARGINS;
Alpha = 0f; Alpha = 0f;
AlwaysPresent = true;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -5,11 +5,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
@ -30,9 +28,6 @@ namespace osu.Game
[Resolved] [Resolved]
private DialogOverlay dialogOverlay { get; set; } private DialogOverlay dialogOverlay { get; set; }
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; }
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private OsuGame game { get; set; } private OsuGame game { get; set; }
@ -90,7 +85,7 @@ namespace osu.Game
var type = current.GetType(); var type = current.GetType();
// check if we are already at a valid target screen. // check if we are already at a valid target screen.
if (validScreens.Any(t => t.IsAssignableFrom(type)) && !beatmap.Disabled) if (validScreens.Any(t => t.IsAssignableFrom(type)))
{ {
finalAction(current); finalAction(current);
Cancel(); Cancel();

View File

@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Difficulty
private DifficultyAttributes calculate(IBeatmap beatmap, Mod[] mods, double clockRate) private DifficultyAttributes calculate(IBeatmap beatmap, Mod[] mods, double clockRate)
{ {
var skills = CreateSkills(beatmap); var skills = CreateSkills(beatmap, mods);
if (!beatmap.HitObjects.Any()) if (!beatmap.HitObjects.Any())
return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); return CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
@ -202,7 +202,8 @@ namespace osu.Game.Rulesets.Difficulty
/// Creates the <see cref="Skill"/>s to calculate the difficulty of an <see cref="IBeatmap"/>. /// Creates the <see cref="Skill"/>s to calculate the difficulty of an <see cref="IBeatmap"/>.
/// </summary> /// </summary>
/// <param name="beatmap">The <see cref="IBeatmap"/> whose difficulty will be calculated.</param> /// <param name="beatmap">The <see cref="IBeatmap"/> whose difficulty will be calculated.</param>
/// <param name="mods">Mods to calculate difficulty with.</param>
/// <returns>The <see cref="Skill"/>s.</returns> /// <returns>The <see cref="Skill"/>s.</returns>
protected abstract Skill[] CreateSkills(IBeatmap beatmap); protected abstract Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods);
} }
} }

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Difficulty.Skills namespace osu.Game.Rulesets.Difficulty.Skills
{ {
@ -46,10 +47,22 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// </summary> /// </summary>
protected double CurrentStrain { get; private set; } = 1; protected double CurrentStrain { get; private set; } = 1;
/// <summary>
/// Mods for use in skill calculations.
/// </summary>
protected IReadOnlyList<Mod> Mods => mods;
private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section. private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section.
private readonly List<double> strainPeaks = new List<double>(); private readonly List<double> strainPeaks = new List<double>();
private readonly Mod[] mods;
protected Skill(Mod[] mods)
{
this.mods = mods;
}
/// <summary> /// <summary>
/// Process a <see cref="DifficultyHitObject"/> and update current strain values accordingly. /// Process a <see cref="DifficultyHitObject"/> and update current strain values accordingly.
/// </summary> /// </summary>

View File

@ -150,17 +150,13 @@ namespace osu.Game.Rulesets.Judgements
} }
if (JudgementBody.Drawable is IAnimatableJudgement animatable) if (JudgementBody.Drawable is IAnimatableJudgement animatable)
{
var drawableAnimation = (Drawable)animatable;
animatable.PlayAnimation(); animatable.PlayAnimation();
// a derived version of DrawableJudgement may be proposing a lifetime. // a derived version of DrawableJudgement may be proposing a lifetime.
// if not adjusted (or the skinned portion requires greater bounds than calculated) use the skinned source's lifetime. // if not adjusted (or the skinned portion requires greater bounds than calculated) use the skinned source's lifetime.
double lastTransformTime = drawableAnimation.LatestTransformEndTime; double lastTransformTime = JudgementBody.Drawable.LatestTransformEndTime;
if (LifetimeEnd == double.MaxValue || lastTransformTime > LifetimeEnd) if (LifetimeEnd == double.MaxValue || lastTransformTime > LifetimeEnd)
LifetimeEnd = lastTransformTime; LifetimeEnd = lastTransformTime;
}
} }
} }

View File

@ -63,6 +63,7 @@ namespace osu.Game.Rulesets.UI
~DrawableRulesetDependencies() ~DrawableRulesetDependencies()
{ {
// required to potentially clean up sample store from audio hierarchy.
Dispose(false); Dispose(false);
} }

View File

@ -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 osu.Game.Beatmaps;
using osu.Game.Rulesets;
namespace osu.Game.Screens
{
/// <summary>
/// Denotes a screen which can handle beatmap / ruleset selection via local logic.
/// This is used in the <see cref="OsuGame.PresentBeatmap"/> flow to handle cases which require custom logic,
/// for instance, if a lease is held on the Beatmap.
/// </summary>
public interface IHandlePresentBeatmap
{
/// <summary>
/// Invoked with a requested beatmap / ruleset for selection.
/// </summary>
/// <param name="beatmap">The beatmap to be selected.</param>
/// <param name="ruleset">The ruleset to be selected.</param>
void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset);
}
}

View File

@ -9,10 +9,15 @@ namespace osu.Game.Screens.Menu
{ {
public class ConfirmExitDialog : PopupDialog public class ConfirmExitDialog : PopupDialog
{ {
public ConfirmExitDialog(Action confirm, Action cancel) /// <summary>
/// Construct a new exit confirmation dialog.
/// </summary>
/// <param name="onConfirm">An action to perform on confirmation.</param>
/// <param name="onCancel">An optional action to perform on cancel.</param>
public ConfirmExitDialog(Action onConfirm, Action onCancel = null)
{ {
HeaderText = "Are you sure you want to exit?"; HeaderText = "Are you sure you want to exit osu!?";
BodyText = "Last chance to back out."; BodyText = "Last chance to turn back";
Icon = FontAwesome.Solid.ExclamationTriangle; Icon = FontAwesome.Solid.ExclamationTriangle;
@ -20,13 +25,13 @@ namespace osu.Game.Screens.Menu
{ {
new PopupDialogOkButton new PopupDialogOkButton
{ {
Text = @"Goodbye", Text = @"Let me out!",
Action = confirm Action = onConfirm
}, },
new PopupDialogCancelButton new PopupDialogCancelButton
{ {
Text = @"Just a little more", Text = @"Just a little more...",
Action = cancel Action = onCancel
}, },
}; };
} }

View File

@ -9,12 +9,14 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets;
using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer;
@ -23,7 +25,7 @@ using osu.Game.Screens.Select;
namespace osu.Game.Screens.Menu namespace osu.Game.Screens.Menu
{ {
public class MainMenu : OsuScreen public class MainMenu : OsuScreen, IHandlePresentBeatmap
{ {
public const float FADE_IN_DURATION = 300; public const float FADE_IN_DURATION = 300;
@ -104,7 +106,7 @@ namespace osu.Game.Screens.Menu
Beatmap.SetDefault(); Beatmap.SetDefault();
this.Push(new Editor()); this.Push(new Editor());
}, },
OnSolo = onSolo, OnSolo = loadSoloSongSelect,
OnMultiplayer = () => this.Push(new Multiplayer()), OnMultiplayer = () => this.Push(new Multiplayer()),
OnPlaylists = () => this.Push(new Playlists()), OnPlaylists = () => this.Push(new Playlists()),
OnExit = confirmAndExit, OnExit = confirmAndExit,
@ -160,9 +162,7 @@ namespace osu.Game.Screens.Menu
LoadComponentAsync(songSelect = new PlaySongSelect()); LoadComponentAsync(songSelect = new PlaySongSelect());
} }
public void LoadToSolo() => Schedule(onSolo); private void loadSoloSongSelect() => this.Push(consumeSongSelect());
private void onSolo() => this.Push(consumeSongSelect());
private Screen consumeSongSelect() private Screen consumeSongSelect()
{ {
@ -289,5 +289,13 @@ namespace osu.Game.Screens.Menu
this.FadeOut(3000); this.FadeOut(3000);
return base.OnExiting(next); return base.OnExiting(next);
} }
public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset)
{
Beatmap.Value = beatmap;
Ruleset.Value = ruleset;
Schedule(loadSoloSongSelect);
}
} }
} }

View File

@ -4,9 +4,11 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
@ -19,6 +21,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private LoadingLayer loadingLayer; private LoadingLayer loadingLayer;
/// <summary>
/// Construct a new instance of multiplayer song select.
/// </summary>
/// <param name="beatmap">An optional initial beatmap selection to perform.</param>
/// <param name="ruleset">An optional initial ruleset selection to perform.</param>
public MultiplayerMatchSongSelect(WorkingBeatmap beatmap = null, RulesetInfo ruleset = null)
{
if (beatmap != null || ruleset != null)
{
Schedule(() =>
{
if (beatmap != null) Beatmap.Value = beatmap;
if (ruleset != null) Ruleset.Value = ruleset;
});
}
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {

View File

@ -12,9 +12,13 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match;
@ -29,7 +33,7 @@ using ParticipantsList = osu.Game.Screens.OnlinePlay.Multiplayer.Participants.Pa
namespace osu.Game.Screens.OnlinePlay.Multiplayer namespace osu.Game.Screens.OnlinePlay.Multiplayer
{ {
[Cached] [Cached]
public class MultiplayerMatchSubScreen : RoomSubScreen public class MultiplayerMatchSubScreen : RoomSubScreen, IHandlePresentBeatmap
{ {
public override string Title { get; } public override string Title { get; }
@ -279,14 +283,36 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(ruleset)).Concat(SelectedItem.Value.RequiredMods).ToList(); Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(ruleset)).Concat(SelectedItem.Value.RequiredMods).ToList();
} }
[Resolved(canBeNull: true)]
private DialogOverlay dialogOverlay { get; set; }
private bool exitConfirmed;
public override bool OnBackButton() public override bool OnBackButton()
{ {
if (client.Room != null && settingsOverlay.State.Value == Visibility.Visible) if (client.Room == null)
{
// room has not been created yet; exit immediately.
return base.OnBackButton();
}
if (settingsOverlay.State.Value == Visibility.Visible)
{ {
settingsOverlay.Hide(); settingsOverlay.Hide();
return true; return true;
} }
if (!exitConfirmed && dialogOverlay != null)
{
dialogOverlay.Push(new ConfirmDialog("Are you sure you want to leave this multiplayer match?", () =>
{
exitConfirmed = true;
this.Exit();
}));
return true;
}
return base.OnBackButton(); return base.OnBackButton();
} }
@ -394,5 +420,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
modSettingChangeTracker?.Dispose(); modSettingChangeTracker?.Dispose();
} }
public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset)
{
if (!this.IsCurrentScreen())
return;
if (!client.IsHost)
{
// todo: should handle this when the request queue is implemented.
// if we decide that the presentation should exit the user from the multiplayer game, the PresentBeatmap
// flow may need to change to support an "unable to present" return value.
return;
}
this.Push(new MultiplayerMatchSongSelect(beatmap, ruleset));
}
} }
} }

View File

@ -84,14 +84,15 @@ namespace osu.Game.Screens.Play.HUD
{ {
InternalChildren = new[] InternalChildren = new[]
{ {
displayedCountSpriteText = createSpriteText().With(s =>
{
s.Alpha = 0;
}),
popOutCount = createSpriteText().With(s => popOutCount = createSpriteText().With(s =>
{ {
s.Alpha = 0; s.Alpha = 0;
s.Margin = new MarginPadding(0.05f); s.Margin = new MarginPadding(0.05f);
s.Blending = BlendingParameters.Additive;
}),
displayedCountSpriteText = createSpriteText().With(s =>
{
s.Alpha = 0;
}) })
}; };

View File

@ -36,6 +36,7 @@ namespace osu.Game.Skinning
~Skin() ~Skin()
{ {
// required to potentially clean up sample store from audio hierarchy.
Dispose(false); Dispose(false);
} }

View File

@ -7,8 +7,10 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual.Multiplayer namespace osu.Game.Tests.Visual.Multiplayer
{ {
@ -48,7 +50,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
RoomManager.Schedule(() => RoomManager.PartRoom()); RoomManager.Schedule(() => RoomManager.PartRoom());
if (joinRoom) if (joinRoom)
{
Room.Name.Value = "test name";
Room.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
Ruleset = { Value = Ruleset.Value }
});
RoomManager.Schedule(() => RoomManager.CreateRoom(Room)); RoomManager.Schedule(() => RoomManager.CreateRoom(Room));
}
}); });
public override void SetUpSteps() public override void SetUpSteps()

View File

@ -3,12 +3,15 @@
#nullable enable #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
@ -25,6 +28,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Resolved] [Resolved]
private IAPIProvider api { get; set; } = null!; private IAPIProvider api { get; set; } = null!;
[Resolved]
private BeatmapManager beatmaps { get; set; } = null!;
private readonly TestMultiplayerRoomManager roomManager;
public TestMultiplayerClient(TestMultiplayerRoomManager roomManager)
{
this.roomManager = roomManager;
}
public void Connect() => isConnected.Value = true; public void Connect() => isConnected.Value = true;
public void Disconnect() => isConnected.Value = false; public void Disconnect() => isConnected.Value = false;
@ -89,13 +102,28 @@ namespace osu.Game.Tests.Visual.Multiplayer
protected override Task<MultiplayerRoom> JoinRoom(long roomId) protected override Task<MultiplayerRoom> JoinRoom(long roomId)
{ {
var user = new MultiplayerRoomUser(api.LocalUser.Value.Id) { User = api.LocalUser.Value }; var apiRoom = roomManager.Rooms.Single(r => r.RoomID.Value == roomId);
var room = new MultiplayerRoom(roomId); var user = new MultiplayerRoomUser(api.LocalUser.Value.Id)
room.Users.Add(user); {
User = api.LocalUser.Value
};
if (room.Users.Count == 1) var room = new MultiplayerRoom(roomId)
room.Host = user; {
Settings =
{
Name = apiRoom.Name.Value,
BeatmapID = apiRoom.Playlist.Last().BeatmapID,
RulesetID = apiRoom.Playlist.Last().RulesetID,
BeatmapChecksum = apiRoom.Playlist.Last().Beatmap.Value.MD5Hash,
RequiredMods = apiRoom.Playlist.Last().RequiredMods.Select(m => new APIMod(m)).ToArray(),
AllowedMods = apiRoom.Playlist.Last().AllowedMods.Select(m => new APIMod(m)).ToArray(),
PlaylistItemId = apiRoom.Playlist.Last().ID
},
Users = { user },
Host = user
};
return Task.FromResult(room); return Task.FromResult(room);
} }
@ -150,5 +178,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
return ((IMultiplayerClient)this).LoadRequested(); return ((IMultiplayerClient)this).LoadRequested();
} }
protected override Task<BeatmapSetInfo> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default)
{
Debug.Assert(Room != null);
var apiRoom = roomManager.Rooms.Single(r => r.RoomID.Value == Room.RoomID);
var set = apiRoom.Playlist.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet
?? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId)?.BeatmapSet;
if (set == null)
throw new InvalidOperationException("Beatmap not found.");
return Task.FromResult(set);
}
} }
} }

View File

@ -32,11 +32,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
RoomManager = new TestMultiplayerRoomManager();
Client = new TestMultiplayerClient(RoomManager);
OngoingOperationTracker = new OngoingOperationTracker();
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
Client = new TestMultiplayerClient(), Client,
RoomManager = new TestMultiplayerRoomManager(), RoomManager,
OngoingOperationTracker = new OngoingOperationTracker(), OngoingOperationTracker,
content = new Container { RelativeSizeAxes = Axes.Both } content = new Container { RelativeSizeAxes = Axes.Both }
}); });
} }

View File

@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Cached] [Cached]
public readonly Bindable<FilterCriteria> Filter = new Bindable<FilterCriteria>(new FilterCriteria()); public readonly Bindable<FilterCriteria> Filter = new Bindable<FilterCriteria>(new FilterCriteria());
private readonly List<Room> rooms = new List<Room>(); public new readonly List<Room> Rooms = new List<Room>();
protected override void LoadComplete() protected override void LoadComplete()
{ {
@ -35,6 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
int currentScoreId = 0; int currentScoreId = 0;
int currentRoomId = 0; int currentRoomId = 0;
int currentPlaylistItemId = 0;
((DummyAPIAccess)api).HandleRequest = req => ((DummyAPIAccess)api).HandleRequest = req =>
{ {
@ -46,7 +47,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
createdRoom.CopyFrom(createRoomRequest.Room); createdRoom.CopyFrom(createRoomRequest.Room);
createdRoom.RoomID.Value ??= currentRoomId++; createdRoom.RoomID.Value ??= currentRoomId++;
rooms.Add(createdRoom); for (int i = 0; i < createdRoom.Playlist.Count; i++)
createdRoom.Playlist[i].ID = currentPlaylistItemId++;
Rooms.Add(createdRoom);
createRoomRequest.TriggerSuccess(createdRoom); createRoomRequest.TriggerSuccess(createdRoom);
break; break;
@ -61,7 +65,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
case GetRoomsRequest getRoomsRequest: case GetRoomsRequest getRoomsRequest:
var roomsWithoutParticipants = new List<Room>(); var roomsWithoutParticipants = new List<Room>();
foreach (var r in rooms) foreach (var r in Rooms)
{ {
var newRoom = new Room(); var newRoom = new Room();
@ -75,7 +79,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
break; break;
case GetRoomRequest getRoomRequest: case GetRoomRequest getRoomRequest:
getRoomRequest.TriggerSuccess(rooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId)); getRoomRequest.TriggerSuccess(Rooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId));
break; break;
case GetBeatmapSetRequest getBeatmapSetRequest: case GetBeatmapSetRequest getBeatmapSetRequest:

View File

@ -86,11 +86,6 @@ namespace osu.Game.Utils
#region Disposal #region Disposal
~SentryLogger()
{
Dispose(false);
}
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);

View File

@ -22,17 +22,17 @@
<PackageReference Include="Humanizer" Version="2.8.26" /> <PackageReference Include="Humanizer" Version="2.8.26" />
<PackageReference Include="MessagePack" Version="2.2.85" /> <PackageReference Include="MessagePack" Version="2.2.85" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.2" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.2" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.2" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.3" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.2" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" /> <PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2021.226.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.302.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
<PackageReference Include="Sentry" Version="3.0.1" /> <PackageReference Include="Sentry" Version="3.0.7" />
<PackageReference Include="SharpCompress" Version="0.27.1" /> <PackageReference Include="SharpCompress" Version="0.28.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup> </ItemGroup>

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.226.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.302.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -80,7 +80,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="3.0.3" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="3.0.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.3" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.3" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="3.0.3" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.3" />
<PackageReference Include="MessagePack" Version="1.7.3.7" /> <PackageReference Include="MessagePack" Version="1.7.3.7" />
<PackageReference Include="MessagePack.Annotations" Version="2.2.85" /> <PackageReference Include="MessagePack.Annotations" Version="2.2.85" />
</ItemGroup> </ItemGroup>
@ -91,8 +91,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2021.226.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.302.0" />
<PackageReference Include="SharpCompress" Version="0.27.1" /> <PackageReference Include="SharpCompress" Version="0.28.1" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />