1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 14:17:26 +08:00

Merge branch 'master' into fix-messagepack-union

This commit is contained in:
Dan Balasescu 2021-08-20 16:01:04 +09:00 committed by GitHub
commit 54bc532f0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 1965 additions and 1663 deletions

View File

@ -0,0 +1,34 @@
// 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 BenchmarkDotNet.Attributes;
using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Benchmarks
{
public class BenchmarkMod : BenchmarkTest
{
private OsuModDoubleTime mod;
[Params(1, 10, 100)]
public int Times { get; set; }
[GlobalSetup]
public void GlobalSetup()
{
mod = new OsuModDoubleTime();
}
[Benchmark]
public int ModHashCode()
{
var hashCode = new HashCode();
for (int i = 0; i < Times; i++)
hashCode.Add(mod);
return hashCode.ToHashCode();
}
}
}

View File

@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
protected override bool InterpolateMovements => !disjointTrail;
protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1);
protected override bool AvoidDrawingNearCursor => !disjointTrail;
protected override void Update()
{

View File

@ -138,6 +138,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected virtual bool InterpolateMovements => true;
protected virtual float IntervalMultiplier => 1.0f;
protected virtual bool AvoidDrawingNearCursor => false;
private Vector2? lastPosition;
private readonly InputResampler resampler = new InputResampler();
@ -171,8 +172,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
Vector2 direction = diff / distance;
float interval = partSize.X / 2.5f * IntervalMultiplier;
float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0);
for (float d = interval; d < distance; d += interval)
for (float d = interval; d < stopAt; d += interval)
{
lastPosition = pos1 + direction * d;
addPart(lastPosition.Value);

View File

@ -1,56 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Tests.Beatmaps
{
[TestFixture]
public class BeatmapDifficultyCacheTest
{
[Test]
public void TestKeyEqualsWithDifferentModInstances()
{
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
Assert.That(key1, Is.EqualTo(key2));
}
[Test]
public void TestKeyEqualsWithDifferentModOrder()
{
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
Assert.That(key1, Is.EqualTo(key2));
}
[TestCase(1.3, DifficultyRating.Easy)]
[TestCase(1.993, DifficultyRating.Easy)]
[TestCase(1.998, DifficultyRating.Normal)]
[TestCase(2.4, DifficultyRating.Normal)]
[TestCase(2.693, DifficultyRating.Normal)]
[TestCase(2.698, DifficultyRating.Hard)]
[TestCase(3.5, DifficultyRating.Hard)]
[TestCase(3.993, DifficultyRating.Hard)]
[TestCase(3.997, DifficultyRating.Insane)]
[TestCase(5.0, DifficultyRating.Insane)]
[TestCase(5.292, DifficultyRating.Insane)]
[TestCase(5.297, DifficultyRating.Expert)]
[TestCase(6.2, DifficultyRating.Expert)]
[TestCase(6.493, DifficultyRating.Expert)]
[TestCase(6.498, DifficultyRating.ExpertPlus)]
[TestCase(8.3, DifficultyRating.ExpertPlus)]
public void TestDifficultyRatingMapping(double starRating, DifficultyRating expectedBracket)
{
var actualBracket = BeatmapDifficultyCache.GetDifficultyRating(starRating);
Assert.AreEqual(expectedBracket, actualBracket);
}
}
}

View File

@ -0,0 +1,146 @@
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Beatmaps
{
[HeadlessTest]
public class TestSceneBeatmapDifficultyCache : OsuTestScene
{
public const double BASE_STARS = 5.55;
private BeatmapSetInfo importedSet;
[Resolved]
private BeatmapManager beatmaps { get; set; }
private TestBeatmapDifficultyCache difficultyCache;
private IBindable<StarDifficulty?> starDifficultyBindable;
[BackgroundDependencyLoader]
private void load(OsuGameBase osu)
{
importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).Result;
}
[SetUpSteps]
public void SetUpSteps()
{
AddStep("setup difficulty cache", () =>
{
SelectedMods.Value = Array.Empty<Mod>();
Child = difficultyCache = new TestBeatmapDifficultyCache();
starDifficultyBindable = difficultyCache.GetBindableDifficulty(importedSet.Beatmaps.First());
});
AddUntilStep($"star difficulty -> {BASE_STARS}", () => starDifficultyBindable.Value?.Stars == BASE_STARS);
}
[Test]
public void TestStarDifficultyChangesOnModSettings()
{
OsuModDoubleTime dt = null;
AddStep("change selected mod to DT", () => SelectedMods.Value = new[] { dt = new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } });
AddUntilStep($"star difficulty -> {BASE_STARS + 1.5}", () => starDifficultyBindable.Value?.Stars == BASE_STARS + 1.5);
AddStep("change DT speed to 1.25", () => dt.SpeedChange.Value = 1.25);
AddUntilStep($"star difficulty -> {BASE_STARS + 1.25}", () => starDifficultyBindable.Value?.Stars == BASE_STARS + 1.25);
AddStep("change selected mod to NC", () => SelectedMods.Value = new[] { new OsuModNightcore { SpeedChange = { Value = 1.75 } } });
AddUntilStep($"star difficulty -> {BASE_STARS + 1.75}", () => starDifficultyBindable.Value?.Stars == BASE_STARS + 1.75);
}
[Test]
public void TestKeyEqualsWithDifferentModInstances()
{
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
Assert.That(key1, Is.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
}
[Test]
public void TestKeyEqualsWithDifferentModOrder()
{
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
Assert.That(key1, Is.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
}
[Test]
public void TestKeyDoesntEqualWithDifferentModSettings()
{
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } });
Assert.That(key1, Is.Not.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.Not.EqualTo(key2.GetHashCode()));
}
[Test]
public void TestKeyEqualWithMatchingModSettings()
{
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
Assert.That(key1, Is.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
}
[TestCase(1.3, DifficultyRating.Easy)]
[TestCase(1.993, DifficultyRating.Easy)]
[TestCase(1.998, DifficultyRating.Normal)]
[TestCase(2.4, DifficultyRating.Normal)]
[TestCase(2.693, DifficultyRating.Normal)]
[TestCase(2.698, DifficultyRating.Hard)]
[TestCase(3.5, DifficultyRating.Hard)]
[TestCase(3.993, DifficultyRating.Hard)]
[TestCase(3.997, DifficultyRating.Insane)]
[TestCase(5.0, DifficultyRating.Insane)]
[TestCase(5.292, DifficultyRating.Insane)]
[TestCase(5.297, DifficultyRating.Expert)]
[TestCase(6.2, DifficultyRating.Expert)]
[TestCase(6.493, DifficultyRating.Expert)]
[TestCase(6.498, DifficultyRating.ExpertPlus)]
[TestCase(8.3, DifficultyRating.ExpertPlus)]
public void TestDifficultyRatingMapping(double starRating, DifficultyRating expectedBracket)
{
var actualBracket = BeatmapDifficultyCache.GetDifficultyRating(starRating);
Assert.AreEqual(expectedBracket, actualBracket);
}
private class TestBeatmapDifficultyCache : BeatmapDifficultyCache
{
protected override Task<StarDifficulty> ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default)
{
var rateAdjust = lookup.OrderedMods.OfType<ModRateAdjust>().SingleOrDefault();
if (rateAdjust != null)
return Task.FromResult(new StarDifficulty(BASE_STARS + rateAdjust.SpeedChange.Value, 0));
return Task.FromResult(new StarDifficulty(BASE_STARS, 0));
}
}
}
}

View File

@ -78,6 +78,24 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
}
[Test]
public void TestClampWhenSeekOutsideBeatmapBounds()
{
AddStep("stop clock", Clock.Stop);
AddStep("seek before start time", () => Clock.Seek(-1000));
AddAssert("time is clamped to 0", () => Clock.CurrentTime == 0);
AddStep("seek beyond track length", () => Clock.Seek(Clock.TrackLength + 1000));
AddAssert("time is clamped to track length", () => Clock.CurrentTime == Clock.TrackLength);
AddStep("seek smoothly before start time", () => Clock.SeekSmoothlyTo(-1000));
AddAssert("time is clamped to 0", () => Clock.CurrentTime == 0);
AddStep("seek smoothly beyond track length", () => Clock.SeekSmoothlyTo(Clock.TrackLength + 1000));
AddAssert("time is clamped to track length", () => Clock.CurrentTime == Clock.TrackLength);
}
protected override void Dispose(bool isDisposing)
{
Beatmap.Disabled = false;

View File

@ -3,7 +3,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Game.Overlays;
using osu.Game.Rulesets;
@ -66,7 +65,6 @@ namespace osu.Game.Tests.Visual.Gameplay
protected class OverlayTestPlayer : TestPlayer
{
public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value;
public new Bindable<bool> LocalUserPlaying => base.LocalUserPlaying;
}
}
}

View File

@ -10,11 +10,11 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
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.Tests.Beatmaps;
using osu.Game.Users;
@ -108,12 +108,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
EndDate = { Value = DateTimeOffset.Now },
}),
createDrawableRoom(new Room
{
Name = { Value = "Room 4 (realtime)" },
Status = { Value = new RoomStatusOpen() },
Category = { Value = RoomCategory.Realtime },
}),
createDrawableRoom(new Room
{
Name = { Value = "Room 4 (spotlight)" },
Status = { Value = new RoomStatusOpen() },
@ -134,7 +128,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Name = { Value = "Room with password" },
Status = { Value = new RoomStatusOpen() },
Category = { Value = RoomCategory.Realtime },
Type = { Value = MatchType.HeadToHead },
}));
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
@ -159,10 +153,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}));
}
var drawableRoom = new DrawableRoom(room) { MatchingFilter = true };
drawableRoom.Action = () => drawableRoom.State = drawableRoom.State == SelectionState.Selected ? SelectionState.NotSelected : SelectionState.Selected;
return drawableRoom;
return new DrawableLoungeRoom(room) { MatchingFilter = true };
}
}
}

View File

@ -0,0 +1,131 @@
// 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 Moq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.Play;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneGameplayChatDisplay : MultiplayerTestScene
{
private GameplayChatDisplay chatDisplay;
[Cached(typeof(ILocalUserPlayInfo))]
private ILocalUserPlayInfo localUserInfo;
private readonly Bindable<bool> localUserPlaying = new Bindable<bool>();
private TextBox textBox => chatDisplay.ChildrenOfType<TextBox>().First();
public TestSceneGameplayChatDisplay()
{
var mockLocalUserInfo = new Mock<ILocalUserPlayInfo>();
mockLocalUserInfo.SetupGet(i => i.IsPlaying).Returns(localUserPlaying);
localUserInfo = mockLocalUserInfo.Object;
}
[SetUpSteps]
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("load chat display", () => Child = chatDisplay = new GameplayChatDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 0.5f,
});
AddStep("expand", () => chatDisplay.Expanded.Value = true);
}
[Test]
public void TestCantClickWhenPlaying()
{
setLocalUserPlaying(true);
AddStep("attempt focus chat", () =>
{
InputManager.MoveMouseTo(textBox);
InputManager.Click(MouseButton.Left);
});
assertChatFocused(false);
}
[Test]
public void TestFocusDroppedWhenPlaying()
{
assertChatFocused(false);
AddStep("focus chat", () =>
{
InputManager.MoveMouseTo(textBox);
InputManager.Click(MouseButton.Left);
});
setLocalUserPlaying(true);
assertChatFocused(false);
// should still stay non-focused even after entering a new break section.
setLocalUserPlaying(false);
assertChatFocused(false);
}
[Test]
public void TestFocusOnTabKeyWhenExpanded()
{
setLocalUserPlaying(true);
assertChatFocused(false);
AddStep("press tab", () => InputManager.Key(Key.Tab));
assertChatFocused(true);
}
[Test]
public void TestFocusOnTabKeyWhenNotExpanded()
{
AddStep("set not expanded", () => chatDisplay.Expanded.Value = false);
AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
AddStep("press tab", () => InputManager.Key(Key.Tab));
assertChatFocused(true);
AddUntilStep("is visible", () => chatDisplay.IsPresent);
AddStep("press enter", () => InputManager.Key(Key.Enter));
assertChatFocused(false);
AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
}
[Test]
public void TestFocusToggleViaAction()
{
AddStep("set not expanded", () => chatDisplay.Expanded.Value = false);
AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
AddStep("press tab", () => InputManager.Key(Key.Tab));
assertChatFocused(true);
AddUntilStep("is visible", () => chatDisplay.IsPresent);
AddStep("press tab", () => InputManager.Key(Key.Tab));
assertChatFocused(false);
AddUntilStep("is not visible", () => !chatDisplay.IsPresent);
}
private void assertChatFocused(bool isFocused) =>
AddAssert($"chat {(isFocused ? "focused" : "not focused")}", () => textBox.HasFocus == isFocused);
private void setLocalUserPlaying(bool playing) =>
AddStep($"local user {(playing ? "playing" : "not playing")}", () => localUserPlaying.Value = playing);
}
}

View File

@ -9,6 +9,7 @@ using osu.Framework.Testing;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Tests.Visual.OnlinePlay;
using osuTK.Input;
@ -17,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneLoungeRoomsContainer : OnlinePlayTestScene
{
protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager;
protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
private RoomsContainer container;
@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("add rooms", () => RoomManager.AddRooms(3));
AddAssert("has 3 rooms", () => container.Rooms.Count == 3);
AddStep("remove first room", () => RoomManager.Rooms.Remove(RoomManager.Rooms.FirstOrDefault()));
AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.FirstOrDefault()));
AddAssert("has 2 rooms", () => container.Rooms.Count == 2);
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0));
@ -150,6 +151,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
private bool checkRoomSelected(Room room) => SelectedRoom.Value == room;
private Room getRoomInFlow(int index) =>
(container.ChildrenOfType<FillFlowContainer<DrawableRoom>>().First().FlowingChildren.ElementAt(index) as DrawableRoom)?.Room;
(container.ChildrenOfType<FillFlowContainer<DrawableLoungeRoom>>().First().FlowingChildren.ElementAt(index) as DrawableRoom)?.Room;
}
}

View File

@ -1,55 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchHeader : OnlinePlayTestScene
{
[SetUp]
public new void Setup() => Schedule(() =>
{
SelectedRoom.Value = new Room
{
Name = { Value = "A very awesome room" },
Host = { Value = new User { Id = 2, Username = "peppy" } },
Playlist =
{
new PlaylistItem
{
Beatmap =
{
Value = new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
Title = "Title",
Artist = "Artist",
AuthorString = "Author",
},
Version = "Version",
Ruleset = new OsuRuleset().RulesetInfo
}
},
RequiredMods =
{
new OsuModDoubleTime(),
new OsuModNoFail(),
new OsuModRelax(),
}
}
}
};
Child = new Header();
});
}
}

View File

@ -236,8 +236,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("join room", () => InputManager.Key(Key.Enter));
DrawableRoom.PasswordEntryPopover passwordEntryPopover = null;
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.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());

View File

@ -9,7 +9,6 @@ using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Tests.Visual.OnlinePlay;
using osuTK.Input;
@ -18,7 +17,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerLoungeSubScreen : OnlinePlayTestScene
{
protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager;
protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
private LoungeSubScreen loungeScreen;
@ -59,20 +58,20 @@ 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<DrawableRoom.PasswordEntryPopover>().Any());
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
AddStep("exit screen", () => Stack.Exit());
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().Any());
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
}
[Test]
public void TestJoinRoomWithPassword()
{
DrawableRoom.PasswordEntryPopover passwordEntryPopover = null;
DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
AddStep("add room", () => RoomManager.AddRooms(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<DrawableRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.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());
@ -83,12 +82,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestJoinRoomWithPasswordViaKeyboardOnly()
{
DrawableRoom.PasswordEntryPopover passwordEntryPopover = null;
DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
AddStep("add room", () => RoomManager.AddRooms(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<DrawableRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
AddStep("press enter", () => InputManager.Key(Key.Enter));

View File

@ -59,23 +59,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for load", () => screen.IsCurrentScreen());
}
[Test]
public void TestSettingValidity()
{
AddAssert("create button not enabled", () => !this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single().Enabled.Value);
AddStep("set playlist", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
});
});
AddAssert("create button enabled", () => this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single().Enabled.Value);
}
[Test]
public void TestCreatedRoom()
{
@ -97,6 +80,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for join", () => Client.Room != null);
}
[Test]
public void TestSettingValidity()
{
AddAssert("create button not enabled", () => !this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single().Enabled.Value);
AddStep("set playlist", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
});
});
AddAssert("create button enabled", () => this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single().Enabled.Value);
}
[Test]
public void TestStartMatchWhileSpectating()
{

View File

@ -0,0 +1,38 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Multiplayer;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerPlayer : MultiplayerTestScene
{
private MultiplayerPlayer player;
[SetUpSteps]
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("set beatmap", () =>
{
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
});
AddStep("initialise gameplay", () =>
{
Stack.Push(player = new MultiplayerPlayer(Client.CurrentMatchPlayingItem.Value, Client.Room?.Users.ToArray()));
});
}
[Test]
public void TestGameplay()
{
AddUntilStep("wait for gameplay start", () => player.LocalUserPlaying.Value);
}
}
}

View File

@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
public class TestScenePlaylistsLoungeSubScreen : OnlinePlayTestScene
{
protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager;
protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
private LoungeSubScreen loungeScreen;
@ -37,6 +37,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("reset mouse", () => InputManager.ReleaseButton(MouseButton.Left));
AddStep("add rooms", () => RoomManager.AddRooms(30));
AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 30);
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0]));
@ -53,6 +54,7 @@ namespace osu.Game.Tests.Visual.Playlists
public void TestScrollSelectedIntoView()
{
AddStep("add rooms", () => RoomManager.AddRooms(30));
AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 30);
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0]));

View File

@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddUntilStep("error not displayed", () => !settings.ErrorText.IsPresent);
}
private class TestRoomSettings : PlaylistsMatchSettingsOverlay
private class TestRoomSettings : PlaylistsRoomSettingsOverlay
{
public TriangleButton ApplyButton => ((MatchSettings)Settings).ApplyButton;

View File

@ -11,7 +11,6 @@ using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
@ -19,7 +18,6 @@ using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Playlists
@ -36,18 +34,6 @@ namespace osu.Game.Tests.Visual.Playlists
{
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
((DummyAPIAccess)API).HandleRequest = req =>
{
switch (req)
{
case CreateRoomScoreRequest createRoomScoreRequest:
createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 });
return true;
}
return false;
};
}
[SetUpSteps]
@ -66,7 +52,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
SelectedRoom.Value.RoomID.Value = 1;
SelectedRoom.Value.Name.Value = "my awesome room";
SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" };
SelectedRoom.Value.Host.Value = API.LocalUser.Value;
SelectedRoom.Value.RecentParticipants.Add(SelectedRoom.Value.Host.Value);
SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
SelectedRoom.Value.Playlist.Add(new PlaylistItem
@ -86,7 +72,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("set room properties", () =>
{
SelectedRoom.Value.Name.Value = "my awesome room";
SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" };
SelectedRoom.Value.Host.Value = API.LocalUser.Value;
SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
@ -96,7 +82,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("move mouse to create button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<PlaylistsMatchSettingsOverlay.CreateRoomButton>().Single());
InputManager.MoveMouseTo(this.ChildrenOfType<PlaylistsRoomSettingsOverlay.CreateRoomButton>().Single());
});
AddStep("click", () => InputManager.Click(MouseButton.Left));
@ -137,7 +123,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("load room", () =>
{
SelectedRoom.Value.Name.Value = "my awesome room";
SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" };
SelectedRoom.Value.Host.Value = API.LocalUser.Value;
SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = importedSet.Beatmaps[0] },
@ -147,7 +133,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("create room", () =>
{
InputManager.MoveMouseTo(match.ChildrenOfType<PlaylistsMatchSettingsOverlay.CreateRoomButton>().Single());
InputManager.MoveMouseTo(match.ChildrenOfType<PlaylistsRoomSettingsOverlay.CreateRoomButton>().Single());
InputManager.Click(MouseButton.Left);
});

View File

@ -76,5 +76,23 @@ namespace osu.Game.Tests.Visual.Settings
AddStep("restore default", () => sliderBar.Current.SetDefault());
AddUntilStep("restore button hidden", () => restoreDefaultValueButton.Alpha == 0);
}
[Test]
public void TestWarningTextVisibility()
{
SettingsNumberBox numberBox = null;
AddStep("create settings item", () => Child = numberBox = new SettingsNumberBox());
AddAssert("warning text not created", () => !numberBox.ChildrenOfType<SettingsNoticeText>().Any());
AddStep("set warning text", () => numberBox.WarningText = "this is a warning!");
AddAssert("warning text created", () => numberBox.ChildrenOfType<SettingsNoticeText>().Single().Alpha == 1);
AddStep("unset warning text", () => numberBox.WarningText = default);
AddAssert("warning text hidden", () => numberBox.ChildrenOfType<SettingsNoticeText>().Single().Alpha == 0);
AddStep("set warning text again", () => numberBox.WarningText = "another warning!");
AddAssert("warning text shown again", () => numberBox.ChildrenOfType<SettingsNoticeText>().Single().Alpha == 1);
}
}
}

View File

@ -8,6 +8,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
@ -65,6 +66,12 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("show", () => { infoWedge.Show(); });
AddSliderStep("change star difficulty", 0, 11.9, 5.55, v =>
{
foreach (var hasCurrentValue in infoWedge.Info.ChildrenOfType<IHasCurrentValue<StarDifficulty>>())
hasCurrentValue.Current.Value = new StarDifficulty(v, 0);
});
foreach (var rulesetInfo in rulesets.AvailableRulesets)
{
var instance = rulesetInfo.CreateInstance();

View File

@ -3,7 +3,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
@ -50,32 +49,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
StarRatingDisplay starRating = null;
BindableDouble starRatingNumeric;
AddStep("load display", () =>
{
Child = starRating = new StarRatingDisplay(new StarDifficulty(5.55, 1))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(3f),
};
});
AddStep("transform over spectrum", () =>
{
starRatingNumeric = new BindableDouble();
starRatingNumeric.BindValueChanged(val => starRating.Current.Value = new StarDifficulty(val.NewValue, 1));
this.TransformBindableTo(starRatingNumeric, 10, 10000, Easing.OutQuint);
});
}
[Test]
public void TestChangingStarRatingDisplay()
{
StarRatingDisplay starRating = null;
AddStep("load display", () => Child = starRating = new StarRatingDisplay(new StarDifficulty(5.55, 1))
AddStep("load display", () => Child = starRating = new StarRatingDisplay(new StarDifficulty(5.55, 1), animated: true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,

View File

@ -14,6 +14,7 @@ using osu.Framework.Lists;
using osu.Framework.Logging;
using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@ -56,12 +57,28 @@ namespace osu.Game.Beatmaps
[Resolved]
private Bindable<IReadOnlyList<Mod>> currentMods { get; set; }
private ModSettingChangeTracker modSettingChangeTracker;
private ScheduledDelegate debouncedModSettingsChange;
protected override void LoadComplete()
{
base.LoadComplete();
currentRuleset.BindValueChanged(_ => updateTrackedBindables());
currentMods.BindValueChanged(_ => updateTrackedBindables(), true);
currentMods.BindValueChanged(mods =>
{
modSettingChangeTracker?.Dispose();
updateTrackedBindables();
modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue);
modSettingChangeTracker.SettingChanged += _ =>
{
debouncedModSettingsChange?.Cancel();
debouncedModSettingsChange = Scheduler.AddDelayed(updateTrackedBindables, 100);
};
}, true);
}
/// <summary>
@ -84,7 +101,7 @@ namespace osu.Game.Beatmaps
/// Retrieves a bindable containing the star difficulty of a <see cref="BeatmapInfo"/> with a given <see cref="RulesetInfo"/> and <see cref="Mod"/> combination.
/// </summary>
/// <remarks>
/// The bindable will not update to follow the currently-selected ruleset and mods.
/// The bindable will not update to follow the currently-selected ruleset and mods or its settings.
/// </remarks>
/// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> to get the difficulty of.</param>
/// <param name="rulesetInfo">The <see cref="RulesetInfo"/> to get the difficulty with. If <c>null</c>, the <paramref name="beatmapInfo"/>'s ruleset is used.</param>
@ -275,6 +292,8 @@ namespace osu.Game.Beatmaps
{
base.Dispose(isDisposing);
modSettingChangeTracker?.Dispose();
cancelTrackedBindableUpdate();
updateScheduler?.Dispose();
}
@ -297,7 +316,7 @@ namespace osu.Game.Beatmaps
public bool Equals(DifficultyCacheLookup other)
=> Beatmap.ID == other.Beatmap.ID
&& Ruleset.ID == other.Ruleset.ID
&& OrderedMods.Select(m => m.Acronym).SequenceEqual(other.OrderedMods.Select(m => m.Acronym));
&& OrderedMods.SequenceEqual(other.OrderedMods);
public override int GetHashCode()
{
@ -307,7 +326,7 @@ namespace osu.Game.Beatmaps
hashCode.Add(Ruleset.ID);
foreach (var mod in OrderedMods)
hashCode.Add(mod.Acronym);
hashCode.Add(mod);
return hashCode.ToHashCode();
}

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
@ -22,6 +23,7 @@ namespace osu.Game.Beatmaps.Drawables
/// </summary>
public class StarRatingDisplay : CompositeDrawable, IHasCurrentValue<StarDifficulty>
{
private readonly bool animated;
private readonly Box background;
private readonly SpriteIcon starIcon;
private readonly OsuSpriteText starsText;
@ -34,6 +36,14 @@ namespace osu.Game.Beatmaps.Drawables
set => current.Current = value;
}
private readonly Bindable<double> displayedStars = new BindableDouble();
/// <summary>
/// The currently displayed stars of this display wrapped in a bindable.
/// This bindable gets transformed on change rather than instantaneous, if animation is enabled.
/// </summary>
public IBindable<double> DisplayedStars => displayedStars;
[Resolved]
private OsuColour colours { get; set; }
@ -45,8 +55,11 @@ namespace osu.Game.Beatmaps.Drawables
/// </summary>
/// <param name="starDifficulty">The already computed <see cref="StarDifficulty"/> to display.</param>
/// <param name="size">The size of the star rating display.</param>
public StarRatingDisplay(StarDifficulty starDifficulty, StarRatingDisplaySize size = StarRatingDisplaySize.Regular)
/// <param name="animated">Whether the star rating display will perform transforms on change rather than updating instantaneously.</param>
public StarRatingDisplay(StarDifficulty starDifficulty, StarRatingDisplaySize size = StarRatingDisplaySize.Regular, bool animated = false)
{
this.animated = animated;
Current.Value = starDifficulty;
AutoSizeAxes = Axes.Both;
@ -112,7 +125,7 @@ namespace osu.Game.Beatmaps.Drawables
// see https://github.com/ppy/osu-framework/issues/3271.
Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold),
Shadow = false,
}
},
}
}
},
@ -126,12 +139,22 @@ namespace osu.Game.Beatmaps.Drawables
Current.BindValueChanged(c =>
{
starsText.Text = c.NewValue.Stars.ToString("0.00");
if (animated)
this.TransformBindableTo(displayedStars, c.NewValue.Stars, 750, Easing.OutQuint);
else
displayedStars.Value = c.NewValue.Stars;
});
background.Colour = colours.ForStarDifficulty(c.NewValue.Stars);
displayedStars.Value = Current.Value.Stars;
starIcon.Colour = c.NewValue.Stars >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4Extensions.FromHex("303d47");
starsText.Colour = c.NewValue.Stars >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4.Black.Opacity(0.75f);
displayedStars.BindValueChanged(s =>
{
starsText.Text = s.NewValue.ToLocalisableString("0.00");
background.Colour = colours.ForStarDifficulty(s.NewValue);
starIcon.Colour = s.NewValue >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4Extensions.FromHex("303d47");
starsText.Colour = s.NewValue >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4.Black.Opacity(0.75f);
}, true);
}
}

View File

@ -28,6 +28,8 @@ namespace osu.Game.Graphics.UserInterface
Scheduler.Add(() => GetContainingInputManager().ChangeFocus(this), false);
}
public new void KillFocus() => base.KillFocus();
public bool HoldFocus
{
get => allowImmediateFocus && focus;

View File

@ -149,7 +149,7 @@ namespace osu.Game.Graphics.UserInterface
glowColour = value;
var effect = EdgeEffect;
effect.Colour = value;
effect.Colour = Glowing ? value : value.Opacity(0);
EdgeEffect = effect;
}
}

View File

@ -90,6 +90,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.Left, GlobalAction.SeekReplayBackward),
new KeyBinding(InputKey.Right, GlobalAction.SeekReplayForward),
new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD),
new KeyBinding(InputKey.Tab, GlobalAction.ToggleChatFocus),
};
public IEnumerable<KeyBinding> SongSelectKeyBindings => new[]
@ -280,5 +281,8 @@ namespace osu.Game.Input.Bindings
[Description("Seek replay backward")]
SeekReplayBackward,
[Description("Toggle chat focus")]
ToggleChatFocus
}
}

View File

@ -305,9 +305,11 @@ namespace osu.Game.Online.API
{
req.Perform(this);
if (req.CompletionState != APIRequestCompletionState.Completed)
return false;
// we could still be in initialisation, at which point we don't want to say we're Online yet.
if (IsLoggedIn) state.Value = APIState.Online;
failureCount = 0;
return true;
}
@ -381,7 +383,7 @@ namespace osu.Game.Online.API
}
}
public bool IsLoggedIn => localUser.Value.Id > 1;
public bool IsLoggedIn => localUser.Value.Id > 1; // TODO: should this also be true if attempting to connect?
public void Queue(APIRequest request)
{

View File

@ -84,7 +84,7 @@ namespace osu.Game.Online.API
/// The state of this request, from an outside perspective.
/// This is used to ensure correct notification events are fired.
/// </summary>
private APIRequestCompletionState completionState;
public APIRequestCompletionState CompletionState { get; private set; }
public void Perform(IAPIProvider api)
{
@ -127,10 +127,10 @@ namespace osu.Game.Online.API
{
lock (completionStateLock)
{
if (completionState != APIRequestCompletionState.Waiting)
if (CompletionState != APIRequestCompletionState.Waiting)
return;
completionState = APIRequestCompletionState.Completed;
CompletionState = APIRequestCompletionState.Completed;
}
if (API == null)
@ -143,10 +143,10 @@ namespace osu.Game.Online.API
{
lock (completionStateLock)
{
if (completionState != APIRequestCompletionState.Waiting)
if (CompletionState != APIRequestCompletionState.Waiting)
return;
completionState = APIRequestCompletionState.Failed;
CompletionState = APIRequestCompletionState.Failed;
}
if (API == null)
@ -161,7 +161,7 @@ namespace osu.Game.Online.API
{
lock (completionStateLock)
{
if (completionState != APIRequestCompletionState.Waiting)
if (CompletionState != APIRequestCompletionState.Waiting)
return;
WebRequest?.Abort();
@ -200,7 +200,7 @@ namespace osu.Game.Online.API
get
{
lock (completionStateLock)
return completionState == APIRequestCompletionState.Failed;
return CompletionState == APIRequestCompletionState.Failed;
}
}

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Chat;
@ -22,7 +23,7 @@ namespace osu.Game.Online.Chat
{
public readonly Bindable<Channel> Channel = new Bindable<Channel>();
private readonly FocusedTextBox textbox;
protected readonly ChatTextBox Textbox;
protected ChannelManager ChannelManager;
@ -30,6 +31,8 @@ namespace osu.Game.Online.Chat
private readonly bool postingTextbox;
protected readonly Box Background;
private const float textbox_height = 30;
/// <summary>
@ -44,7 +47,7 @@ namespace osu.Game.Online.Chat
InternalChildren = new Drawable[]
{
new Box
Background = new Box
{
Colour = Color4.Black,
Alpha = 0.8f,
@ -54,7 +57,7 @@ namespace osu.Game.Online.Chat
if (postingTextbox)
{
AddInternal(textbox = new FocusedTextBox
AddInternal(Textbox = new ChatTextBox
{
RelativeSizeAxes = Axes.X,
Height = textbox_height,
@ -65,7 +68,7 @@ namespace osu.Game.Online.Chat
Origin = Anchor.BottomLeft,
});
textbox.OnCommit += postMessage;
Textbox.OnCommit += postMessage;
}
Channel.BindValueChanged(channelChanged);
@ -82,7 +85,7 @@ namespace osu.Game.Online.Chat
private void postMessage(TextBox sender, bool newtext)
{
var text = textbox.Text.Trim();
var text = Textbox.Text.Trim();
if (string.IsNullOrWhiteSpace(text))
return;
@ -92,7 +95,7 @@ namespace osu.Game.Online.Chat
else
ChannelManager?.PostMessage(text, target: Channel.Value);
textbox.Text = string.Empty;
Textbox.Text = string.Empty;
}
protected virtual ChatLine CreateMessage(Message message) => new StandAloneMessage(message);
@ -110,6 +113,25 @@ namespace osu.Game.Online.Chat
AddInternal(drawableChannel);
}
public class ChatTextBox : FocusedTextBox
{
protected override void LoadComplete()
{
base.LoadComplete();
BackgroundUnfocused = new Color4(10, 10, 10, 10);
BackgroundFocused = new Color4(10, 10, 10, 255);
}
protected override void OnFocusLost(FocusLostEvent e)
{
base.OnFocusLost(e);
FocusLost?.Invoke();
}
public Action FocusLost;
}
public class StandAloneDrawableChannel : DrawableChannel
{
public Func<Message, ChatLine> CreateChatLineAction;

View File

@ -8,6 +8,5 @@ namespace osu.Game.Online.Rooms
// used for osu-web deserialization so names shouldn't be changed.
Normal,
Spotlight,
Realtime,
}
}

View File

@ -65,7 +65,7 @@ namespace osu.Game.Overlays.Settings
{
set
{
bool hasValue = string.IsNullOrWhiteSpace(value.ToString());
bool hasValue = !string.IsNullOrWhiteSpace(value.ToString());
if (warningText == null)
{
@ -76,7 +76,7 @@ namespace osu.Game.Overlays.Settings
FlowContent.Add(warningText = new SettingsNoticeText(colours) { Margin = new MarginPadding { Bottom = 5 } });
}
warningText.Alpha = hasValue ? 0 : 1;
warningText.Alpha = hasValue ? 1 : 0;
warningText.Text = value.ToString(); // TODO: Remove ToString() call after TextFlowContainer supports localisation (see https://github.com/ppy/osu-framework/issues/4636).
}
}

View File

@ -129,6 +129,17 @@ namespace osu.Game.Rulesets.Mods
[JsonIgnore]
public virtual Type[] IncompatibleMods => Array.Empty<Type>();
private IReadOnlyList<IBindable> settingsBacking;
/// <summary>
/// A list of the all <see cref="IBindable"/> settings within this mod.
/// </summary>
internal IReadOnlyList<IBindable> Settings =>
settingsBacking ??= this.GetSettingsSourceProperties()
.Select(p => p.Item2.GetValue(this))
.Cast<IBindable>()
.ToList();
/// <summary>
/// Creates a copy of this <see cref="Mod"/> initialised to a default state.
/// </summary>
@ -191,15 +202,39 @@ namespace osu.Game.Rulesets.Mods
if (ReferenceEquals(this, other)) return true;
return GetType() == other.GetType() &&
this.GetSettingsSourceProperties().All(pair =>
EqualityComparer<object>.Default.Equals(
ModUtils.GetSettingUnderlyingValue(pair.Item2.GetValue(this)),
ModUtils.GetSettingUnderlyingValue(pair.Item2.GetValue(other))));
Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default);
}
public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add(GetType());
foreach (var setting in Settings)
hashCode.Add(ModUtils.GetSettingUnderlyingValue(setting));
return hashCode.ToHashCode();
}
/// <summary>
/// Reset all custom settings for this mod back to their defaults.
/// </summary>
public virtual void ResetSettingsToDefaults() => CopyFrom((Mod)Activator.CreateInstance(GetType()));
private class ModSettingsEqualityComparer : IEqualityComparer<IBindable>
{
public static ModSettingsEqualityComparer Default { get; } = new ModSettingsEqualityComparer();
public bool Equals(IBindable x, IBindable y)
{
object xValue = x == null ? null : ModUtils.GetSettingUnderlyingValue(x);
object yValue = y == null ? null : ModUtils.GetSettingUnderlyingValue(y);
return EqualityComparer<object>.Default.Equals(xValue, yValue);
}
public int GetHashCode(IBindable obj) => ModUtils.GetSettingUnderlyingValue(obj).GetHashCode();
}
}
}

View File

@ -150,8 +150,6 @@ namespace osu.Game.Screens.Edit
if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First())
seekTime = timingPoint.Time;
// Ensure the sought point is within the boundaries
seekTime = Math.Clamp(seekTime, 0, TrackLength);
SeekSmoothlyTo(seekTime);
}
@ -190,6 +188,9 @@ namespace osu.Game.Screens.Edit
seekingOrStopped.Value = IsSeeking = true;
ClearTransforms();
// Ensure the sought point is within the boundaries
position = Math.Clamp(position, 0, TrackLength);
return underlyingClock.Seek(position);
}
@ -288,7 +289,7 @@ namespace osu.Game.Screens.Edit
}
private void transformSeekTo(double seek, double duration = 0, Easing easing = Easing.None)
=> this.TransformTo(this.PopulateTransform(new TransformSeek(), seek, duration, easing));
=> this.TransformTo(this.PopulateTransform(new TransformSeek(), Math.Clamp(seek, 0, TrackLength), duration, easing));
private double currentTime
{

View File

@ -1,32 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
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.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Input.Bindings;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.Components;
@ -35,105 +22,26 @@ using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public class DrawableRoom : OsuClickableContainer, IStateful<SelectionState>, IFilterable, IHasContextMenu, IHasPopover, IKeyBindingHandler<GlobalAction>
public class DrawableRoom : CompositeDrawable
{
public const float SELECTION_BORDER_WIDTH = 4;
private const float corner_radius = 10;
private const float transition_duration = 60;
protected const float CORNER_RADIUS = 10;
private const float height = 100;
public event Action<SelectionState> StateChanged;
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
private Drawable selectionBox;
[Resolved(canBeNull: true)]
private LoungeSubScreen loungeScreen { get; set; }
public readonly Room Room;
[Resolved]
private BeatmapManager beatmaps { get; set; }
[Resolved(canBeNull: true)]
private Bindable<Room> selectedRoom { get; set; }
[Resolved(canBeNull: true)]
private LoungeSubScreen lounge { get; set; }
public readonly Room Room;
private SelectionState state;
private Sample sampleSelect;
private Sample sampleJoin;
public SelectionState State
{
get => state;
set
{
if (value == state)
return;
state = value;
if (selectionBox != null)
{
if (state == SelectionState.Selected)
selectionBox.FadeIn(transition_duration);
else
selectionBox.FadeOut(transition_duration);
}
StateChanged?.Invoke(State);
}
}
public IEnumerable<string> FilterTerms => new[] { Room.Name.Value };
private bool matchingFilter;
public bool MatchingFilter
{
get => matchingFilter;
set
{
matchingFilter = value;
if (!IsLoaded)
return;
if (matchingFilter)
this.FadeIn(200);
else
Hide();
}
}
private int numberOfAvatars = 7;
public int NumberOfAvatars
{
get => numberOfAvatars;
set
{
numberOfAvatars = value;
if (recentParticipantsList != null)
recentParticipantsList.NumberOfCircles = value;
}
}
protected Container ButtonsContainer { get; private set; }
private readonly Bindable<MatchType> roomType = new Bindable<MatchType>();
private readonly Bindable<RoomCategory> roomCategory = new Bindable<RoomCategory>();
private readonly Bindable<bool> hasPassword = new Bindable<bool>();
private RecentParticipantsList recentParticipantsList;
private RoomSpecialCategoryPill specialCategoryPill;
public bool FilteringActive { get; set; }
private PasswordProtectedIcon passwordIcon;
private readonly Bindable<bool> hasPassword = new Bindable<bool>();
private EndDateInfo endDateInfo;
public DrawableRoom(Room room)
{
@ -143,7 +51,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Height = height;
Masking = true;
CornerRadius = corner_radius + SELECTION_BORDER_WIDTH / 2;
CornerRadius = CORNER_RADIUS;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
@ -153,9 +61,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colours, AudioManager audio)
private void load(OverlayColourProvider colours)
{
Children = new Drawable[]
InternalChildren = new[]
{
// This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
new Box
@ -163,10 +71,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
RelativeSizeAxes = Axes.Both,
Colour = colours.Background5,
},
new OnlinePlayBackgroundSprite
CreateBackground().With(d =>
{
RelativeSizeAxes = Axes.Both
},
d.RelativeSizeAxes = Axes.Both;
}),
new Container
{
Name = @"Room content",
@ -177,7 +85,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = corner_radius,
CornerRadius = CORNER_RADIUS,
Children = new Drawable[]
{
new GridContainer
@ -238,10 +146,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
new EndDateInfo
endDateInfo = new EndDateInfo
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
Origin = Anchor.CentreLeft,
},
}
},
@ -289,13 +197,21 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Spacing = new Vector2(5),
Padding = new MarginPadding
{
Right = 10,
Vertical = 5
Vertical = 20,
},
Children = new Drawable[]
{
ButtonsContainer = new Container
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X
},
recentParticipantsList = new RecentParticipantsList
{
Anchor = Anchor.CentreRight,
@ -308,36 +224,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
},
},
},
new StatusColouredContainer(transition_duration)
{
RelativeSizeAxes = Axes.Both,
Child = selectionBox = new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = state == SelectionState.Selected ? 1 : 0,
Masking = true,
CornerRadius = corner_radius,
BorderThickness = SELECTION_BORDER_WIDTH,
BorderColour = Color4.White,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
},
};
sampleSelect = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Submit.GetDescription()}-select");
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
return new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent))
{
Model = { Value = Room }
};
}
@ -345,11 +231,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
base.LoadComplete();
if (matchingFilter)
this.FadeInFromZero(transition_duration);
else
Alpha = 0;
roomCategory.BindTo(Room.Category);
roomCategory.BindValueChanged(c =>
{
@ -359,62 +240,39 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
specialCategoryPill.Hide();
}, true);
roomType.BindTo(Room.Type);
roomType.BindValueChanged(t =>
{
endDateInfo.Alpha = t.NewValue == MatchType.Playlists ? 1 : 0;
}, true);
hasPassword.BindTo(Room.HasPassword);
hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true);
}
public Popover GetPopover() => new PasswordEntryPopover(Room) { JoinRequested = lounge.Join };
public MenuItem[] ContextMenuItems => new MenuItem[]
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
new OsuMenuItem("Create copy", MenuItemType.Standard, () =>
return new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent))
{
lounge?.Open(Room.DeepClone());
})
};
public bool OnPressed(GlobalAction action)
{
if (selectedRoom.Value != Room)
return false;
switch (action)
{
case GlobalAction.Select:
TriggerClick();
return true;
}
return false;
Model = { Value = Room }
};
}
public void OnReleased(GlobalAction action)
private int numberOfAvatars = 7;
public int NumberOfAvatars
{
get => numberOfAvatars;
set
{
numberOfAvatars = value;
if (recentParticipantsList != null)
recentParticipantsList.NumberOfCircles = value;
}
}
protected override bool ShouldBeConsideredForInput(Drawable child) => state == SelectionState.Selected || child is HoverSounds;
protected override bool OnClick(ClickEvent e)
{
if (Room != selectedRoom.Value)
{
sampleSelect?.Play();
selectedRoom.Value = Room;
return true;
}
if (Room.HasPassword.Value)
{
sampleJoin?.Play();
this.ShowPopover();
return true;
}
sampleJoin?.Play();
lounge?.Join(Room, null);
return base.OnClick(e);
}
protected virtual Drawable CreateBackground() => new OnlinePlayBackgroundSprite();
private class RoomNameText : OsuSpriteText
{
@ -500,52 +358,5 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
};
}
}
public class PasswordEntryPopover : OsuPopover
{
private readonly Room room;
public Action<Room, string> JoinRequested;
public PasswordEntryPopover(Room room)
{
this.room = room;
}
private OsuPasswordTextBox passwordTextbox;
[BackgroundDependencyLoader]
private void load()
{
Child = new FillFlowContainer
{
Margin = new MarginPadding(10),
Spacing = new Vector2(5),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
passwordTextbox = new OsuPasswordTextBox
{
Width = 200,
},
new TriangleButton
{
Width = 80,
Text = "Join Room",
Action = () => JoinRequested?.Invoke(room, passwordTextbox.Text)
}
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox));
passwordTextbox.OnCommit += (_, __) => JoinRequested?.Invoke(room, passwordTextbox.Text);
}
}
}
}

View File

@ -15,7 +15,6 @@ using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Game.Extensions;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Online.Rooms;
using osuTK;
@ -26,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
private readonly IBindableList<Room> rooms = new BindableList<Room>();
private readonly FillFlowContainer<DrawableRoom> roomFlow;
private readonly FillFlowContainer<DrawableLoungeRoom> roomFlow;
public IReadOnlyList<DrawableRoom> Rooms => roomFlow.FlowingChildren.Cast<DrawableRoom>().ToArray();
@ -56,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = roomFlow = new FillFlowContainer<DrawableRoom>
Child = roomFlow = new FillFlowContainer<DrawableLoungeRoom>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
@ -74,16 +73,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
rooms.BindTo(roomManager.Rooms);
Filter?.BindValueChanged(criteria => applyFilterCriteria(criteria.NewValue), true);
selectedRoom.BindValueChanged(selection =>
{
updateSelection();
}, true);
}
private void updateSelection() =>
roomFlow.Children.ForEach(r => r.State = r.Room == selectedRoom.Value ? SelectionState.Selected : SelectionState.NotSelected);
private void applyFilterCriteria(FilterCriteria criteria)
{
roomFlow.Children.ForEach(r =>
@ -122,22 +113,17 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
foreach (var room in rooms)
{
roomFlow.Add(new DrawableRoom(room));
roomFlow.Add(new DrawableLoungeRoom(room));
}
applyFilterCriteria(Filter?.Value);
updateSelection();
}
private void removeRooms(IEnumerable<Room> rooms)
{
foreach (var r in rooms)
{
var toRemove = roomFlow.Single(d => d.Room == r);
toRemove.Action = null;
roomFlow.Remove(toRemove);
roomFlow.RemoveAll(d => d.Room == r);
// selection may have a lease due to being in a sub screen.
if (!selectedRoom.Disabled)

View File

@ -0,0 +1,226 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
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.Graphics.UserInterface;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Input.Bindings;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Lounge
{
/// <summary>
/// A <see cref="DrawableRoom"/> with lounge-specific interactions such as selection and hover sounds.
/// </summary>
public class DrawableLoungeRoom : DrawableRoom, IFilterable, IHasContextMenu, IHasPopover, IKeyBindingHandler<GlobalAction>
{
private const float transition_duration = 60;
private const float selection_border_width = 4;
[Resolved(canBeNull: true)]
private LoungeSubScreen lounge { get; set; }
[Resolved(canBeNull: true)]
private Bindable<Room> selectedRoom { get; set; }
private Sample sampleSelect;
private Sample sampleJoin;
private Drawable selectionBox;
public DrawableLoungeRoom(Room room)
: base(room)
{
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleSelect = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Submit.GetDescription()}-select");
AddRangeInternal(new Drawable[]
{
new StatusColouredContainer(transition_duration)
{
RelativeSizeAxes = Axes.Both,
Child = selectionBox = new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
Masking = true,
CornerRadius = CORNER_RADIUS,
BorderThickness = selection_border_width,
BorderColour = Color4.White,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
},
new HoverSounds()
});
}
protected override void LoadComplete()
{
base.LoadComplete();
if (matchingFilter)
this.FadeInFromZero(transition_duration);
else
Alpha = 0;
selectedRoom.BindValueChanged(updateSelectedRoom, true);
}
private void updateSelectedRoom(ValueChangedEvent<Room> selected)
{
if (selected.NewValue == Room)
selectionBox.FadeIn(transition_duration);
else
selectionBox.FadeOut(transition_duration);
}
public bool FilteringActive { get; set; }
public IEnumerable<string> FilterTerms => new[] { Room.Name.Value };
private bool matchingFilter;
public bool MatchingFilter
{
get => matchingFilter;
set
{
matchingFilter = value;
if (!IsLoaded)
return;
if (matchingFilter)
this.FadeIn(200);
else
Hide();
}
}
public Popover GetPopover() => new PasswordEntryPopover(Room) { JoinRequested = lounge.Join };
public MenuItem[] ContextMenuItems => new MenuItem[]
{
new OsuMenuItem("Create copy", MenuItemType.Standard, () =>
{
lounge?.Open(Room.DeepClone());
})
};
public bool OnPressed(GlobalAction action)
{
if (selectedRoom.Value != Room)
return false;
switch (action)
{
case GlobalAction.Select:
TriggerClick();
return true;
}
return false;
}
public void OnReleased(GlobalAction action)
{
}
protected override bool ShouldBeConsideredForInput(Drawable child) => selectedRoom.Value == Room || child is HoverSounds;
protected override bool OnClick(ClickEvent e)
{
if (Room != selectedRoom.Value)
{
sampleSelect?.Play();
selectedRoom.Value = Room;
return true;
}
if (Room.HasPassword.Value)
{
sampleJoin?.Play();
this.ShowPopover();
return true;
}
sampleJoin?.Play();
lounge?.Join(Room, null);
return base.OnClick(e);
}
public class PasswordEntryPopover : OsuPopover
{
private readonly Room room;
public Action<Room, string> JoinRequested;
public PasswordEntryPopover(Room room)
{
this.room = room;
}
private OsuPasswordTextBox passwordTextbox;
[BackgroundDependencyLoader]
private void load()
{
Child = new FillFlowContainer
{
Margin = new MarginPadding(10),
Spacing = new Vector2(5),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
passwordTextbox = new OsuPasswordTextBox
{
Width = 200,
},
new TriangleButton
{
Width = 80,
Text = "Join Room",
Action = () => JoinRequested?.Invoke(room, passwordTextbox.Text)
}
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox));
passwordTextbox.OnCommit += (_, __) => JoinRequested?.Invoke(room, passwordTextbox.Text);
}
}
}
}

View File

@ -76,6 +76,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] IdleTracker idleTracker)
{
const float controls_area_height = 25f;
if (idleTracker != null)
isIdle.BindTo(idleTracker.IsIdle);
@ -84,86 +86,73 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
InternalChildren = new Drawable[]
{
ListingPollingComponent = CreatePollingComponent().With(c => c.Filter.BindTarget = filter),
loadingLayer = new LoadingLayer(true),
new Container
{
Name = @"Rooms area",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
Left = WaveOverlayContainer.WIDTH_PADDING,
Right = WaveOverlayContainer.WIDTH_PADDING,
Horizontal = WaveOverlayContainer.WIDTH_PADDING,
Top = Header.HEIGHT + controls_area_height + 20,
},
Child = new GridContainer
Child = scrollContainer = new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
ScrollbarOverlapsContent = false,
Child = roomsContainer = new RoomsContainer
{
new Dimension(GridSizeMode.Absolute, Header.HEIGHT),
new Dimension(GridSizeMode.Absolute, 25),
new Dimension(GridSizeMode.Absolute, 20)
Filter = { BindTarget = filter }
}
},
},
loadingLayer = new LoadingLayer(true),
new FillFlowContainer
{
Name = @"Header area flow",
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.WIDTH_PADDING },
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
Height = Header.HEIGHT,
Child = searchTextBox = new LoungeSearchTextBox
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
RelativeSizeAxes = Axes.X,
Width = 0.6f,
},
},
Content = new[]
new Container
{
new Drawable[]
RelativeSizeAxes = Axes.X,
Height = controls_area_height,
Children = new Drawable[]
{
searchTextBox = new LoungeSearchTextBox
Buttons.WithChild(CreateNewRoomButton().With(d =>
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
RelativeSizeAxes = Axes.X,
Width = 0.6f,
},
},
new Drawable[]
{
new Container
d.Anchor = Anchor.BottomLeft;
d.Origin = Anchor.BottomLeft;
d.Size = new Vector2(150, 37.5f);
d.Action = () => Open();
})),
new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Depth = float.MinValue, // Contained filters should appear over the top of rooms.
Children = new Drawable[]
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10),
ChildrenEnumerable = CreateFilterControls().Select(f => f.With(d =>
{
Buttons.WithChild(CreateNewRoomButton().With(d =>
{
d.Anchor = Anchor.BottomLeft;
d.Origin = Anchor.BottomLeft;
d.Size = new Vector2(150, 37.5f);
d.Action = () => Open();
})),
new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10),
ChildrenEnumerable = CreateFilterControls().Select(f => f.With(d =>
{
d.Anchor = Anchor.TopRight;
d.Origin = Anchor.TopRight;
}))
}
}
d.Anchor = Anchor.TopRight;
d.Origin = Anchor.TopRight;
}))
}
},
null,
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
scrollContainer = new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
ScrollbarOverlapsContent = false,
Child = roomsContainer = new RoomsContainer
{
Filter = { BindTarget = filter }
}
},
}
},
}
}
},

View File

@ -1,48 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Screens.OnlinePlay.Playlists;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Match.Components
{
public class Footer : CompositeDrawable
{
public const float HEIGHT = 50;
public Action OnStart;
private readonly Drawable background;
public Footer()
{
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
InternalChildren = new[]
{
background = new Box { RelativeSizeAxes = Axes.Both },
new PlaylistsReadyButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(600, 50),
Action = () => OnStart?.Invoke()
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
background.Colour = Color4Extensions.FromHex(@"28242d");
}
}
}

View File

@ -1,79 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Match.Components
{
public class Header : OnlinePlayComposite
{
public const float HEIGHT = 50;
private UpdateableAvatar avatar;
private LinkFlowContainer hostText;
public Header()
{
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
avatar = new UpdateableAvatar
{
Size = new Vector2(50),
Masking = true,
CornerRadius = 10,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 30),
Current = { BindTarget = RoomName }
},
hostText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 20))
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
}
}
}
}
};
Host.BindValueChanged(host =>
{
avatar.User = host.NewValue;
hostText.Clear();
if (host.NewValue != null)
{
hostText.AddText("hosted by ");
hostText.AddUserLink(host.NewValue, s => s.Font = s.Font.With(weight: FontWeight.SemiBold));
}
}, true);
}
}
}

View File

@ -19,9 +19,12 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
[Resolved(CanBeNull = true)]
private ChannelManager channelManager { get; set; }
public MatchChatDisplay()
private readonly bool leaveChannelOnDispose;
public MatchChatDisplay(bool leaveChannelOnDispose = true)
: base(true)
{
this.leaveChannelOnDispose = leaveChannelOnDispose;
}
protected override void LoadComplete()
@ -42,7 +45,9 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
channelManager?.LeaveChannel(Channel.Value);
if (leaveChannelOnDispose)
channelManager?.LeaveChannel(Channel.Value);
}
}
}

View File

@ -14,10 +14,10 @@ using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Match.Components
{
public abstract class MatchSettingsOverlay : FocusedOverlayContainer, IKeyBindingHandler<GlobalAction>
public abstract class RoomSettingsOverlay : FocusedOverlayContainer, IKeyBindingHandler<GlobalAction>
{
protected const float TRANSITION_DURATION = 350;
protected const float FIELD_PADDING = 45;
protected const float FIELD_PADDING = 25;
protected OnlinePlayComposite Settings { get; set; }
@ -27,11 +27,16 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected abstract bool IsLoading { get; }
protected RoomSettingsOverlay()
{
RelativeSizeAxes = Axes.Both;
Masking = true;
CornerRadius = 10;
}
[BackgroundDependencyLoader]
private void load()
{
Masking = true;
Add(Settings = CreateSettings());
}
@ -43,12 +48,14 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
{
base.PopIn();
Settings.MoveToY(0, TRANSITION_DURATION, Easing.OutQuint);
Settings.FadeIn(TRANSITION_DURATION / 2);
}
protected override void PopOut()
{
base.PopOut();
Settings.MoveToY(-1, TRANSITION_DURATION, Easing.InSine);
Settings.Delay(TRANSITION_DURATION / 2).FadeOut(TRANSITION_DURATION / 2);
}
public bool OnPressed(GlobalAction action)

View File

@ -0,0 +1,64 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Match
{
public class DrawableMatchRoom : DrawableRoom
{
public Action OnEdit;
[Resolved]
private IAPIProvider api { get; set; }
private readonly IBindable<User> host = new Bindable<User>();
private readonly bool allowEdit;
[CanBeNull]
private Drawable editButton;
public DrawableMatchRoom(Room room, bool allowEdit = true)
: base(room)
{
this.allowEdit = allowEdit;
host.BindTo(room.Host);
}
[BackgroundDependencyLoader]
private void load()
{
if (allowEdit)
{
ButtonsContainer.Add(editButton = new PurpleTriangleButton
{
RelativeSizeAxes = Axes.Y,
Size = new Vector2(100, 1),
Text = "Edit",
Action = () => OnEdit?.Invoke()
});
}
}
protected override void LoadComplete()
{
base.LoadComplete();
if (editButton != null)
host.BindValueChanged(h => editButton.Alpha = h.NewValue?.Equals(api.LocalUser.Value) == true ? 1 : 0, true);
}
protected override Drawable CreateBackground() => new RoomBackgroundSprite();
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.Drawables;
namespace osu.Game.Screens.OnlinePlay.Match
{
public class RoomBackgroundSprite : RoomSubScreenComposite
{
protected readonly BeatmapSetCoverType BeatmapSetCoverType;
private UpdateableBeatmapBackgroundSprite sprite;
public RoomBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover)
{
BeatmapSetCoverType = beatmapSetCoverType;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = sprite = new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
}
protected override void LoadComplete()
{
base.LoadComplete();
SelectedItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap.Value, true);
}
}
}

View File

@ -31,8 +31,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
public override bool DisallowExternalBeatmapRulesetChanges => true;
private readonly ModSelectOverlay userModsSelectOverlay;
/// <summary>
/// A container that provides controls for selection of user mods.
/// This will be shown/hidden automatically when applicable.
@ -46,6 +44,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
/// </summary>
protected readonly Bindable<IReadOnlyList<Mod>> UserMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
protected readonly IBindable<long?> RoomId = new Bindable<long?>();
[Resolved]
private MusicController music { get; set; }
@ -58,55 +58,184 @@ namespace osu.Game.Screens.OnlinePlay.Match
private IBindable<WeakReference<BeatmapSetInfo>> managerUpdated;
[Cached]
protected OnlinePlayBeatmapAvailabilityTracker BeatmapAvailabilityTracker { get; }
protected OnlinePlayBeatmapAvailabilityTracker BeatmapAvailabilityTracker { get; private set; }
protected IBindable<BeatmapAvailability> BeatmapAvailability => BeatmapAvailabilityTracker.Availability;
protected RoomSubScreen()
public readonly Room Room;
private readonly bool allowEdit;
private ModSelectOverlay userModsSelectOverlay;
private RoomSettingsOverlay settingsOverlay;
private Drawable mainContent;
/// <summary>
/// Creates a new <see cref="RoomSubScreen"/>.
/// </summary>
/// <param name="room">The <see cref="Room"/>.</param>
/// <param name="allowEdit">Whether to allow editing room settings post-creation.</param>
protected RoomSubScreen(Room room, bool allowEdit = true)
{
Room = room;
this.allowEdit = allowEdit;
Padding = new MarginPadding { Top = Header.HEIGHT };
AddRangeInternal(new Drawable[]
BeatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"3e3a44") // This is super temporary.
},
BeatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{
SelectedItem = { BindTarget = SelectedItem }
},
new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Depth = float.MinValue,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING },
Child = userModsSelectOverlay = new UserModSelectOverlay
{
SelectedMods = { BindTarget = UserMods },
IsValidMod = _ => false
}
},
});
}
SelectedItem = { BindTarget = SelectedItem }
};
protected override void ClearInternal(bool disposeChildren = true) =>
throw new InvalidOperationException($"{nameof(RoomSubScreen)}'s children should not be cleared as it will remove required components");
RoomId.BindTo(room.RoomID);
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleStart = audio.Samples.Get(@"SongSelect/confirm-selection");
InternalChildren = new Drawable[]
{
BeatmapAvailabilityTracker,
new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.Absolute, 50)
},
Content = new[]
{
// Padded main content (drawable room + main content)
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
Horizontal = WaveOverlayContainer.WIDTH_PADDING,
Bottom = 30
},
Children = new[]
{
mainContent = new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Absolute, 10)
},
Content = new[]
{
new Drawable[]
{
new DrawableMatchRoom(Room, allowEdit)
{
OnEdit = () => settingsOverlay.Show()
}
},
null,
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 10,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"3e3a44") // Temporary.
},
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(20),
Child = CreateMainContent(),
},
new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = userModsSelectOverlay = new UserModSelectOverlay
{
SelectedMods = { BindTarget = UserMods },
IsValidMod = _ => false
}
},
}
}
}
}
},
new Container
{
RelativeSizeAxes = Axes.Both,
// Resolves 1px masking errors between the settings overlay and the room panel.
Padding = new MarginPadding(-1),
Child = settingsOverlay = CreateRoomSettingsOverlay()
}
},
},
},
// Footer
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"28242d") // Temporary.
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(5),
Child = CreateFooter()
},
}
}
}
}
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
RoomId.BindValueChanged(id =>
{
if (id.NewValue == null)
{
// A new room is being created.
// The main content should be hidden until the settings overlay is hidden, signaling the room is ready to be displayed.
mainContent.Hide();
settingsOverlay.Show();
}
else
{
mainContent.Show();
settingsOverlay.Hide();
}
}, true);
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy();
@ -117,12 +246,25 @@ namespace osu.Game.Screens.OnlinePlay.Match
public override bool OnBackButton()
{
if (Room.RoomID.Value == null)
{
// room has not been created yet; exit immediately.
settingsOverlay.Hide();
return base.OnBackButton();
}
if (userModsSelectOverlay.State.Value == Visibility.Visible)
{
userModsSelectOverlay.Hide();
return true;
}
if (settingsOverlay.State.Value == Visibility.Visible)
{
settingsOverlay.Hide();
return true;
}
return base.OnBackButton();
}
@ -257,6 +399,21 @@ namespace osu.Game.Screens.OnlinePlay.Match
track.Looping = false;
}
/// <summary>
/// Creates the main centred content.
/// </summary>
protected abstract Drawable CreateMainContent();
/// <summary>
/// Creates the footer content.
/// </summary>
protected abstract Drawable CreateFooter();
/// <summary>
/// Creates the room settings overlay.
/// </summary>
protected abstract RoomSettingsOverlay CreateRoomSettingsOverlay();
private class UserModSelectOverlay : LocalPlayerModSelectOverlay
{
}

View File

@ -0,0 +1,111 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osu.Game.Screens.Play;
namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
public class GameplayChatDisplay : MatchChatDisplay, IKeyBindingHandler<GlobalAction>
{
[Resolved]
private ILocalUserPlayInfo localUserInfo { get; set; }
private IBindable<bool> localUserPlaying = new Bindable<bool>();
public override bool PropagatePositionalInputSubTree => !localUserPlaying.Value;
public Bindable<bool> Expanded = new Bindable<bool>();
private readonly Bindable<bool> expandedFromTextboxFocus = new Bindable<bool>();
private const float height = 100;
public override bool PropagateNonPositionalInputSubTree => true;
public GameplayChatDisplay()
: base(leaveChannelOnDispose: false)
{
RelativeSizeAxes = Axes.X;
Background.Alpha = 0.2f;
Textbox.FocusLost = () => expandedFromTextboxFocus.Value = false;
}
protected override void LoadComplete()
{
base.LoadComplete();
localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
localUserPlaying.BindValueChanged(playing =>
{
// for now let's never hold focus. this avoid misdirected gameplay keys entering chat.
// note that this is done within this callback as it triggers an un-focus as well.
Textbox.HoldFocus = false;
// only hold focus (after sending a message) during breaks
Textbox.ReleaseFocusOnCommit = playing.NewValue;
}, true);
Expanded.BindValueChanged(_ => updateExpandedState(), true);
expandedFromTextboxFocus.BindValueChanged(focus =>
{
if (focus.NewValue)
updateExpandedState();
else
{
// on finishing typing a message there should be a brief delay before hiding.
using (BeginDelayedSequence(600))
updateExpandedState();
}
}, true);
}
public bool OnPressed(GlobalAction action)
{
switch (action)
{
case GlobalAction.ToggleChatFocus:
if (Textbox.HasFocus)
{
Schedule(() => Textbox.KillFocus());
}
else
{
expandedFromTextboxFocus.Value = true;
// schedule required to ensure the textbox has become present from above bindable update.
Schedule(() => Textbox.TakeFocus());
}
return true;
}
return false;
}
public void OnReleased(GlobalAction action)
{
}
private void updateExpandedState()
{
if (Expanded.Value || expandedFromTextboxFocus.Value)
{
this.FadeIn(300, Easing.OutQuint);
this.ResizeHeightTo(height, 500, Easing.OutQuint);
}
else
{
this.FadeOut(300, Easing.OutQuint);
this.ResizeHeightTo(0, 500, Easing.OutQuint);
}
}
}
}

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Screens;
using osu.Game.Online.API;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
@ -35,6 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(5),
Children = new Drawable[]
{
beatmapPanelContainer = new Container

View File

@ -2,18 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using System;
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.Game.Graphics;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
public class MultiplayerMatchFooter : CompositeDrawable
{
public const float HEIGHT = 50;
private const float ready_button_width = 600;
private const float spectate_button_width = 200;
@ -27,54 +22,42 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
set => spectateButton.OnSpectateClick = value;
}
private readonly Drawable background;
private readonly MultiplayerReadyButton readyButton;
private readonly MultiplayerSpectateButton spectateButton;
public MultiplayerMatchFooter()
{
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
RelativeSizeAxes = Axes.Both;
InternalChildren = new[]
InternalChild = new GridContainer
{
background = new Box { RelativeSizeAxes = Axes.Both },
new GridContainer
RelativeSizeAxes = Axes.Both,
Content = new[]
{
RelativeSizeAxes = Axes.Both,
Content = new[]
new Drawable[]
{
new Drawable[]
null,
spectateButton = new MultiplayerSpectateButton
{
null,
spectateButton = new MultiplayerSpectateButton
{
RelativeSizeAxes = Axes.Both,
},
null,
readyButton = new MultiplayerReadyButton
{
RelativeSizeAxes = Axes.Both,
},
null
}
},
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(maxSize: spectate_button_width),
new Dimension(GridSizeMode.Absolute, 10),
new Dimension(maxSize: ready_button_width),
new Dimension()
RelativeSizeAxes = Axes.Both,
},
null,
readyButton = new MultiplayerReadyButton
{
RelativeSizeAxes = Axes.Both,
},
null
}
},
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(maxSize: spectate_button_width),
new Dimension(GridSizeMode.Absolute, 5),
new Dimension(maxSize: ready_button_width),
new Dimension()
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
background.Colour = Color4Extensions.FromHex(@"28242d");
}
}
}

View File

@ -1,106 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osu.Game.Users.Drawables;
using osuTK;
using FontWeight = osu.Game.Graphics.FontWeight;
using OsuColour = osu.Game.Graphics.OsuColour;
using OsuFont = osu.Game.Graphics.OsuFont;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
public class MultiplayerMatchHeader : OnlinePlayComposite
{
public const float HEIGHT = 50;
public Action OpenSettings;
private UpdateableAvatar avatar;
private LinkFlowContainer hostText;
private Button openSettingsButton;
[Resolved]
private IAPIProvider api { get; set; }
public MultiplayerMatchHeader()
{
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
InternalChildren = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
avatar = new UpdateableAvatar
{
Size = new Vector2(50),
Masking = true,
CornerRadius = 10,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 30),
Current = { BindTarget = RoomName }
},
hostText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 20))
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
}
}
}
}
},
openSettingsButton = new PurpleTriangleButton
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Size = new Vector2(150, HEIGHT),
Text = "Open settings",
Action = () => OpenSettings?.Invoke(),
Alpha = 0
}
};
Host.BindValueChanged(host =>
{
avatar.User = host.NewValue;
hostText.Clear();
if (host.NewValue != null)
{
hostText.AddText("hosted by ");
hostText.AddUserLink(host.NewValue, s => s.Font = s.Font.With(weight: FontWeight.SemiBold));
}
openSettingsButton.Alpha = host.NewValue?.Equals(api.LocalUser.Value) == true ? 1 : 0;
}, true);
}
}
}

View File

@ -26,7 +26,7 @@ using osuTK;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
public class MultiplayerMatchSettingsOverlay : MatchSettingsOverlay
public class MultiplayerMatchSettingsOverlay : RoomSettingsOverlay
{
private MatchSettings settings;
@ -150,6 +150,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
LengthLimit = 100,
},
},
new Section("Room visibility")
@ -207,6 +208,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
LengthLimit = 255,
},
},
}

View File

@ -50,7 +50,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override Room CreateNewRoom() => new Room
{
Name = { Value = $"{api.LocalUser}'s awesome room" },
Category = { Value = RoomCategory.Realtime },
Type = { Value = MatchType.HeadToHead },
};

View File

@ -42,8 +42,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
public override string ShortTitle => "room";
public readonly Room Room;
[Resolved]
private MultiplayerClient client { get; set; }
@ -58,209 +56,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[CanBeNull]
private IDisposable readyClickOperation;
private GridContainer mainContent;
private MultiplayerMatchSettingsOverlay settingsOverlay;
public MultiplayerMatchSubScreen(Room room)
: base(room)
{
Room = room;
Title = room.RoomID.Value == null ? "New room" : room.Name.Value;
Activity.Value = new UserActivity.InLobby(room);
}
[BackgroundDependencyLoader]
private void load()
{
AddRangeInternal(new Drawable[]
{
mainContent = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
Horizontal = HORIZONTAL_OVERFLOW_PADDING + 55,
Vertical = 20
},
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
},
Content = new[]
{
new Drawable[]
{
new MultiplayerMatchHeader
{
OpenSettings = () => settingsOverlay.Show()
}
},
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 5, Vertical = 10 },
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400),
new Dimension(),
new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600),
},
Content = new[]
{
new Drawable[]
{
// Main left column
new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize)
},
Content = new[]
{
new Drawable[] { new ParticipantsListHeader() },
new Drawable[]
{
new ParticipantsList
{
RelativeSizeAxes = Axes.Both
},
}
}
},
// Spacer
null,
// Main right column
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new OverlinedHeader("Beatmap"),
new BeatmapSelectionControl { RelativeSizeAxes = Axes.X }
}
},
UserModsSection = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Top = 10 },
Children = new Drawable[]
{
new OverlinedHeader("Extra mods"),
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
new UserModSelectButton
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Width = 90,
Text = "Select",
Action = ShowUserModSelect,
},
new ModDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Current = UserMods,
Scale = new Vector2(0.8f),
},
}
}
}
}
}
}
}
}
}
}
},
new Drawable[]
{
new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize)
},
Content = new[]
{
new Drawable[] { new OverlinedHeader("Chat") },
new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } }
}
}
}
},
}
}
},
new Drawable[]
{
new MultiplayerMatchFooter
{
OnReadyClick = onReadyClick,
OnSpectateClick = onSpectateClick
}
}
},
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
}
},
settingsOverlay = new MultiplayerMatchSettingsOverlay
{
RelativeSizeAxes = Axes.Both,
State = { Value = client.Room == null ? Visibility.Visible : Visibility.Hidden }
}
});
if (client.Room == null)
{
// A new room is being created.
// The main content should be hidden until the settings overlay is hidden, signaling the room is ready to be displayed.
mainContent.Hide();
settingsOverlay.State.BindValueChanged(visibility =>
{
if (visibility.NewValue == Visibility.Hidden)
mainContent.Show();
}, true);
}
}
protected override void LoadComplete()
{
base.LoadComplete();
@ -292,6 +94,126 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
}, true);
}
protected override Drawable CreateMainContent() => new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 5, Vertical = 10 },
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400),
new Dimension(),
new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600),
},
Content = new[]
{
new Drawable[]
{
// Main left column
new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize)
},
Content = new[]
{
new Drawable[] { new ParticipantsListHeader() },
new Drawable[]
{
new ParticipantsList
{
RelativeSizeAxes = Axes.Both
},
}
}
},
// Spacer
null,
// Main right column
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[] { new OverlinedHeader("Beatmap") },
new Drawable[] { new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } },
new[]
{
UserModsSection = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Top = 10 },
Alpha = 0,
Children = new Drawable[]
{
new OverlinedHeader("Extra mods"),
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
new UserModSelectButton
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Width = 90,
Text = "Select",
Action = ShowUserModSelect,
},
new ModDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Current = UserMods,
Scale = new Vector2(0.8f),
},
}
},
}
},
},
new Drawable[] { new OverlinedHeader("Chat") { Margin = new MarginPadding { Vertical = 5 }, }, },
new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } }
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
}
},
}
}
}
}
},
},
};
protected override Drawable CreateFooter() => new MultiplayerMatchFooter
{
OnReadyClick = onReadyClick,
OnSpectateClick = onSpectateClick
};
protected override RoomSettingsOverlay CreateRoomSettingsOverlay() => new MultiplayerMatchSettingsOverlay();
protected override void UpdateMods()
{
if (SelectedItem.Value == null || client.LocalUser == null)
@ -308,23 +230,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private bool exitConfirmed;
public override bool OnBackButton()
{
if (client.Room == null)
{
// room has not been created yet; exit immediately.
return base.OnBackButton();
}
if (settingsOverlay.State.Value == Visibility.Visible)
{
settingsOverlay.Hide();
return true;
}
return base.OnBackButton();
}
public override bool OnExiting(IScreen next)
{
// the room may not be left immediately after a disconnection due to async flow,
@ -508,19 +413,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (client != null)
{
client.RoomUpdated -= onRoomUpdated;
client.LoadRequested -= onLoadRequested;
}
modSettingChangeTracker?.Dispose();
}
public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset)
{
if (!this.IsCurrentScreen())
@ -536,5 +428,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
this.Push(new MultiplayerMatchSongSelect(beatmap, ruleset));
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (client != null)
{
client.RoomUpdated -= onRoomUpdated;
client.LoadRequested -= onLoadRequested;
}
modSettingChangeTracker?.Dispose();
}
}
}

View File

@ -68,6 +68,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(5)
});
// todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area.
@ -78,7 +79,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
((IBindable<bool>)leaderboard.Expanded).BindTo(HUDOverlay.ShowHud);
leaderboardFlow.Add(l);
leaderboardFlow.Insert(0, l);
if (leaderboard.TeamScores.Count >= 2)
{
@ -87,10 +88,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Team1Score = { BindTarget = leaderboard.TeamScores.First().Value },
Team2Score = { BindTarget = leaderboard.TeamScores.Last().Value },
Expanded = { BindTarget = HUDOverlay.ShowHud },
}, leaderboardFlow.Add);
}, scoreDisplay => leaderboardFlow.Insert(1, scoreDisplay));
}
});
LoadComponentAsync(new GameplayChatDisplay
{
Expanded = { BindTarget = HUDOverlay.ShowHud },
}, chat => leaderboardFlow.Insert(2, chat));
HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue });
}

View File

@ -23,12 +23,6 @@ namespace osu.Game.Screens.OnlinePlay
RelativeSizeAxes = Axes.Both;
}
public const float X_SHIFT = 200;
public const double X_MOVE_DURATION = 800;
public const double RESUME_TRANSITION_DELAY = DISAPPEAR_DURATION / 2;
public const double APPEAR_DURATION = 800;
public const double DISAPPEAR_DURATION = 500;
@ -36,28 +30,23 @@ namespace osu.Game.Screens.OnlinePlay
public override void OnEntering(IScreen last)
{
this.FadeInFromZero(APPEAR_DURATION, Easing.OutQuint);
this.FadeInFromZero(APPEAR_DURATION, Easing.OutQuint);
this.MoveToX(X_SHIFT).MoveToX(0, X_MOVE_DURATION, Easing.OutQuint);
}
public override bool OnExiting(IScreen next)
{
this.FadeOut(DISAPPEAR_DURATION, Easing.OutQuint);
this.MoveToX(X_SHIFT, X_MOVE_DURATION, Easing.OutQuint);
return false;
}
public override void OnResuming(IScreen last)
{
this.Delay(RESUME_TRANSITION_DELAY).FadeIn(APPEAR_DURATION, Easing.OutQuint);
this.MoveToX(0, X_MOVE_DURATION, Easing.OutQuint);
this.FadeIn(APPEAR_DURATION, Easing.OutQuint);
}
public override void OnSuspending(IScreen next)
{
this.FadeOut(DISAPPEAR_DURATION, Easing.OutQuint);
this.MoveToX(-X_SHIFT, X_MOVE_DURATION, Easing.OutQuint);
}
public override string ToString() => Title;

View File

@ -0,0 +1,32 @@
// 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;
using osu.Framework.Graphics.Containers;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Playlists
{
public class PlaylistsRoomFooter : CompositeDrawable
{
public Action OnStart;
public PlaylistsRoomFooter()
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new[]
{
new PlaylistsReadyButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Size = new Vector2(600, 1),
Action = () => OnStart?.Invoke()
}
};
}
}
}

View File

@ -22,7 +22,7 @@ using osuTK;
namespace osu.Game.Screens.OnlinePlay.Playlists
{
public class PlaylistsMatchSettingsOverlay : MatchSettingsOverlay
public class PlaylistsRoomSettingsOverlay : RoomSettingsOverlay
{
public Action EditPlaylist;
@ -193,7 +193,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Child = new GridContainer
{
RelativeSizeAxes = Axes.X,
Height = 500,
Height = 448,
Content = new[]
{
new Drawable[]

View File

@ -20,7 +20,6 @@ using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Users;
using osuTK;
using Footer = osu.Game.Screens.OnlinePlay.Match.Components.Footer;
namespace osu.Game.Screens.OnlinePlay.Playlists
{
@ -30,21 +29,16 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
public override string ShortTitle => "playlist";
[Resolved(typeof(Room), nameof(Room.RoomID))]
private Bindable<long?> roomId { get; set; }
[Resolved(typeof(Room), nameof(Room.Playlist))]
private BindableList<PlaylistItem> playlist { get; set; }
[Resolved]
private IAPIProvider api { get; set; }
private readonly IBindable<bool> isIdle = new BindableBool();
private MatchSettingsOverlay settingsOverlay;
private MatchLeaderboard leaderboard;
private OverlinedHeader participantsHeader;
private GridContainer mainContent;
private SelectionPollingComponent selectionPollingComponent;
public PlaylistsRoomSubScreen(Room room)
: base(room, false) // Editing is temporarily not allowed.
{
Title = room.RoomID.Value == null ? "New playlist" : room.Name.Value;
Activity.Value = new UserActivity.InLobby(room);
@ -56,235 +50,149 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
if (idleTracker != null)
isIdle.BindTo(idleTracker.IsIdle);
AddRangeInternal(new Drawable[]
{
selectionPollingComponent = new SelectionPollingComponent(),
mainContent = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
Horizontal = 105,
Vertical = 20
},
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
},
Content = new[]
{
new Drawable[] { new Match.Components.Header() },
new Drawable[]
{
participantsHeader = new OverlinedHeader("Participants")
{
ShowLine = false
}
},
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Top = 5 },
Child = new ParticipantsDisplay(Direction.Horizontal)
{
Details = { BindTarget = participantsHeader.Details }
}
}
},
new Drawable[]
{
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 5 },
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[] { new OverlinedPlaylistHeader(), },
new Drawable[]
{
new DrawableRoomPlaylistWithResults
{
RelativeSizeAxes = Axes.Both,
Items = { BindTarget = playlist },
SelectedItem = { BindTarget = SelectedItem },
RequestShowResults = item =>
{
Debug.Assert(roomId.Value != null);
ParentScreen?.Push(new PlaylistsResultsScreen(null, roomId.Value.Value, item, false));
}
}
},
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
}
}
},
null,
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new[]
{
UserModsSection = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Bottom = 10 },
Children = new Drawable[]
{
new OverlinedHeader("Extra mods"),
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
new UserModSelectButton
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Width = 90,
Text = "Select",
Action = ShowUserModSelect,
},
new ModDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Current = UserMods,
Scale = new Vector2(0.8f),
},
}
}
}
},
},
new Drawable[]
{
new OverlinedHeader("Leaderboard")
},
new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, },
new Drawable[] { new OverlinedHeader("Chat"), },
new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } }
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 120),
}
},
null
},
},
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400),
new Dimension(),
new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600),
new Dimension(),
}
}
}
},
}
}
},
new Drawable[]
{
new Footer { OnStart = StartPlay }
}
},
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
}
},
settingsOverlay = new PlaylistsMatchSettingsOverlay
{
RelativeSizeAxes = Axes.Both,
EditPlaylist = () =>
{
if (this.IsCurrentScreen())
this.Push(new PlaylistsSongSelect());
},
State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden }
}
});
if (roomId.Value == null)
{
// A new room is being created.
// The main content should be hidden until the settings overlay is hidden, signaling the room is ready to be displayed.
mainContent.Hide();
settingsOverlay.State.BindValueChanged(visibility =>
{
if (visibility.NewValue == Visibility.Hidden)
mainContent.Show();
}, true);
}
AddInternal(selectionPollingComponent = new SelectionPollingComponent());
}
[Resolved]
private IAPIProvider api { get; set; }
protected override void LoadComplete()
{
base.LoadComplete();
isIdle.BindValueChanged(_ => updatePollingRate(), true);
roomId.BindValueChanged(id =>
RoomId.BindValueChanged(id =>
{
if (id.NewValue == null)
settingsOverlay.Show();
else
if (id.NewValue != null)
{
settingsOverlay.Hide();
// Set the first playlist item.
// This is scheduled since updating the room and playlist may happen in an arbitrary order (via Room.CopyFrom()).
Schedule(() => SelectedItem.Value = playlist.FirstOrDefault());
Schedule(() => SelectedItem.Value = Room.Playlist.FirstOrDefault());
}
}, true);
}
protected override Drawable CreateMainContent() => new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 5 },
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[] { new OverlinedPlaylistHeader(), },
new Drawable[]
{
new DrawableRoomPlaylistWithResults
{
RelativeSizeAxes = Axes.Both,
Items = { BindTarget = Room.Playlist },
SelectedItem = { BindTarget = SelectedItem },
RequestShowResults = item =>
{
Debug.Assert(RoomId.Value != null);
ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false));
}
}
},
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
}
}
},
null,
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new[]
{
UserModsSection = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Alpha = 0,
Margin = new MarginPadding { Bottom = 10 },
Children = new Drawable[]
{
new OverlinedHeader("Extra mods"),
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
new UserModSelectButton
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Width = 90,
Text = "Select",
Action = ShowUserModSelect,
},
new ModDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Current = UserMods,
Scale = new Vector2(0.8f),
},
}
}
}
},
},
new Drawable[]
{
new OverlinedHeader("Leaderboard")
},
new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, },
new Drawable[] { new OverlinedHeader("Chat"), },
new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } }
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 120),
}
},
},
},
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400),
new Dimension(),
new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600),
}
};
protected override Drawable CreateFooter() => new PlaylistsRoomFooter
{
OnStart = StartPlay
};
protected override RoomSettingsOverlay CreateRoomSettingsOverlay() => new PlaylistsRoomSettingsOverlay
{
EditPlaylist = () =>
{
if (this.IsCurrentScreen())
this.Push(new PlaylistsSongSelect());
},
};
private void updatePollingRate()
{
selectionPollingComponent.TimeBetweenPolls.Value = isIdle.Value ? 30000 : 5000;

View File

@ -75,7 +75,9 @@ namespace osu.Game.Screens.Play
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
protected readonly Bindable<bool> LocalUserPlaying = new Bindable<bool>();
public IBindable<bool> LocalUserPlaying => localUserPlaying;
private readonly Bindable<bool> localUserPlaying = new Bindable<bool>();
public int RestartCount;
@ -442,7 +444,7 @@ namespace osu.Game.Screens.Play
{
bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value;
OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered;
LocalUserPlaying.Value = inGameplay;
localUserPlaying.Value = inGameplay;
}
private void updateSampleDisabledState()

View File

@ -37,6 +37,8 @@ namespace osu.Game.Screens.Select
public const float BORDER_THICKNESS = 2.5f;
private const float shear_width = 36.75f;
private const float transition_duration = 250;
private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGE_HEIGHT, 0);
[Resolved]
@ -45,11 +47,6 @@ namespace osu.Game.Screens.Select
[Resolved]
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
[Resolved]
private BeatmapDifficultyCache difficultyCache { get; set; }
private IBindable<StarDifficulty?> beatmapDifficulty;
protected Container DisplayedContent { get; private set; }
protected WedgeInfoText Info { get; private set; }
@ -76,24 +73,24 @@ namespace osu.Game.Screens.Select
ruleset.BindValueChanged(_ => updateDisplay());
}
private const double animation_duration = 800;
protected override void PopIn()
{
this.MoveToX(0, 800, Easing.OutQuint);
this.RotateTo(0, 800, Easing.OutQuint);
this.FadeIn(250);
this.MoveToX(0, animation_duration, Easing.OutQuint);
this.RotateTo(0, animation_duration, Easing.OutQuint);
this.FadeIn(transition_duration);
}
protected override void PopOut()
{
this.MoveToX(-100, 800, Easing.In);
this.RotateTo(10, 800, Easing.In);
this.FadeOut(500, Easing.In);
this.MoveToX(-100, animation_duration, Easing.In);
this.RotateTo(10, animation_duration, Easing.In);
this.FadeOut(transition_duration * 2, Easing.In);
}
private WorkingBeatmap beatmap;
private CancellationTokenSource cancellationSource;
public WorkingBeatmap Beatmap
{
get => beatmap;
@ -102,12 +99,6 @@ namespace osu.Game.Screens.Select
if (beatmap == value) return;
beatmap = value;
cancellationSource?.Cancel();
cancellationSource = new CancellationTokenSource();
beatmapDifficulty?.UnbindAll();
beatmapDifficulty = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo, cancellationSource.Token);
beatmapDifficulty.BindValueChanged(_ => updateDisplay());
updateDisplay();
}
@ -127,7 +118,7 @@ namespace osu.Game.Screens.Select
{
State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible;
DisplayedContent?.FadeOut(250);
DisplayedContent?.FadeOut(transition_duration);
DisplayedContent?.Expire();
DisplayedContent = null;
}
@ -146,7 +137,7 @@ namespace osu.Game.Screens.Select
Children = new Drawable[]
{
new BeatmapInfoWedgeBackground(beatmap),
Info = new WedgeInfoText(beatmap, ruleset.Value, mods.Value, beatmapDifficulty.Value ?? new StarDifficulty()),
Info = new WedgeInfoText(beatmap, ruleset.Value, mods.Value),
}
}, loaded =>
{
@ -159,12 +150,6 @@ namespace osu.Game.Screens.Select
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
cancellationSource?.Cancel();
}
public class WedgeInfoText : Container
{
public OsuSpriteText VersionLabel { get; private set; }
@ -173,6 +158,9 @@ namespace osu.Game.Screens.Select
public BeatmapSetOnlineStatusPill StatusPill { get; private set; }
public FillFlowContainer MapperContainer { get; private set; }
private Container difficultyColourBar;
private StarRatingDisplay starRatingDisplay;
private ILocalisedBindableString titleBinding;
private ILocalisedBindableString artistBinding;
private FillFlowContainer infoLabelContainer;
@ -181,20 +169,21 @@ namespace osu.Game.Screens.Select
private readonly WorkingBeatmap beatmap;
private readonly RulesetInfo ruleset;
private readonly IReadOnlyList<Mod> mods;
private readonly StarDifficulty starDifficulty;
private ModSettingChangeTracker settingChangeTracker;
public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList<Mod> mods, StarDifficulty difficulty)
public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList<Mod> mods)
{
this.beatmap = beatmap;
ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset;
this.mods = mods;
starDifficulty = difficulty;
}
private CancellationTokenSource cancellationSource;
private IBindable<StarDifficulty?> starDifficulty;
[BackgroundDependencyLoader]
private void load(LocalisationManager localisation)
private void load(OsuColour colours, LocalisationManager localisation, BeatmapDifficultyCache difficultyCache)
{
var beatmapInfo = beatmap.BeatmapInfo;
var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
@ -204,12 +193,30 @@ namespace osu.Game.Screens.Select
titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.TitleUnicode, metadata.Title));
artistBinding = localisation.GetLocalisedString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist));
const float top_height = 0.7f;
Children = new Drawable[]
{
new DifficultyColourBar(starDifficulty)
difficultyColourBar = new Container
{
RelativeSizeAxes = Axes.Y,
Width = 20,
Width = 20f,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Width = top_height,
},
new Box
{
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Both,
Alpha = 0.5f,
X = top_height,
Width = 1 - top_height,
}
}
},
new FillFlowContainer
{
@ -240,14 +247,16 @@ namespace osu.Game.Screens.Select
Padding = new MarginPadding { Top = 14, Right = shear_width / 2 },
AutoSizeAxes = Axes.Both,
Shear = wedged_container_shear,
Children = new[]
Spacing = new Vector2(0f, 5f),
Children = new Drawable[]
{
createStarRatingDisplay(starDifficulty).With(display =>
starRatingDisplay = new StarRatingDisplay(default, animated: true)
{
display.Anchor = Anchor.TopRight;
display.Origin = Anchor.TopRight;
display.Shear = -wedged_container_shear;
}),
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Shear = -wedged_container_shear,
Alpha = 0f,
},
StatusPill = new BeatmapSetOnlineStatusPill
{
Anchor = Anchor.TopRight,
@ -303,6 +312,18 @@ namespace osu.Game.Screens.Select
titleBinding.BindValueChanged(_ => setMetadata(metadata.Source));
artistBinding.BindValueChanged(_ => setMetadata(metadata.Source), true);
starRatingDisplay.DisplayedStars.BindValueChanged(s =>
{
difficultyColourBar.Colour = colours.ForStarDifficulty(s.NewValue);
}, true);
starDifficulty = difficultyCache.GetBindableDifficulty(beatmapInfo, (cancellationSource = new CancellationTokenSource()).Token);
starDifficulty.BindValueChanged(s =>
{
starRatingDisplay.FadeIn(transition_duration);
starRatingDisplay.Current.Value = s.NewValue ?? default;
});
// no difficulty means it can't have a status to show
if (beatmapInfo.Version == null)
StatusPill.Hide();
@ -310,13 +331,6 @@ namespace osu.Game.Screens.Select
addInfoLabels();
}
private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0
? new StarRatingDisplay(difficulty)
{
Margin = new MarginPadding { Bottom = 5 }
}
: Empty();
private void setMetadata(string source)
{
ArtistLabel.Text = artistBinding.Value;
@ -428,6 +442,7 @@ namespace osu.Game.Screens.Select
{
base.Dispose(isDisposing);
settingChangeTracker?.Dispose();
cancellationSource?.Cancel();
}
public class InfoLabel : Container, IHasTooltip
@ -488,43 +503,6 @@ namespace osu.Game.Screens.Select
};
}
}
private class DifficultyColourBar : Container
{
private readonly StarDifficulty difficulty;
public DifficultyColourBar(StarDifficulty difficulty)
{
this.difficulty = difficulty;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
const float full_opacity_ratio = 0.7f;
var difficultyColour = colours.ForStarDifficulty(difficulty.Stars);
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = difficultyColour,
Width = full_opacity_ratio,
},
new Box
{
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Both,
Colour = difficultyColour,
Alpha = 0.5f,
X = full_opacity_ratio,
Width = 1 - full_opacity_ratio,
}
};
}
}
}
}
}

View File

@ -1,150 +1,36 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Multiplayer
{
/// <summary>
/// A <see cref="RoomManager"/> for use in multiplayer test scenes. Should generally not be used by itself outside of a <see cref="MultiplayerTestScene"/>.
/// A <see cref="RoomManager"/> for use in multiplayer test scenes, backed by a <see cref="TestRoomRequestsHandler"/>.
/// Should generally not be used by itself outside of a <see cref="MultiplayerTestScene"/>.
/// </summary>
/// <remarks>
/// This implementation will pretend to be a server, handling room retrieval and manipulation requests
/// and returning a roughly expected state, without the need for a server to be running.
/// </remarks>
public class TestRequestHandlingMultiplayerRoomManager : MultiplayerRoomManager
{
[Resolved]
private IAPIProvider api { get; set; }
public IReadOnlyList<Room> ServerSideRooms => handler.ServerSideRooms;
[Resolved]
private OsuGameBase game { get; set; }
public IReadOnlyList<Room> ServerSideRooms => serverSideRooms;
private readonly List<Room> serverSideRooms = new List<Room>();
private int currentRoomId;
private int currentPlaylistItemId;
private readonly TestRoomRequestsHandler handler = new TestRoomRequestsHandler();
[BackgroundDependencyLoader]
private void load()
private void load(IAPIProvider api, OsuGameBase game)
{
int currentScoreId = 0;
// Handling here is pretending to be a server, while also updating the local state to match
// how the server would eventually respond and update the RoomManager.
((DummyAPIAccess)api).HandleRequest = req =>
{
switch (req)
{
case CreateRoomRequest createRoomRequest:
var apiRoom = new Room();
apiRoom.CopyFrom(createRoomRequest.Room);
// Passwords are explicitly not copied between rooms.
apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value);
apiRoom.Password.Value = createRoomRequest.Room.Password.Value;
AddServerSideRoom(apiRoom);
var responseRoom = new APICreatedRoom();
responseRoom.CopyFrom(createResponseRoom(apiRoom, false));
createRoomRequest.TriggerSuccess(responseRoom);
return true;
case JoinRoomRequest joinRoomRequest:
{
var room = ServerSideRooms.Single(r => r.RoomID.Value == joinRoomRequest.Room.RoomID.Value);
if (joinRoomRequest.Password != room.Password.Value)
{
joinRoomRequest.TriggerFailure(new InvalidOperationException("Invalid password."));
return true;
}
joinRoomRequest.TriggerSuccess();
return true;
}
case PartRoomRequest partRoomRequest:
partRoomRequest.TriggerSuccess();
return true;
case GetRoomsRequest getRoomsRequest:
var roomsWithoutParticipants = new List<Room>();
foreach (var r in ServerSideRooms)
roomsWithoutParticipants.Add(createResponseRoom(r, false));
getRoomsRequest.TriggerSuccess(roomsWithoutParticipants);
return true;
case GetRoomRequest getRoomRequest:
getRoomRequest.TriggerSuccess(createResponseRoom(ServerSideRooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true));
return true;
case GetBeatmapSetRequest getBeatmapSetRequest:
var onlineReq = new GetBeatmapSetRequest(getBeatmapSetRequest.ID, getBeatmapSetRequest.Type);
onlineReq.Success += res => getBeatmapSetRequest.TriggerSuccess(res);
onlineReq.Failure += e => getBeatmapSetRequest.TriggerFailure(e);
// Get the online API from the game's dependencies.
game.Dependencies.Get<IAPIProvider>().Queue(onlineReq);
return true;
case CreateRoomScoreRequest createRoomScoreRequest:
createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 });
return true;
case SubmitRoomScoreRequest submitRoomScoreRequest:
submitRoomScoreRequest.TriggerSuccess(new MultiplayerScore
{
ID = currentScoreId++,
Accuracy = 1,
EndedAt = DateTimeOffset.Now,
Passed = true,
Rank = ScoreRank.S,
MaxCombo = 1000,
TotalScore = 1000000,
User = api.LocalUser.Value,
Statistics = new Dictionary<HitResult, int>()
});
return true;
}
return false;
};
((DummyAPIAccess)api).HandleRequest = request => handler.HandleRequest(request, api.LocalUser.Value, game);
}
public void AddServerSideRoom(Room room)
{
room.RoomID.Value ??= currentRoomId++;
for (int i = 0; i < room.Playlist.Count; i++)
room.Playlist[i].ID = currentPlaylistItemId++;
serverSideRooms.Add(room);
}
private Room createResponseRoom(Room room, bool withParticipants)
{
var responseRoom = new Room();
responseRoom.CopyFrom(room);
responseRoom.Password.Value = null;
if (!withParticipants)
responseRoom.RecentParticipants.Clear();
return responseRoom;
}
/// <summary>
/// Adds a room to a local "server-side" list that's returned when a <see cref="GetRoomsRequest"/> is fired.
/// </summary>
/// <param name="room">The room.</param>
public void AddServerSideRoom(Room room) => handler.AddServerSideRoom(room);
}
}

View File

@ -1,111 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.OnlinePlay
{
/// <summary>
/// A very simple <see cref="RoomManager"/> for use in online play test scenes.
/// </summary>
public class BasicTestRoomManager : IRoomManager
{
public event Action RoomsUpdated;
public readonly BindableList<Room> Rooms = new BindableList<Room>();
public Action<Room, string> JoinRoomRequested;
public IBindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
IBindableList<Room> IRoomManager.Rooms => Rooms;
private int currentRoomId;
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
{
room.RoomID.Value ??= Rooms.Select(r => r.RoomID.Value).Where(id => id != null).Select(id => id.Value).DefaultIfEmpty().Max() + 1;
onSuccess?.Invoke(room);
AddOrUpdateRoom(room);
}
public void AddOrUpdateRoom(Room room)
{
var existing = Rooms.FirstOrDefault(r => r.RoomID.Value != null && r.RoomID.Value == room.RoomID.Value);
if (existing != null)
existing.CopyFrom(room);
else
Rooms.Add(room);
RoomsUpdated?.Invoke();
}
public void RemoveRoom(Room room)
{
Rooms.Remove(room);
RoomsUpdated?.Invoke();
}
public void ClearRooms()
{
Rooms.Clear();
RoomsUpdated?.Invoke();
}
public void JoinRoom(Room room, string password, Action<Room> onSuccess = null, Action<string> onError = null)
{
JoinRoomRequested?.Invoke(room, password);
onSuccess?.Invoke(room);
}
public void PartRoom()
{
}
public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false)
{
for (int i = 0; i < count; i++)
{
var room = new Room
{
RoomID = { Value = currentRoomId },
Position = { Value = currentRoomId },
Name = { Value = $"Room {currentRoomId}" },
Host = { Value = new User { Username = "Host" } },
EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) },
Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal },
Password = { Value = withPassword ? "password" : string.Empty }
};
if (ruleset != null)
{
room.Playlist.Add(new PlaylistItem
{
Ruleset = { Value = ruleset },
Beatmap =
{
Value = new BeatmapInfo
{
Metadata = new BeatmapMetadata()
}
}
});
}
CreateRoom(room);
currentRoomId++;
}
}
}
}

View File

@ -71,6 +71,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay
drawableComponents.Add(drawable);
}
protected virtual IRoomManager CreateRoomManager() => new BasicTestRoomManager();
protected virtual IRoomManager CreateRoomManager() => new TestRequestHandlingRoomManager();
}
}

View File

@ -0,0 +1,75 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.OnlinePlay
{
/// <summary>
/// A very simple <see cref="RoomManager"/> for use in online play test scenes.
/// </summary>
public class TestRequestHandlingRoomManager : RoomManager
{
public Action<Room, string> JoinRoomRequested;
private int currentRoomId;
private readonly TestRoomRequestsHandler handler = new TestRoomRequestsHandler();
[BackgroundDependencyLoader]
private void load(IAPIProvider api, OsuGameBase game)
{
((DummyAPIAccess)api).HandleRequest = request => handler.HandleRequest(request, api.LocalUser.Value, game);
}
public override void JoinRoom(Room room, string password = null, Action<Room> onSuccess = null, Action<string> onError = null)
{
JoinRoomRequested?.Invoke(room, password);
base.JoinRoom(room, password, onSuccess, onError);
}
public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false)
{
for (int i = 0; i < count; i++)
{
var room = new Room
{
RoomID = { Value = -currentRoomId },
Name = { Value = $@"Room {currentRoomId}" },
Host = { Value = new User { Username = @"Host" } },
EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) },
Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal },
};
if (withPassword)
room.Password.Value = @"password";
if (ruleset != null)
{
room.Playlist.Add(new PlaylistItem
{
Ruleset = { Value = ruleset },
Beatmap =
{
Value = new BeatmapInfo
{
Metadata = new BeatmapMetadata()
}
}
});
}
CreateRoom(room);
currentRoomId++;
}
}
}
}

View File

@ -0,0 +1,147 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.OnlinePlay
{
/// <summary>
/// Represents a handler which pretends to be a server, handling room retrieval and manipulation requests
/// and returning a roughly expected state, without the need for a server to be running.
/// </summary>
public class TestRoomRequestsHandler
{
public IReadOnlyList<Room> ServerSideRooms => serverSideRooms;
private readonly List<Room> serverSideRooms = new List<Room>();
private int currentRoomId;
private int currentPlaylistItemId;
private int currentScoreId;
/// <summary>
/// Handles an API request, while also updating the local state to match
/// how the server would eventually respond and update an <see cref="RoomManager"/>.
/// </summary>
/// <param name="request">The API request to handle.</param>
/// <param name="localUser">The local user to store in responses where required.</param>
/// <param name="game">The game base for cases where actual online requests need to be sent.</param>
/// <returns>Whether the request was successfully handled.</returns>
public bool HandleRequest(APIRequest request, User localUser, OsuGameBase game)
{
switch (request)
{
case CreateRoomRequest createRoomRequest:
var apiRoom = new Room();
apiRoom.CopyFrom(createRoomRequest.Room);
// Passwords are explicitly not copied between rooms.
apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value);
apiRoom.Password.Value = createRoomRequest.Room.Password.Value;
AddServerSideRoom(apiRoom);
var responseRoom = new APICreatedRoom();
responseRoom.CopyFrom(createResponseRoom(apiRoom, false));
createRoomRequest.TriggerSuccess(responseRoom);
return true;
case JoinRoomRequest joinRoomRequest:
{
var room = ServerSideRooms.Single(r => r.RoomID.Value == joinRoomRequest.Room.RoomID.Value);
if (joinRoomRequest.Password != room.Password.Value)
{
joinRoomRequest.TriggerFailure(new InvalidOperationException("Invalid password."));
return true;
}
joinRoomRequest.TriggerSuccess();
return true;
}
case PartRoomRequest partRoomRequest:
partRoomRequest.TriggerSuccess();
return true;
case GetRoomsRequest getRoomsRequest:
var roomsWithoutParticipants = new List<Room>();
foreach (var r in ServerSideRooms)
roomsWithoutParticipants.Add(createResponseRoom(r, false));
getRoomsRequest.TriggerSuccess(roomsWithoutParticipants);
return true;
case GetRoomRequest getRoomRequest:
getRoomRequest.TriggerSuccess(createResponseRoom(ServerSideRooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true));
return true;
case GetBeatmapSetRequest getBeatmapSetRequest:
var onlineReq = new GetBeatmapSetRequest(getBeatmapSetRequest.ID, getBeatmapSetRequest.Type);
onlineReq.Success += res => getBeatmapSetRequest.TriggerSuccess(res);
onlineReq.Failure += e => getBeatmapSetRequest.TriggerFailure(e);
// Get the online API from the game's dependencies.
game.Dependencies.Get<IAPIProvider>().Queue(onlineReq);
return true;
case CreateRoomScoreRequest createRoomScoreRequest:
createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 });
return true;
case SubmitRoomScoreRequest submitRoomScoreRequest:
submitRoomScoreRequest.TriggerSuccess(new MultiplayerScore
{
ID = currentScoreId++,
Accuracy = 1,
EndedAt = DateTimeOffset.Now,
Passed = true,
Rank = ScoreRank.S,
MaxCombo = 1000,
TotalScore = 1000000,
User = localUser,
Statistics = new Dictionary<HitResult, int>()
});
return true;
}
return false;
}
/// <summary>
/// Adds a room to a local "server-side" list that's returned when a <see cref="GetRoomsRequest"/> is fired.
/// </summary>
/// <param name="room">The room.</param>
public void AddServerSideRoom(Room room)
{
room.RoomID.Value ??= currentRoomId++;
for (int i = 0; i < room.Playlist.Count; i++)
room.Playlist[i].ID = currentPlaylistItemId++;
serverSideRooms.Add(room);
}
private Room createResponseRoom(Room room, bool withParticipants)
{
var responseRoom = new Room();
responseRoom.CopyFrom(room);
responseRoom.Password.Value = null;
if (!withParticipants)
responseRoom.RecentParticipants.Clear();
return responseRoom;
}
}
}

View File

@ -105,8 +105,9 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBeProtected_002EGlobal/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MergeCastWithTypeCheck/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MergeConditionalExpression/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MergeIntoNegatedPattern/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MergeIntoPattern/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MergeSequentialChecks/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MergeSequentialPatterns/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MethodHasAsyncOverload/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MethodHasAsyncOverloadWithCancellation/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MethodSupportsCancellation/@EntryIndexedValue">WARNING</s:String>