1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-14 03:03:02 +08:00

Merge branch 'master' into multiplayer-team-vs-results

This commit is contained in:
Bartłomiej Dach 2021-08-12 20:39:48 +02:00
commit 70005cb39d
No known key found for this signature in database
GPG Key ID: BCECCD4FA41F6497
76 changed files with 2318 additions and 808 deletions

View File

@ -51,8 +51,8 @@
<Reference Include="Java.Interop" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.808.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.809.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.810.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.811.0" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -0,0 +1,120 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osuTK;
namespace osu.Game.Rulesets.Catch.Tests.Mods
{
[TestFixture]
public class CatchModMirrorTest
{
[Test]
public void TestModMirror()
{
IBeatmap original = createBeatmap(false);
IBeatmap mirrored = createBeatmap(true);
assertEffectivePositionsMirrored(original, mirrored);
}
private static IBeatmap createBeatmap(bool withMirrorMod)
{
var beatmap = createRawBeatmap();
var mirrorMod = new CatchModMirror();
var beatmapProcessor = new CatchBeatmapProcessor(beatmap);
beatmapProcessor.PreProcess();
foreach (var hitObject in beatmap.HitObjects)
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
beatmapProcessor.PostProcess();
if (withMirrorMod)
mirrorMod.ApplyToBeatmap(beatmap);
return beatmap;
}
private static IBeatmap createRawBeatmap() => new Beatmap
{
HitObjects = new List<HitObject>
{
new Fruit
{
OriginalX = 150,
StartTime = 0
},
new Fruit
{
OriginalX = 450,
StartTime = 500
},
new JuiceStream
{
OriginalX = 250,
Path = new SliderPath
{
ControlPoints =
{
new PathControlPoint(new Vector2(-100, 1)),
new PathControlPoint(new Vector2(0, 2)),
new PathControlPoint(new Vector2(100, 3)),
new PathControlPoint(new Vector2(0, 4))
}
},
StartTime = 1000,
},
new BananaShower
{
StartTime = 5000,
Duration = 5000
}
}
};
private static void assertEffectivePositionsMirrored(IBeatmap original, IBeatmap mirrored)
{
if (original.HitObjects.Count != mirrored.HitObjects.Count)
Assert.Fail($"Top-level object count mismatch (original: {original.HitObjects.Count}, mirrored: {mirrored.HitObjects.Count})");
for (int i = 0; i < original.HitObjects.Count; ++i)
{
var originalObject = (CatchHitObject)original.HitObjects[i];
var mirroredObject = (CatchHitObject)mirrored.HitObjects[i];
// banana showers themselves are exempt, as we only really care about their nested bananas' positions.
if (!effectivePositionMirrored(originalObject, mirroredObject) && !(originalObject is BananaShower))
Assert.Fail($"{originalObject.GetType().Name} at time {originalObject.StartTime} is not mirrored ({printEffectivePositions(originalObject, mirroredObject)})");
if (originalObject.NestedHitObjects.Count != mirroredObject.NestedHitObjects.Count)
Assert.Fail($"{originalObject.GetType().Name} nested object count mismatch (original: {originalObject.NestedHitObjects.Count}, mirrored: {mirroredObject.NestedHitObjects.Count})");
for (int j = 0; j < originalObject.NestedHitObjects.Count; ++j)
{
var originalNested = (CatchHitObject)originalObject.NestedHitObjects[j];
var mirroredNested = (CatchHitObject)mirroredObject.NestedHitObjects[j];
if (!effectivePositionMirrored(originalNested, mirroredNested))
Assert.Fail($"{originalObject.GetType().Name}'s nested {originalNested.GetType().Name} at time {originalObject.StartTime} is not mirrored ({printEffectivePositions(originalNested, mirroredNested)})");
}
}
}
private static string printEffectivePositions(CatchHitObject original, CatchHitObject mirrored)
=> $"original X: {original.EffectiveX}, mirrored X is: {mirrored.EffectiveX}, mirrored X should be: {CatchPlayfield.WIDTH - original.EffectiveX}";
private static bool effectivePositionMirrored(CatchHitObject original, CatchHitObject mirrored)
=> Precision.AlmostEquals(original.EffectiveX, CatchPlayfield.WIDTH - mirrored.EffectiveX);
}
}

View File

@ -117,6 +117,7 @@ namespace osu.Game.Rulesets.Catch
{
new CatchModDifficultyAdjust(),
new CatchModClassic(),
new CatchModMirror(),
};
case ModType.Automation:

View File

@ -0,0 +1,87 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osuTK;
namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModMirror : ModMirror, IApplicableToBeatmap
{
public override string Description => "Fruits are flipped horizontally.";
/// <remarks>
/// <see cref="IApplicableToBeatmap"/> is used instead of <see cref="IApplicableToHitObject"/>,
/// as <see cref="CatchBeatmapProcessor"/> applies offsets in <see cref="CatchBeatmapProcessor.PostProcess"/>.
/// <see cref="IApplicableToBeatmap"/> runs after post-processing, while <see cref="IApplicableToHitObject"/> runs before it.
/// </remarks>
public void ApplyToBeatmap(IBeatmap beatmap)
{
foreach (var hitObject in beatmap.HitObjects)
applyToHitObject(hitObject);
}
private void applyToHitObject(HitObject hitObject)
{
var catchObject = (CatchHitObject)hitObject;
switch (catchObject)
{
case Fruit fruit:
mirrorEffectiveX(fruit);
break;
case JuiceStream juiceStream:
mirrorEffectiveX(juiceStream);
mirrorJuiceStreamPath(juiceStream);
break;
case BananaShower bananaShower:
mirrorBananaShower(bananaShower);
break;
}
}
/// <summary>
/// Mirrors the effective X position of <paramref name="catchObject"/> and its nested hit objects.
/// </summary>
private static void mirrorEffectiveX(CatchHitObject catchObject)
{
catchObject.OriginalX = CatchPlayfield.WIDTH - catchObject.OriginalX;
catchObject.XOffset = -catchObject.XOffset;
foreach (var nested in catchObject.NestedHitObjects.Cast<CatchHitObject>())
{
nested.OriginalX = CatchPlayfield.WIDTH - nested.OriginalX;
nested.XOffset = -nested.XOffset;
}
}
/// <summary>
/// Mirrors the path of the <paramref name="juiceStream"/>.
/// </summary>
private static void mirrorJuiceStreamPath(JuiceStream juiceStream)
{
var controlPoints = juiceStream.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray();
foreach (var point in controlPoints)
point.Position.Value = new Vector2(-point.Position.Value.X, point.Position.Value.Y);
juiceStream.Path = new SliderPath(controlPoints, juiceStream.Path.ExpectedDistance.Value);
}
/// <summary>
/// Mirrors X positions of all bananas in the <paramref name="bananaShower"/>.
/// </summary>
private static void mirrorBananaShower(BananaShower bananaShower)
{
foreach (var banana in bananaShower.NestedHitObjects.OfType<Banana>())
banana.XOffset = CatchPlayfield.WIDTH - banana.XOffset;
}
}
}

View File

@ -41,6 +41,11 @@ namespace osu.Game.Rulesets.Osu.Edit
protected override GameplayCursorContainer CreateCursor() => null;
public OsuEditorPlayfield()
{
HitPolicy = new AnyOrderHitPolicy();
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
// Roughly matches osu!stable's slider border portions.
=> base.CalculatedBorderPortion * 0.77f;
public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f);
public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, 0.7f);
protected override Color4 ColourAt(float position)
{

View File

@ -0,0 +1,22 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.UI
{
/// <summary>
/// An <see cref="IHitPolicy"/> which allows hitobjects to be hit in any order.
/// </summary>
public class AnyOrderHitPolicy : IHitPolicy
{
public IHitObjectContainer HitObjectContainer { get; set; }
public bool IsHittable(DrawableHitObject hitObject, double time) => true;
public void HandleHit(DrawableHitObject hitObject)
{
}
}
}

View File

@ -2,11 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Gameplay
@ -121,6 +123,18 @@ namespace osu.Game.Tests.Gameplay
AddAssert("Drawable lifetime is restored", () => dho.LifetimeStart == 666 && dho.LifetimeEnd == 999);
}
[Test]
public void TestStateChangeBeforeLoadComplete()
{
TestDrawableHitObject dho = null;
AddStep("Add DHO and apply result", () =>
{
Child = dho = new TestDrawableHitObject(new HitObject { StartTime = Time.Current });
dho.MissForcefully();
});
AddAssert("DHO state is correct", () => dho.State.Value == ArmedState.Miss);
}
private class TestDrawableHitObject : DrawableHitObject
{
public const double INITIAL_LIFETIME_OFFSET = 100;
@ -141,6 +155,19 @@ namespace osu.Game.Tests.Gameplay
if (SetLifetimeStartOnApply)
LifetimeStart = LIFETIME_ON_APPLY;
}
public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss);
protected override void UpdateHitStateTransforms(ArmedState state)
{
if (state != ArmedState.Miss)
{
base.UpdateHitStateTransforms(state);
return;
}
this.FadeOut(1000);
}
}
private class TestLifetimeEntry : HitObjectLifetimeEntry

View File

@ -24,6 +24,8 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddRepeatStep("add some users", () => Client.AddUser(new User { Id = id++ }), 5);
checkPlayingUserCount(0);
AddAssert("playlist item is available", () => Client.CurrentMatchPlayingItem.Value != null);
changeState(3, MultiplayerUserState.WaitingForLoad);
checkPlayingUserCount(3);
@ -41,6 +43,8 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddStep("leave room", () => Client.LeaveRoom());
checkPlayingUserCount(0);
AddAssert("playlist item is null", () => Client.CurrentMatchPlayingItem.Value == null);
}
[Test]

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Logging;
using osu.Framework.Testing;
using osu.Game.Graphics.Sprites;
using osu.Game.Online;
using osuTK;
@ -15,6 +16,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Components
{
[HeadlessTest]
public class TestScenePollingComponent : OsuTestScene
{
private Container pollBox;

View File

@ -0,0 +1,168 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
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.Components;
using osu.Game.Tests.Beatmaps;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneDrawableRoom : OsuTestScene
{
[Cached]
private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
[Cached]
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
[Test]
public void TestMultipleStatuses()
{
AddStep("create rooms", () =>
{
Child = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.9f),
Spacing = new Vector2(10),
Children = new Drawable[]
{
createDrawableRoom(new Room
{
Name = { Value = "Flyte's Trash Playlist" },
Status = { Value = new RoomStatusOpen() },
EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
Playlist =
{
new PlaylistItem
{
Beatmap =
{
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
StarDifficulty = 2.5
}
}.BeatmapInfo,
}
}
}
}),
createDrawableRoom(new Room
{
Name = { Value = "Room 2" },
Status = { Value = new RoomStatusPlaying() },
EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
Playlist =
{
new PlaylistItem
{
Beatmap =
{
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
StarDifficulty = 2.5
}
}.BeatmapInfo,
}
},
new PlaylistItem
{
Beatmap =
{
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
StarDifficulty = 4.5
}
}.BeatmapInfo,
}
}
}
}),
createDrawableRoom(new Room
{
Name = { Value = "Room 3" },
Status = { Value = new RoomStatusEnded() },
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() },
Category = { Value = RoomCategory.Spotlight },
}),
}
};
});
}
[Test]
public void TestEnableAndDisablePassword()
{
DrawableRoom drawableRoom = null;
Room room = null;
AddStep("create room", () => Child = drawableRoom = createDrawableRoom(room = new Room
{
Name = { Value = "Room with password" },
Status = { Value = new RoomStatusOpen() },
Category = { Value = RoomCategory.Realtime },
}));
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
AddStep("set password", () => room.Password.Value = "password");
AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
AddStep("unset password", () => room.Password.Value = string.Empty);
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
}
private DrawableRoom createDrawableRoom(Room room)
{
room.Host.Value ??= new User { Username = "peppy", Id = 2 };
if (room.RecentParticipants.Count == 0)
{
room.RecentParticipants.AddRange(Enumerable.Range(0, 20).Select(i => new User
{
Id = i,
Username = $"User {i}"
}));
}
var drawableRoom = new DrawableRoom(room) { MatchingFilter = true };
drawableRoom.Action = () => drawableRoom.State = drawableRoom.State == SelectionState.Selected ? SelectionState.NotSelected : SelectionState.Selected;
return drawableRoom;
}
}
}

View File

@ -14,7 +14,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
assertDownloadButtonVisible(false);
void assertDownloadButtonVisible(bool visible) => AddUntilStep($"download button {(visible ? "shown" : "hidden")}",
() => playlist.ChildrenOfType<BeatmapDownloadTrackingComposite>().Single().Alpha == (visible ? 1 : 0));
() => playlist.ChildrenOfType<BeatmapPanelDownloadButton>().Single().Alpha == (visible ? 1 : 0));
}
[Test]
@ -229,7 +229,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
createPlaylist(byOnlineId, byChecksum);
AddAssert("download buttons shown", () => playlist.ChildrenOfType<BeatmapDownloadTrackingComposite>().All(d => d.IsPresent));
AddAssert("download buttons shown", () => playlist.ChildrenOfType<BeatmapPanelDownloadButton>().All(d => d.IsPresent));
}
[Test]

View File

@ -1,49 +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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneLoungeRoomInfo : OnlinePlayTestScene
{
[SetUp]
public new void Setup() => Schedule(() =>
{
SelectedRoom.Value = new Room();
Child = new RoomInfo
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 500
};
});
[Test]
public void TestNonSelectedRoom()
{
AddStep("set null room", () => SelectedRoom.Value.RoomID.Value = null);
}
[Test]
public void TestOpenRoom()
{
AddStep("set open room", () =>
{
SelectedRoom.Value.RoomID.Value = 0;
SelectedRoom.Value.Name.Value = "Room 0";
SelectedRoom.Value.Host.Value = new User { Username = "peppy", Id = 2 };
SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMonths(1);
SelectedRoom.Value.Status.Value = new RoomStatusOpen();
});
}
}
}

View File

@ -97,11 +97,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
TeamID = 1,
};
SpectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId);
SpectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId);
SpectatorClient.StartPlay(player1.UserID, importedBeatmapId);
SpectatorClient.StartPlay(player2.UserID, importedBeatmapId);
playingUsers.Add(new MultiplayerRoomUser(PLAYER_1_ID));
playingUsers.Add(new MultiplayerRoomUser(PLAYER_2_ID));
playingUsers.Add(player1);
playingUsers.Add(player2);
});
loadSpectateScreen();

View File

@ -423,10 +423,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void createRoom(Func<Room> room)
{
AddStep("open room", () =>
{
multiplayerScreen.OpenNewRoom(room());
});
AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
AddStep("open room", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().Single().Open(room()));
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
AddWaitStep("wait for transition", 2);

View File

@ -12,6 +12,7 @@ using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Spectator;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets.Osu.Scoring;
@ -51,12 +52,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
OsuScoreProcessor scoreProcessor;
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
var multiplayerUsers = new List<MultiplayerRoomUser>();
foreach (var user in users)
{
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true);
multiplayerUsers.Add(OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true));
}
Children = new Drawable[]
@ -64,9 +66,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
scoreProcessor = new OsuScoreProcessor(),
};
scoreProcessor.ApplyBeatmap(playable);
scoreProcessor.ApplyBeatmap(playableBeatmap);
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, Client.Room?.Users.ToArray())
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu.Scoring;
@ -55,7 +56,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
OsuScoreProcessor scoreProcessor;
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
var multiplayerUsers = new List<MultiplayerRoomUser>();
foreach (var user in users)
{
@ -66,6 +68,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
TeamID = RNG.Next(0, 2)
};
multiplayerUsers.Add(roomUser);
}
Children = new Drawable[]
@ -73,9 +77,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
scoreProcessor = new OsuScoreProcessor(),
};
scoreProcessor.ApplyBeatmap(playable);
scoreProcessor.ApplyBeatmap(playableBeatmap);
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, Client.Room?.Users.ToArray())
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,

View File

@ -155,6 +155,42 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("second user crown visible", () => this.ChildrenOfType<ParticipantPanel>().ElementAt(1).ChildrenOfType<SpriteIcon>().First().Alpha == 1);
}
[Test]
public void TestKickButtonOnlyPresentWhenHost()
{
AddStep("add user", () => Client.AddUser(new User
{
Id = 3,
Username = "Second",
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
}));
AddUntilStep("kick buttons visible", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Count(d => d.IsPresent) == 1);
AddStep("make second user host", () => Client.TransferHost(3));
AddUntilStep("kick buttons not visible", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Count(d => d.IsPresent) == 0);
AddStep("make local user host again", () => Client.TransferHost(API.LocalUser.Value.Id));
AddUntilStep("kick buttons visible", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Count(d => d.IsPresent) == 1);
}
[Test]
public void TestKickButtonKicks()
{
AddStep("add user", () => Client.AddUser(new User
{
Id = 3,
Username = "Second",
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
}));
AddStep("kick second user", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Single(d => d.IsPresent).TriggerClick());
AddAssert("second user kicked", () => Client.Room?.Users.Single().UserID == API.LocalUser.Value.Id);
}
[Test]
public void TestManyUsers()
{

View File

@ -0,0 +1,95 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneRankRangePill : MultiplayerTestScene
{
[SetUp]
public new void Setup() => Schedule(() =>
{
Child = new RankRangePill
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
};
});
[Test]
public void TestSingleUser()
{
AddStep("add user", () =>
{
Client.AddUser(new User
{
Id = 2,
Statistics = { GlobalRank = 1234 }
});
// Remove the local user so only the one above is displayed.
Client.RemoveUser(API.LocalUser.Value);
});
}
[Test]
public void TestMultipleUsers()
{
AddStep("add users", () =>
{
Client.AddUser(new User
{
Id = 2,
Statistics = { GlobalRank = 1234 }
});
Client.AddUser(new User
{
Id = 3,
Statistics = { GlobalRank = 3333 }
});
Client.AddUser(new User
{
Id = 4,
Statistics = { GlobalRank = 4321 }
});
// Remove the local user so only the ones above are displayed.
Client.RemoveUser(API.LocalUser.Value);
});
}
[TestCase(1, 10)]
[TestCase(10, 100)]
[TestCase(100, 1000)]
[TestCase(1000, 10000)]
[TestCase(10000, 100000)]
[TestCase(100000, 1000000)]
[TestCase(1000000, 10000000)]
public void TestRange(int min, int max)
{
AddStep("add users", () =>
{
Client.AddUser(new User
{
Id = 2,
Statistics = { GlobalRank = min }
});
Client.AddUser(new User
{
Id = 3,
Statistics = { GlobalRank = max }
});
// Remove the local user so only the ones above are displayed.
Client.RemoveUser(API.LocalUser.Value);
});
}
}
}

View File

@ -0,0 +1,143 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
using osu.Game.Users.Drawables;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneRecentParticipantsList : OnlinePlayTestScene
{
private RecentParticipantsList list;
[SetUp]
public new void Setup() => Schedule(() =>
{
SelectedRoom.Value = new Room { Name = { Value = "test room" } };
Child = list = new RecentParticipantsList
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
NumberOfCircles = 4
};
});
[Test]
public void TestCircleCountNearLimit()
{
AddStep("add 8 users", () =>
{
for (int i = 0; i < 8; i++)
addUser(i);
});
AddStep("set 8 circles", () => list.NumberOfCircles = 8);
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
AddStep("add one more user", () => addUser(9));
AddAssert("2 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 2);
AddStep("remove first user", () => removeUserAt(0));
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
AddStep("add one more user", () => addUser(9));
AddAssert("2 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 2);
AddStep("remove last user", () => removeUserAt(8));
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
}
[Test]
public void TestHiddenUsersBecomeDisplayed()
{
AddStep("add 8 users", () =>
{
for (int i = 0; i < 8; i++)
addUser(i);
});
AddStep("set 3 circles", () => list.NumberOfCircles = 3);
for (int i = 0; i < 8; i++)
{
AddStep("remove user", () => removeUserAt(0));
int remainingUsers = 7 - i;
int displayedUsers = remainingUsers > 3 ? 2 : remainingUsers;
AddAssert($"{displayedUsers} avatars displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == displayedUsers);
}
}
[Test]
public void TestCircleCount()
{
AddStep("add 50 users", () =>
{
for (int i = 0; i < 50; i++)
addUser(i);
});
AddStep("set 3 circles", () => list.NumberOfCircles = 3);
AddAssert("2 users displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 2);
AddAssert("48 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 48);
AddStep("set 10 circles", () => list.NumberOfCircles = 10);
AddAssert("9 users displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 9);
AddAssert("41 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 41);
}
[Test]
public void TestAddAndRemoveUsers()
{
AddStep("add 50 users", () =>
{
for (int i = 0; i < 50; i++)
addUser(i);
});
AddStep("remove from start", () => removeUserAt(0));
AddAssert("3 circles displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 3);
AddAssert("46 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 46);
AddStep("remove from end", () => removeUserAt(SelectedRoom.Value.RecentParticipants.Count - 1));
AddAssert("3 circles displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 3);
AddAssert("45 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 45);
AddRepeatStep("remove 45 users", () => removeUserAt(0), 45);
AddAssert("3 circles displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 3);
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
AddAssert("hidden users bubble hidden", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Alpha < 0.5f);
AddStep("remove another user", () => removeUserAt(0));
AddAssert("2 circles displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 2);
AddAssert("0 hidden users", () => list.ChildrenOfType<RecentParticipantsList.HiddenUserCount>().Single().Count == 0);
AddRepeatStep("remove the remaining two users", () => removeUserAt(0), 2);
AddAssert("0 circles displayed", () => !list.ChildrenOfType<UpdateableAvatar>().Any());
}
private void addUser(int id)
{
SelectedRoom.Value.RecentParticipants.Add(new User
{
Id = id,
Username = $"User {id}"
});
SelectedRoom.Value.ParticipantCount.Value++;
}
private void removeUserAt(int index)
{
SelectedRoom.Value.RecentParticipants.RemoveAt(index);
SelectedRoom.Value.ParticipantCount.Value--;
}
}
}

View File

@ -1,81 +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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneRoomStatus : OsuTestScene
{
[Test]
public void TestMultipleStatuses()
{
AddStep("create rooms", () =>
{
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Children = new Drawable[]
{
new DrawableRoom(new Room
{
Name = { Value = "Open - ending in 1 day" },
Status = { Value = new RoomStatusOpen() },
EndDate = { Value = DateTimeOffset.Now.AddDays(1) }
}) { MatchingFilter = true },
new DrawableRoom(new Room
{
Name = { Value = "Playing - ending in 1 day" },
Status = { Value = new RoomStatusPlaying() },
EndDate = { Value = DateTimeOffset.Now.AddDays(1) }
}) { MatchingFilter = true },
new DrawableRoom(new Room
{
Name = { Value = "Ended" },
Status = { Value = new RoomStatusEnded() },
EndDate = { Value = DateTimeOffset.Now }
}) { MatchingFilter = true },
new DrawableRoom(new Room
{
Name = { Value = "Open" },
Status = { Value = new RoomStatusOpen() },
Category = { Value = RoomCategory.Realtime }
}) { MatchingFilter = true },
}
};
});
}
[Test]
public void TestEnableAndDisablePassword()
{
DrawableRoom drawableRoom = null;
Room room = null;
AddStep("create room", () => Child = drawableRoom = new DrawableRoom(room = new Room
{
Name = { Value = "Room with password" },
Status = { Value = new RoomStatusOpen() },
Category = { Value = RoomCategory.Realtime },
}) { MatchingFilter = true });
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
AddStep("set password", () => room.Password.Value = "password");
AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
AddStep("unset password", () => room.Password.Value = string.Empty);
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
}
}
}

View File

@ -17,6 +17,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
@ -150,10 +151,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void createRoom(Func<Room> room)
{
AddStep("open room", () =>
{
multiplayerScreen.OpenNewRoom(room());
});
AddStep("open room", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().Single().Open(room()));
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
AddWaitStep("wait for transition", 2);

View File

@ -10,7 +10,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
@ -30,10 +29,9 @@ using osu.Game.Skinning;
using osu.Game.Utils;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual
namespace osu.Game.Tests.Visual.Navigation
{
[TestFixture]
[HeadlessTest]
public class TestSceneOsuGame : OsuTestScene
{
private IReadOnlyList<Type> requiredGameDependencies => new[]

View File

@ -16,6 +16,7 @@ using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
@ -316,7 +317,8 @@ namespace osu.Game.Tests.Visual.Navigation
PushAndConfirm(() => multiplayer = new TestMultiplayer());
AddStep("open room", () => multiplayer.OpenNewRoom());
AddUntilStep("wait for lounge", () => multiplayer.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
AddStep("open room", () => multiplayer.ChildrenOfType<LoungeSubScreen>().Single().Open());
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
AddWaitStep("wait two frames", 2);
}

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using Humanizer;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
@ -95,9 +96,11 @@ namespace osu.Game.Tests.Visual.Online
AddAssert(@"no stream selected", () => changelog.Header.Streams.Current.Value == null);
}
[Test]
public void ShowWithBuild()
[TestCase(false)]
[TestCase(true)]
public void ShowWithBuild(bool isSupporter)
{
AddStep(@"set supporter", () => dummyAPI.LocalUser.Value.IsSupporter = isSupporter);
showBuild(() => new APIChangelogBuild
{
Version = "2018.712.0",
@ -155,6 +158,8 @@ namespace osu.Game.Tests.Visual.Online
AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0);
AddAssert(@"correct build displayed", () => changelog.Current.Value.Version == "2018.712.0");
AddAssert(@"correct stream selected", () => changelog.Header.Streams.Current.Value.Id == 5);
AddUntilStep(@"wait for content load", () => changelog.ChildrenOfType<ChangelogSupporterPromo>().Any());
AddAssert(@"supporter promo showed", () => changelog.ChildrenOfType<ChangelogSupporterPromo>().First().Alpha == (isSupporter ? 0 : 1));
}
[Test]

View File

@ -0,0 +1,35 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Overlays;
using osu.Game.Overlays.Changelog;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneChangelogSupporterPromo : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
public TestSceneChangelogSupporterPromo()
{
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background4,
},
new ChangelogSupporterPromo(),
}
};
}
}
}

View File

@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -68,13 +70,40 @@ namespace osu.Game.Tests.Visual.UserInterface
);
}
private class MyContextMenuContainer : Container, IHasContextMenu
private static MenuItem[] makeMenu()
{
public MenuItem[] ContextMenuItems => new MenuItem[]
return new MenuItem[]
{
new OsuMenuItem(@"Some option"),
new OsuMenuItem(@"Highlighted option", MenuItemType.Highlighted),
new OsuMenuItem(@"Another option"),
new OsuMenuItem(@"Nested option >")
{
Items = new MenuItem[]
{
new OsuMenuItem(@"Sub-One"),
new OsuMenuItem(@"Sub-Two"),
new OsuMenuItem(@"Sub-Three"),
new OsuMenuItem(@"Sub-Nested option >")
{
Items = new MenuItem[]
{
new OsuMenuItem(@"Double Sub-One"),
new OsuMenuItem(@"Double Sub-Two"),
new OsuMenuItem(@"Double Sub-Three"),
new OsuMenuItem(@"Sub-Sub-Nested option >")
{
Items = new MenuItem[]
{
new OsuMenuItem(@"Too Deep One"),
new OsuMenuItem(@"Too Deep Two"),
new OsuMenuItem(@"Too Deep Three"),
}
}
}
}
}
},
new OsuMenuItem(@"Choose me please"),
new OsuMenuItem(@"And me too"),
new OsuMenuItem(@"Trying to fill"),
@ -82,17 +111,29 @@ namespace osu.Game.Tests.Visual.UserInterface
};
}
private class MyContextMenuContainer : Container, IHasContextMenu
{
public MenuItem[] ContextMenuItems => makeMenu();
}
private class AnotherContextMenuContainer : Container, IHasContextMenu
{
public MenuItem[] ContextMenuItems => new MenuItem[]
public MenuItem[] ContextMenuItems
{
new OsuMenuItem(@"Simple option"),
new OsuMenuItem(@"Simple very very long option"),
new OsuMenuItem(@"Change width", MenuItemType.Highlighted, () => this.ResizeWidthTo(Width * 2, 100, Easing.OutQuint)),
new OsuMenuItem(@"Change height", MenuItemType.Highlighted, () => this.ResizeHeightTo(Height * 2, 100, Easing.OutQuint)),
new OsuMenuItem(@"Change width back", MenuItemType.Destructive, () => this.ResizeWidthTo(Width / 2, 100, Easing.OutQuint)),
new OsuMenuItem(@"Change height back", MenuItemType.Destructive, () => this.ResizeHeightTo(Height / 2, 100, Easing.OutQuint)),
};
get
{
List<MenuItem> items = makeMenu().ToList();
items.AddRange(new MenuItem[]
{
new OsuMenuItem(@"Change width", MenuItemType.Highlighted, () => this.ResizeWidthTo(Width * 2, 100, Easing.OutQuint)),
new OsuMenuItem(@"Change height", MenuItemType.Highlighted, () => this.ResizeHeightTo(Height * 2, 100, Easing.OutQuint)),
new OsuMenuItem(@"Change width back", MenuItemType.Destructive, () => this.ResizeWidthTo(Width / 2, 100, Easing.OutQuint)),
new OsuMenuItem(@"Change height back", MenuItemType.Destructive, () => this.ResizeHeightTo(Height / 2, 100, Easing.OutQuint)),
});
return items.ToArray();
}
}
}
}
}

View File

@ -13,8 +13,16 @@ namespace osu.Game.Database
public interface IModelManager<TModel>
where TModel : class
{
/// <summary>
/// A bindable which contains a weak reference to the last item that was updated.
/// This is not thread-safe and should be scheduled locally if consumed from a drawable component.
/// </summary>
IBindable<WeakReference<TModel>> ItemUpdated { get; }
/// <summary>
/// A bindable which contains a weak reference to the last item that was removed.
/// This is not thread-safe and should be scheduled locally if consumed from a drawable component.
/// </summary>
IBindable<WeakReference<TModel>> ItemRemoved { get; }
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
@ -9,6 +10,14 @@ namespace osu.Game.Graphics.Cursor
{
public class OsuContextMenuContainer : ContextMenuContainer
{
protected override Menu CreateMenu() => new OsuContextMenu();
[Cached]
private OsuContextMenuSamples samples = new OsuContextMenuSamples();
public OsuContextMenuContainer()
{
AddInternal(samples);
}
protected override Menu CreateMenu() => new OsuContextMenu(true);
}
}

View File

@ -27,7 +27,7 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
AddRange(new Drawable[]
{
Background = new Box
{
@ -42,7 +42,7 @@ namespace osu.Game.Graphics.UserInterface
Size = new Vector2(13),
Icon = icon,
},
};
});
}
}
}

View File

@ -21,6 +21,9 @@ namespace osu.Game.Graphics.UserInterface
private const float border_width = 3;
private const double animate_in_duration = 150;
private const double animate_out_duration = 500;
public Nub()
{
Box fill;
@ -77,20 +80,26 @@ namespace osu.Game.Graphics.UserInterface
if (value)
{
this.FadeColour(GlowingAccentColour, 500, Easing.OutQuint);
FadeEdgeEffectTo(1, 500, Easing.OutQuint);
this.FadeColour(GlowingAccentColour, animate_in_duration, Easing.OutQuint);
FadeEdgeEffectTo(1, animate_in_duration, Easing.OutQuint);
}
else
{
FadeEdgeEffectTo(0, 500);
this.FadeColour(AccentColour, 500);
FadeEdgeEffectTo(0, animate_out_duration);
this.FadeColour(AccentColour, animate_out_duration);
}
}
}
public bool Expanded
{
set => this.ResizeTo(new Vector2(value ? EXPANDED_SIZE : COLLAPSED_SIZE, 12), 500, Easing.OutQuint);
set
{
if (value)
this.ResizeTo(new Vector2(EXPANDED_SIZE, 12), animate_in_duration, Easing.OutQuint);
else
this.ResizeTo(new Vector2(COLLAPSED_SIZE, 12), animate_out_duration, Easing.OutQuint);
}
}
private readonly Bindable<bool> current = new Bindable<bool>();

View File

@ -3,6 +3,7 @@
using osuTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Effects;
@ -14,7 +15,14 @@ namespace osu.Game.Graphics.UserInterface
{
private const int fade_duration = 250;
public OsuContextMenu()
[Resolved]
private OsuContextMenuSamples samples { get; set; }
// todo: this shouldn't be required after https://github.com/ppy/osu-framework/issues/4519 is fixed.
private bool wasOpened;
private readonly bool playClickSample;
public OsuContextMenu(bool playClickSample = false)
: base(Direction.Vertical)
{
MaskingContainer.CornerRadius = 5;
@ -28,16 +36,38 @@ namespace osu.Game.Graphics.UserInterface
ItemsContainer.Padding = new MarginPadding { Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL };
MaxHeight = 250;
this.playClickSample = playClickSample;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OsuColour colours, AudioManager audio)
{
BackgroundColour = colours.ContextMenuGray;
}
protected override void AnimateOpen() => this.FadeIn(fade_duration, Easing.OutQuint);
protected override void AnimateClose() => this.FadeOut(fade_duration, Easing.OutQuint);
protected override void AnimateOpen()
{
this.FadeIn(fade_duration, Easing.OutQuint);
if (playClickSample)
samples.PlayClickSample();
if (!wasOpened)
samples.PlayOpenSample();
wasOpened = true;
}
protected override void AnimateClose()
{
this.FadeOut(fade_duration, Easing.OutQuint);
if (wasOpened)
samples.PlayCloseSample();
wasOpened = false;
}
protected override Menu CreateSubMenu() => new OsuContextMenu();
}

View File

@ -0,0 +1,35 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
namespace osu.Game.Graphics.UserInterface
{
public class OsuContextMenuSamples : Component
{
private Sample sampleClick;
private Sample sampleOpen;
private Sample sampleClose;
[BackgroundDependencyLoader]
private void load(OsuColour colours, AudioManager audio)
{
sampleClick = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
sampleOpen = audio.Samples.Get(@"UI/dropdown-open");
sampleClose = audio.Samples.Get(@"UI/dropdown-close");
}
public void PlayClickSample() => Scheduler.AddOnce(playClickSample);
private void playClickSample() => sampleClick.Play();
public void PlayOpenSample() => Scheduler.AddOnce(playOpenSample);
private void playOpenSample() => sampleOpen.Play();
public void PlayCloseSample() => Scheduler.AddOnce(playCloseSample);
private void playCloseSample() => sampleClose.Play();
}
}

View File

@ -27,6 +27,14 @@ namespace osu.Game.Online.Multiplayer
/// <exception cref="NotJoinedRoomException">If the user is not in a room.</exception>
Task TransferHost(int userId);
/// <summary>
/// As the host, kick another user from the room.
/// </summary>
/// <param name="userId">The user to kick..</param>
/// <exception cref="NotHostException">A user other than the current host is attempting to kick a user.</exception>
/// <exception cref="NotJoinedRoomException">If the user is not in a room.</exception>
Task KickUser(int userId);
/// <summary>
/// As the host, update the settings of the currently joined room.
/// </summary>

View File

@ -181,6 +181,7 @@ namespace osu.Game.Online.Multiplayer
{
APIRoom = null;
Room = null;
CurrentMatchPlayingItem.Value = null;
PlayingUserIds.Clear();
RoomUpdated?.Invoke();
@ -292,6 +293,8 @@ namespace osu.Game.Online.Multiplayer
public abstract Task TransferHost(int userId);
public abstract Task KickUser(int userId);
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
public abstract Task ChangeState(MultiplayerUserState newState);

View File

@ -91,6 +91,14 @@ namespace osu.Game.Online.Multiplayer
return connection.InvokeAsync(nameof(IMultiplayerServer.TransferHost), userId);
}
public override Task KickUser(int userId)
{
if (!IsConnected.Value)
return Task.CompletedTask;
return connection.InvokeAsync(nameof(IMultiplayerServer.KickUser), userId);
}
public override Task ChangeSettings(MultiplayerRoomSettings settings)
{
if (!IsConnected.Value)

View File

@ -59,8 +59,8 @@ namespace osu.Game.Online.Rooms
protected override bool VerifyDatabasedModel(BeatmapSetInfo databasedSet)
{
int? beatmapId = SelectedItem.Value.Beatmap.Value.OnlineBeatmapID;
string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash;
int? beatmapId = SelectedItem.Value?.Beatmap.Value.OnlineBeatmapID;
string checksum = SelectedItem.Value?.Beatmap.Value.MD5Hash;
var matchingBeatmap = databasedSet.Beatmaps.FirstOrDefault(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum);

View File

@ -8,7 +8,7 @@ namespace osu.Game.Online.Rooms.RoomStatuses
{
public class RoomStatusEnded : RoomStatus
{
public override string Message => @"Ended";
public override string Message => "Ended";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDarker;
}
}

View File

@ -8,7 +8,7 @@ namespace osu.Game.Online.Rooms.RoomStatuses
{
public class RoomStatusOpen : RoomStatus
{
public override string Message => @"Welcoming Players";
public override string Message => "Open";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight;
}
}

View File

@ -8,7 +8,7 @@ namespace osu.Game.Online.Rooms.RoomStatuses
{
public class RoomStatusPlaying : RoomStatus
{
public override string Message => @"Now Playing";
public override string Message => "Playing";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple;
}
}

View File

@ -37,6 +37,13 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
RelativeSizeAxes = Axes.Both,
},
};
button.Add(new DownloadProgressBar(beatmapSet)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Depth = -1,
});
}
protected override void LoadComplete()

View File

@ -71,6 +71,17 @@ namespace osu.Game.Overlays.Changelog
Colour = colourProvider.Background6,
Margin = new MarginPadding { Top = 30 },
},
new ChangelogSupporterPromo
{
Alpha = api.LocalUser.Value.IsSupporter ? 0 : 1,
},
new Box
{
RelativeSizeAxes = Axes.X,
Height = 2,
Colour = colourProvider.Background6,
Alpha = api.LocalUser.Value.IsSupporter ? 0 : 1,
},
comments = new CommentsContainer()
};

View File

@ -0,0 +1,187 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using osu.Game.Resources.Localisation.Web;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Changelog
{
public class ChangelogSupporterPromo : CompositeDrawable
{
private const float image_container_width = 164;
private readonly FillFlowContainer textContainer;
private readonly Container imageContainer;
public ChangelogSupporterPromo()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Padding = new MarginPadding
{
Vertical = 20,
Horizontal = 50,
};
InternalChildren = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Masking = true,
CornerRadius = 6,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.25f),
Offset = new Vector2(0, 1),
Radius = 3,
},
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.3f),
},
new Container
{
RelativeSizeAxes = Axes.X,
Height = 200,
Padding = new MarginPadding { Horizontal = 75 },
Children = new Drawable[]
{
textContainer = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Padding = new MarginPadding { Right = 50 + image_container_width },
},
imageContainer = new Container
{
RelativeSizeAxes = Axes.Y,
Width = image_container_width,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
}
}
},
}
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colour, TextureStore textures)
{
SupporterPromoLinkFlowContainer supportLinkText;
textContainer.Children = new Drawable[]
{
new OsuSpriteText
{
Text = ChangelogStrings.SupportHeading,
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light),
Margin = new MarginPadding { Bottom = 20 },
},
supportLinkText = new SupporterPromoLinkFlowContainer(t =>
{
t.Font = t.Font.With(size: 14);
t.Colour = colour.PinkLighter;
})
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
},
new OsuTextFlowContainer(t =>
{
t.Font = t.Font.With(size: 12);
t.Colour = colour.PinkLighter;
})
{
Text = ChangelogStrings.SupportText2.ToString(),
Margin = new MarginPadding { Top = 10 },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
}
};
supportLinkText.AddText("Support further development of osu! and ");
supportLinkText.AddLink("become and osu!supporter", "https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold));
supportLinkText.AddText(" today!");
imageContainer.Children = new Drawable[]
{
new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill,
Texture = textures.Get(@"Online/supporter-pippi"),
},
new Sprite
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 75,
Height = 75,
Margin = new MarginPadding { Top = 70 },
Texture = textures.Get(@"Online/supporter-heart"),
},
};
}
private class SupporterPromoLinkFlowContainer : LinkFlowContainer
{
public SupporterPromoLinkFlowContainer(Action<SpriteText> defaultCreationParameters)
: base(defaultCreationParameters)
{
}
public new void AddLink(string text, string url, Action<SpriteText> creationParameters) =>
AddInternal(new SupporterPromoLinkCompiler(AddText(text, creationParameters)) { Url = url });
private class SupporterPromoLinkCompiler : DrawableLinkCompiler
{
[Resolved(CanBeNull = true)]
private OsuGame game { get; set; }
public string Url;
public SupporterPromoLinkCompiler(IEnumerable<Drawable> parts)
: base(parts)
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(OsuColour colour)
{
TooltipText = Url;
Action = () => game?.HandleLink(Url);
IdleColour = colour.PinkDark;
HoverColour = Color4.White;
}
}
}
}
}

View File

@ -18,6 +18,7 @@ namespace osu.Game.Overlays
public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green);
public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple);
public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue);
public static OverlayColourProvider Plum { get; } = new OverlayColourProvider(OverlayColourScheme.Plum);
public OverlayColourProvider(OverlayColourScheme colourScheme)
{
@ -80,6 +81,9 @@ namespace osu.Game.Overlays
case OverlayColourScheme.Blue:
return 200 / 360f;
case OverlayColourScheme.Plum:
return 320 / 360f;
}
}
}
@ -92,6 +96,7 @@ namespace osu.Game.Overlays
Lime,
Green,
Purple,
Blue
Blue,
Plum,
}
}

View File

@ -190,7 +190,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
comboIndexBindable.BindValueChanged(_ => UpdateComboColour());
comboIndexWithOffsetsBindable.BindValueChanged(_ => UpdateComboColour(), true);
updateState(ArmedState.Idle, true);
// Apply transforms
updateState(State.Value, true);
}
/// <summary>

View File

@ -1,117 +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.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
namespace osu.Game.Screens.OnlinePlay.Components
{
public class RoomStatusInfo : OnlinePlayComposite
{
public RoomStatusInfo()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
StatusPart statusPart;
EndDatePart endDatePart;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
statusPart = new StatusPart
{
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14)
},
endDatePart = new EndDatePart { Font = OsuFont.GetFont(size: 14) }
}
};
statusPart.EndDate.BindTo(EndDate);
statusPart.Status.BindTo(Status);
statusPart.Availability.BindTo(Availability);
endDatePart.EndDate.BindTo(EndDate);
}
private class EndDatePart : DrawableDate
{
public readonly IBindable<DateTimeOffset?> EndDate = new Bindable<DateTimeOffset?>();
public EndDatePart()
: base(DateTimeOffset.UtcNow)
{
EndDate.BindValueChanged(date =>
{
// If null, set a very large future date to prevent unnecessary schedules.
Date = date.NewValue ?? DateTimeOffset.Now.AddYears(1);
}, true);
}
protected override string Format()
{
if (EndDate.Value == null)
return string.Empty;
var diffToNow = Date.Subtract(DateTimeOffset.Now);
if (diffToNow.TotalSeconds < -5)
return $"Closed {base.Format()}";
if (diffToNow.TotalSeconds < 0)
return "Closed";
if (diffToNow.TotalSeconds < 5)
return "Closing soon";
return $"Closing {base.Format()}";
}
}
private class StatusPart : EndDatePart
{
public readonly IBindable<RoomStatus> Status = new Bindable<RoomStatus>();
public readonly IBindable<RoomAvailability> Availability = new Bindable<RoomAvailability>();
[Resolved]
private OsuColour colours { get; set; }
public StatusPart()
{
EndDate.BindValueChanged(_ => Format());
Status.BindValueChanged(_ => Format());
Availability.BindValueChanged(_ => Format());
}
protected override void LoadComplete()
{
base.LoadComplete();
Text = Format();
}
protected override string Format()
{
if (!IsLoaded)
return string.Empty;
RoomStatus status = Date < DateTimeOffset.Now ? new RoomStatusEnded() : Status.Value ?? new RoomStatusOpen();
this.FadeColour(status.GetAppropriateColour(colours), 100);
return $"{Availability.Value.GetDescription()}, {status.Message}";
}
}
}
}

View File

@ -1,12 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Specialized;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Screens.Ranking.Expanded;
@ -85,6 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
minDisplay.Current.Value = minDifficulty;
maxDisplay.Current.Value = maxDifficulty;
maxDisplay.Alpha = Precision.AlmostEquals(Math.Round(minDifficulty.Stars, 2), Math.Round(maxDifficulty.Stars, 2)) ? 0 : 1;
minBackground.Colour = colours.ForStarDifficulty(minDifficulty.Stars);
maxBackground.Colour = colours.ForStarDifficulty(maxDifficulty.Stars);

View File

@ -11,6 +11,7 @@ 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;
@ -20,7 +21,6 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@ -28,6 +28,7 @@ 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;
using osuTK;
using osuTK.Graphics;
@ -37,21 +38,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
public class DrawableRoom : OsuClickableContainer, IStateful<SelectionState>, IFilterable, IHasContextMenu, IHasPopover, IKeyBindingHandler<GlobalAction>
{
public const float SELECTION_BORDER_WIDTH = 4;
private const float corner_radius = 5;
private const float corner_radius = 10;
private const float transition_duration = 60;
private const float content_padding = 10;
private const float height = 110;
private const float side_strip_width = 5;
private const float cover_width = 145;
private const float height = 100;
public event Action<SelectionState> StateChanged;
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
private readonly Box selectionBox;
private Drawable selectionBox;
[Resolved(canBeNull: true)]
private OnlinePlayScreen parentScreen { get; set; }
private LoungeSubScreen loungeScreen { get; set; }
[Resolved]
private BeatmapManager beatmaps { get; set; }
@ -74,14 +72,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
get => state;
set
{
if (value == state) return;
if (value == state)
return;
state = value;
if (state == SelectionState.Selected)
selectionBox.FadeIn(transition_duration);
else
selectionBox.FadeOut(transition_duration);
if (selectionBox != null)
{
if (state == SelectionState.Selected)
selectionBox.FadeIn(transition_duration);
else
selectionBox.FadeOut(transition_duration);
}
StateChanged?.Invoke(State);
}
@ -108,6 +110,25 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
}
}
private int numberOfAvatars = 7;
public int NumberOfAvatars
{
get => numberOfAvatars;
set
{
numberOfAvatars = value;
if (recentParticipantsList != null)
recentParticipantsList.NumberOfCircles = value;
}
}
private readonly Bindable<RoomCategory> roomCategory = new Bindable<RoomCategory>();
private RecentParticipantsList recentParticipantsList;
private RoomSpecialCategoryPill specialCategoryPill;
public bool FilteringActive { get; set; }
private PasswordProtectedIcon passwordIcon;
@ -119,114 +140,208 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Room = room;
RelativeSizeAxes = Axes.X;
Height = height + SELECTION_BORDER_WIDTH * 2;
CornerRadius = corner_radius + SELECTION_BORDER_WIDTH / 2;
Masking = true;
Height = height;
// create selectionBox here so State can be set before being loaded
selectionBox = new Box
Masking = true;
CornerRadius = corner_radius + SELECTION_BORDER_WIDTH / 2;
EdgeEffect = new EdgeEffectParameters
{
RelativeSizeAxes = Axes.Both,
Alpha = 0f,
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(40),
Radius = 5,
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, AudioManager audio)
private void load(OverlayColourProvider colours, AudioManager audio)
{
float stripWidth = side_strip_width * (Room.Category.Value == RoomCategory.Spotlight ? 2 : 1);
Children = new Drawable[]
{
new StatusColouredContainer(transition_duration)
// This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
new BufferedContainer
{
RelativeSizeAxes = Axes.Both,
Child = selectionBox
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Background5,
},
new OnlinePlayBackgroundSprite
{
RelativeSizeAxes = Axes.Both
},
}
},
new Container
{
Name = @"Room content",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(SELECTION_BORDER_WIDTH),
// This negative padding resolves 1px gaps between this background and the background above.
Padding = new MarginPadding { Left = 20, Vertical = -0.5f },
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = corner_radius,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(40),
Radius = 5,
},
Children = new Drawable[]
{
new Box
// This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
new BufferedContainer
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"212121"),
},
new StatusColouredContainer(transition_duration)
{
RelativeSizeAxes = Axes.Y,
Width = stripWidth,
Child = new Box { RelativeSizeAxes = Axes.Both }
},
new Container
{
RelativeSizeAxes = Axes.Y,
Width = cover_width,
Masking = true,
Margin = new MarginPadding { Left = stripWidth },
Child = new OnlinePlayBackgroundSprite(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Both }
Children = new Drawable[]
{
new GridContainer
{
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Relative, 0.2f)
},
Content = new[]
{
new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Background5,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f))
},
}
}
},
},
},
new Container
{
Name = @"Left details",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
Vertical = content_padding,
Left = stripWidth + cover_width + content_padding,
Right = content_padding,
Left = 20,
Vertical = 5
},
Children = new Drawable[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(5f),
Children = new Drawable[]
{
new RoomName { Font = OsuFont.GetFont(size: 18) },
new ParticipantInfo(),
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Children = new Drawable[]
{
new RoomStatusPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
specialCategoryPill = new RoomSpecialCategoryPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
new EndDateInfo
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 3 },
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new RoomNameText(),
new RoomHostText(),
}
}
},
},
new FillFlowContainer
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Children = new Drawable[]
{
new RoomStatusInfo(),
new BeatmapTitle { TextSize = 14 },
},
},
new ModeTypeInfo
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
},
new PlaylistCountPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
new StarRatingRangeDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Scale = new Vector2(0.8f)
}
}
}
}
},
new FillFlowContainer
{
Name = "Right content",
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Padding = new MarginPadding
{
Right = 10,
Vertical = 5
},
Children = new Drawable[]
{
recentParticipantsList = new RecentParticipantsList
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
NumberOfCircles = NumberOfAvatars
}
}
},
passwordIcon = new PasswordProtectedIcon { Alpha = 0 }
},
},
},
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");
@ -250,6 +365,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
else
Alpha = 0;
roomCategory.BindTo(Room.Category);
roomCategory.BindValueChanged(c =>
{
if (c.NewValue == RoomCategory.Spotlight)
specialCategoryPill.Show();
else
specialCategoryPill.Hide();
}, true);
hasPassword.BindTo(Room.HasPassword);
hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true);
}
@ -260,7 +384,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
new OsuMenuItem("Create copy", MenuItemType.Standard, () =>
{
parentScreen?.OpenNewRoom(Room.DeepClone());
lounge?.Open(Room.DeepClone());
})
};
@ -307,11 +431,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
return base.OnClick(e);
}
private class RoomName : OsuSpriteText
private class RoomNameText : OsuSpriteText
{
[Resolved(typeof(Room), nameof(Online.Rooms.Room.Name))]
private Bindable<string> name { get; set; }
public RoomNameText()
{
Font = OsuFont.GetFont(size: 28);
}
[BackgroundDependencyLoader]
private void load()
{
@ -319,6 +448,41 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
}
}
private class RoomHostText : OnlinePlayComposite
{
private LinkFlowContainer hostText;
public RoomHostText()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = hostText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 16))
{
AutoSizeAxes = Axes.Both
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Host.BindValueChanged(host =>
{
hostText.Clear();
if (host.NewValue != null)
{
hostText.AddText("hosted by ");
hostText.AddUserLink(host.NewValue);
}
}, true);
}
}
public class PasswordProtectedIcon : CompositeDrawable
{
[BackgroundDependencyLoader]
@ -366,7 +530,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private OsuPasswordTextBox passwordTextbox;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load()
{
Child = new FillFlowContainer
{

View File

@ -0,0 +1,65 @@
// 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.Bindables;
using osu.Framework.Graphics;
using osu.Game.Graphics;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public class EndDateInfo : OnlinePlayComposite
{
public EndDateInfo()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new EndDatePart
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12),
EndDate = { BindTarget = EndDate }
};
}
private class EndDatePart : DrawableDate
{
public readonly IBindable<DateTimeOffset?> EndDate = new Bindable<DateTimeOffset?>();
public EndDatePart()
: base(DateTimeOffset.UtcNow)
{
EndDate.BindValueChanged(date =>
{
// If null, set a very large future date to prevent unnecessary schedules.
Date = date.NewValue ?? DateTimeOffset.Now.AddYears(1);
}, true);
}
protected override string Format()
{
if (EndDate.Value == null)
return string.Empty;
var diffToNow = Date.Subtract(DateTimeOffset.Now);
if (diffToNow.TotalSeconds < -5)
return $"Closed {base.Format()}";
if (diffToNow.TotalSeconds < 0)
return "Closed";
if (diffToNow.TotalSeconds < 5)
return "Closing soon";
return $"Closing {base.Format()}";
}
}
}
}

View File

@ -5,19 +5,18 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osuTK.Graphics;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public abstract class FilterControl : CompositeDrawable
{
protected const float VERTICAL_PADDING = 10;
protected const float HORIZONTAL_PADDING = 80;
protected readonly FillFlowContainer Filters;
[Resolved(CanBeNull = true)]
private Bindable<FilterCriteria> filter { get; set; }
@ -25,60 +24,51 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; }
private readonly Box tabStrip;
private readonly SearchTextBox search;
private readonly PageTabControl<RoomStatusFilter> tabs;
private readonly Dropdown<RoomStatusFilter> statusDropdown;
protected FilterControl()
{
InternalChildren = new Drawable[]
RelativeSizeAxes = Axes.X;
Height = 70;
InternalChild = new FillFlowContainer
{
new Box
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(10),
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.25f,
},
tabStrip = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = 1,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
search = new FilterSearchTextBox
{
Top = VERTICAL_PADDING,
Horizontal = HORIZONTAL_PADDING
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.X,
Width = 0.6f,
},
Children = new Drawable[]
Filters = new FillFlowContainer
{
search = new FilterSearchTextBox
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10),
Child = statusDropdown = new SlimEnumDropdown<RoomStatusFilter>
{
RelativeSizeAxes = Axes.X,
},
tabs = new PageTabControl<RoomStatusFilter>
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
},
}
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.None,
Width = 160,
}
},
}
};
tabs.Current.Value = RoomStatusFilter.Open;
tabs.Current.TriggerChange();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
filter ??= new Bindable<FilterCriteria>();
tabStrip.Colour = colours.Yellow;
}
protected override void LoadComplete()
@ -87,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
search.Current.BindValueChanged(_ => updateFilterDebounced());
ruleset.BindValueChanged(_ => UpdateFilter());
tabs.Current.BindValueChanged(_ => UpdateFilter(), true);
statusDropdown.Current.BindValueChanged(_ => UpdateFilter(), true);
}
private ScheduledDelegate scheduledFilterUpdate;
@ -106,7 +96,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
var criteria = CreateCriteria();
criteria.SearchString = search.Current.Value;
criteria.Status = tabs.Current.Value;
criteria.Status = statusDropdown.Current.Value;
criteria.Ruleset = ruleset.Value;
filter.Value = criteria;

View File

@ -1,88 +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 Humanizer;
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.Lounge.Components
{
public class ParticipantInfo : OnlinePlayComposite
{
public ParticipantInfo()
{
RelativeSizeAxes = Axes.X;
Height = 15f;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
OsuSpriteText summary;
Container flagContainer;
LinkFlowContainer hostText;
InternalChildren = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5f, 0f),
Children = new Drawable[]
{
flagContainer = new Container
{
Width = 22f,
RelativeSizeAxes = Axes.Y,
},
hostText = new LinkFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both
}
},
},
new FillFlowContainer
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Colour = colours.Gray9,
Children = new[]
{
summary = new OsuSpriteText
{
Text = "0 participants",
}
},
},
};
Host.BindValueChanged(host =>
{
hostText.Clear();
flagContainer.Clear();
if (host.NewValue != null)
{
hostText.AddText("hosted by ");
hostText.AddUserLink(host.NewValue, s => s.Font = s.Font.With(Typeface.Torus, weight: FontWeight.Bold, italics: true));
flagContainer.Child = new UpdateableFlag(host.NewValue.Country) { RelativeSizeAxes = Axes.Both };
}
}, true);
ParticipantCount.BindValueChanged(count => summary.Text = "participant".ToQuantity(count.NewValue), true);
}
}
}

View File

@ -0,0 +1,81 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
/// <summary>
/// Displays contents in a "pill".
/// </summary>
public class PillContainer : Container
{
private const float padding = 8;
public readonly Drawable Background;
protected override Container<Drawable> Content => content;
private readonly Container content;
public PillContainer()
{
AutoSizeAxes = Axes.X;
Height = 16;
InternalChild = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Masking = true,
Children = new[]
{
Background = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f
},
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = padding },
Child = new GridContainer
{
AutoSizeAxes = Axes.Both,
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize, minSize: 80 - 2 * padding)
},
Content = new[]
{
new[]
{
new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Padding = new MarginPadding { Bottom = 2 },
Child = content = new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
}
}
}
}
}
}
};
}
}
}

View File

@ -0,0 +1,54 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Specialized;
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
/// <summary>
/// A pill that displays the playlist item count.
/// </summary>
public class PlaylistCountPill : OnlinePlayComposite
{
private OsuTextFlowContainer count;
public PlaylistCountPill()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new PillContainer
{
Child = count = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12))
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Playlist.BindCollectionChanged(updateCount, true);
}
private void updateCount(object sender, NotifyCollectionChangedEventArgs e)
{
count.Clear();
count.AddText(Playlist.Count.ToString(), s => s.Font = s.Font.With(weight: FontWeight.Bold));
count.AddText(" ");
count.AddText("Beatmap".ToQuantity(Playlist.Count, ShowQuantityAs.None));
}
}
}

View File

@ -9,18 +9,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public class PlaylistsFilterControl : FilterControl
{
private readonly Dropdown<PlaylistsCategory> dropdown;
private readonly Dropdown<PlaylistsCategory> categoryDropdown;
public PlaylistsFilterControl()
{
AddInternal(dropdown = new SlimEnumDropdown<PlaylistsCategory>
Filters.Add(categoryDropdown = new SlimEnumDropdown<PlaylistsCategory>
{
Anchor = Anchor.BottomRight,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.None,
Width = 160,
X = -HORIZONTAL_PADDING,
Y = -30
});
}
@ -28,14 +26,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
base.LoadComplete();
dropdown.Current.BindValueChanged(_ => UpdateFilter());
categoryDropdown.Current.BindValueChanged(_ => UpdateFilter());
}
protected override FilterCriteria CreateCriteria()
{
var criteria = base.CreateCriteria();
switch (dropdown.Current.Value)
switch (categoryDropdown.Current.Value)
{
case PlaylistsCategory.Normal:
criteria.Category = "normal";

View File

@ -0,0 +1,80 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public class RankRangePill : MultiplayerRoomComposite
{
private OsuTextFlowContainer rankFlow;
public RankRangePill()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new PillContainer
{
Child = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(4),
Children = new Drawable[]
{
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Size = new Vector2(8),
Icon = FontAwesome.Solid.User
},
rankFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12))
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
}
}
}
};
}
protected override void OnRoomUpdated()
{
base.OnRoomUpdated();
rankFlow.Clear();
if (Room == null || Room.Users.All(u => u.User == null))
{
rankFlow.AddText("-");
return;
}
int minRank = Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Min();
int maxRank = Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Max();
rankFlow.AddText("#");
rankFlow.AddText(minRank.ToString("#,0"), s => s.Font = s.Font.With(weight: FontWeight.Bold));
rankFlow.AddText(" - ");
rankFlow.AddText("#");
rankFlow.AddText(maxRank.ToString("#,0"), s => s.Font = s.Font.With(weight: FontWeight.Bold));
}
}
}

View File

@ -0,0 +1,278 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Specialized;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Users;
using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public class RecentParticipantsList : OnlinePlayComposite
{
private const float avatar_size = 36;
private FillFlowContainer<CircularAvatar> avatarFlow;
private HiddenUserCount hiddenUsers;
private OsuSpriteText totalCount;
public RecentParticipantsList()
{
AutoSizeAxes = Axes.X;
Height = 60;
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colours)
{
InternalChildren = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 10,
Shear = new Vector2(0.2f, 0),
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Background4,
}
},
new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(4),
Padding = new MarginPadding { Right = 16 },
Children = new Drawable[]
{
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Size = new Vector2(16),
Margin = new MarginPadding { Left = 8 },
Icon = FontAwesome.Solid.User,
},
totalCount = new OsuSpriteText
{
Font = OsuFont.Default.With(weight: FontWeight.Bold),
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
avatarFlow = new FillFlowContainer<CircularAvatar>
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(4),
Margin = new MarginPadding { Left = 4 },
},
hiddenUsers = new HiddenUserCount
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
}
}
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
RecentParticipants.BindCollectionChanged(onParticipantsChanged, true);
ParticipantCount.BindValueChanged(_ =>
{
updateHiddenUsers();
totalCount.Text = ParticipantCount.Value.ToString();
}, true);
}
private int numberOfCircles = 4;
/// <summary>
/// The maximum number of circles visible (including the "hidden count" circle in the overflow case).
/// </summary>
public int NumberOfCircles
{
get => numberOfCircles;
set
{
numberOfCircles = value;
if (LoadState < LoadState.Loaded)
return;
// Reinitialising the list looks janky, but this is unlikely to be used in a setting where it's visible.
clearUsers();
foreach (var u in RecentParticipants)
addUser(u);
updateHiddenUsers();
}
}
private void onParticipantsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var added in e.NewItems.OfType<User>())
addUser(added);
break;
case NotifyCollectionChangedAction.Remove:
foreach (var removed in e.OldItems.OfType<User>())
removeUser(removed);
break;
case NotifyCollectionChangedAction.Reset:
clearUsers();
break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
// Easiest is to just reinitialise the whole list. These are unlikely to ever be use cases.
clearUsers();
foreach (var u in RecentParticipants)
addUser(u);
break;
}
updateHiddenUsers();
}
private int displayedCircles => avatarFlow.Count + (hiddenUsers.Count > 0 ? 1 : 0);
private void addUser(User user)
{
if (displayedCircles < NumberOfCircles)
avatarFlow.Add(new CircularAvatar { User = user });
}
private void removeUser(User user)
{
avatarFlow.RemoveAll(a => a.User == user);
}
private void clearUsers()
{
avatarFlow.Clear();
updateHiddenUsers();
}
private void updateHiddenUsers()
{
int hiddenCount = 0;
if (RecentParticipants.Count > NumberOfCircles)
hiddenCount = ParticipantCount.Value - NumberOfCircles + 1;
hiddenUsers.Count = hiddenCount;
if (displayedCircles > NumberOfCircles)
avatarFlow.Remove(avatarFlow.Last());
else if (displayedCircles < NumberOfCircles)
{
var nextUser = RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u));
if (nextUser != null) addUser(nextUser);
}
}
private class CircularAvatar : CompositeDrawable
{
public User User
{
get => avatar.User;
set => avatar.User = value;
}
private readonly UpdateableAvatar avatar = new UpdateableAvatar(showUsernameTooltip: true) { RelativeSizeAxes = Axes.Both };
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colours)
{
Size = new Vector2(avatar_size);
InternalChild = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new Drawable[]
{
new Box
{
Colour = colours.Background5,
RelativeSizeAxes = Axes.Both,
},
avatar
}
};
}
}
public class HiddenUserCount : CompositeDrawable
{
public int Count
{
get => count;
set
{
count = value;
countText.Text = $"+{count}";
if (count > 0)
Show();
else
Hide();
}
}
private int count;
private readonly SpriteText countText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.Default.With(weight: FontWeight.Bold),
};
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colours)
{
Size = new Vector2(avatar_size);
Alpha = 0;
InternalChild = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Background5,
},
countText
}
};
}
}
}
}

View File

@ -1,86 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.OnlinePlay.Components;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public class RoomInfo : OnlinePlayComposite
{
private readonly List<Drawable> statusElements = new List<Drawable>();
private readonly OsuTextFlowContainer roomName;
public RoomInfo()
{
AutoSizeAxes = Axes.Y;
RoomLocalUserInfo localUserInfo;
RoomStatusInfo statusInfo;
ModeTypeInfo typeInfo;
ParticipantInfo participantInfo;
InternalChild = new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.X,
Spacing = new Vector2(0, 10),
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
roomName = new OsuTextFlowContainer(t => t.Font = OsuFont.GetFont(size: 30))
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
},
participantInfo = new ParticipantInfo(),
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
statusInfo = new RoomStatusInfo(),
typeInfo = new ModeTypeInfo
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight
}
}
},
localUserInfo = new RoomLocalUserInfo(),
}
};
statusElements.AddRange(new Drawable[]
{
statusInfo, typeInfo, participantInfo, localUserInfo
});
}
protected override void LoadComplete()
{
base.LoadComplete();
if (RoomID.Value == null)
statusElements.ForEach(e => e.FadeOut());
RoomID.BindValueChanged(id =>
{
if (id.NewValue == null)
statusElements.ForEach(e => e.FadeOut(100));
else
statusElements.ForEach(e => e.FadeIn(100));
}, true);
RoomName.BindValueChanged(name =>
{
roomName.Text = name.NewValue ?? "No room selected";
}, true);
}
}
}

View File

@ -1,91 +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.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Screens.OnlinePlay.Components;
using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public class RoomInspector : OnlinePlayComposite
{
private const float transition_duration = 100;
private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 };
[Resolved]
private BeatmapManager beatmaps { get; set; }
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
OverlinedHeader participantsHeader;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.25f
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 30 },
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new RoomInfo
{
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding { Vertical = 60 },
},
participantsHeader = new OverlinedHeader("Recent Participants"),
new ParticipantsDisplay(Direction.Vertical)
{
RelativeSizeAxes = Axes.X,
Height = ParticipantsList.TILE_SIZE * 3,
Details = { BindTarget = participantsHeader.Details }
}
}
}
},
new Drawable[] { new OverlinedPlaylistHeader(), },
new Drawable[]
{
new DrawableRoomPlaylist(false, false)
{
RelativeSizeAxes = Axes.Both,
Items = { BindTarget = Playlist }
},
},
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
}
}
}
};
}
}
}

View File

@ -0,0 +1,49 @@
// 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.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public class RoomSpecialCategoryPill : OnlinePlayComposite
{
private SpriteText text;
public RoomSpecialCategoryPill()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
InternalChild = new PillContainer
{
Background =
{
Colour = colours.Pink,
Alpha = 1
},
Child = text = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12),
Colour = Color4.Black
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Category.BindValueChanged(c => text.Text = c.NewValue.ToString(), true);
}
}
}

View File

@ -0,0 +1,74 @@
// 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.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
/// <summary>
/// A pill that displays the room's current status.
/// </summary>
public class RoomStatusPill : OnlinePlayComposite
{
[Resolved]
private OsuColour colours { get; set; }
private PillContainer pill;
private SpriteText statusText;
public RoomStatusPill()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = pill = new PillContainer
{
Child = statusText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12),
Colour = Color4.Black
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
EndDate.BindValueChanged(_ => updateDisplay());
Status.BindValueChanged(_ => updateDisplay(), true);
FinishTransforms(true);
}
private void updateDisplay()
{
RoomStatus status = getDisplayStatus();
pill.Background.Alpha = 1;
pill.Background.FadeColour(status.GetAppropriateColour(colours), 100);
statusText.Text = status.Message;
}
private RoomStatus getDisplayStatus()
{
if (EndDate.Value < DateTimeOffset.Now)
return new RoomStatusEnded();
return Status.Value;
}
}
}

View File

@ -50,6 +50,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
// account for the fact we are in a scroll container and want a bit of spacing from the scroll bar.
Padding = new MarginPadding { Right = 5 };
InternalChild = new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.X,
@ -59,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(2),
Spacing = new Vector2(10),
}
};
}

View File

@ -9,6 +9,7 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Screens;
using osu.Game.Graphics.Containers;
@ -18,6 +19,8 @@ using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Lounge
{
@ -28,11 +31,17 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
protected Container<OsuButton> Buttons { get; } = new Container<OsuButton>
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
AutoSizeAxes = Axes.Both
};
private readonly IBindable<bool> initialRoomsReceived = new Bindable<bool>();
private readonly IBindable<bool> operationInProgress = new Bindable<bool>();
private FilterControl filter;
private Container content;
private LoadingLayer loadingLayer;
[Resolved]
@ -56,41 +65,71 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
InternalChildren = new Drawable[]
{
content = new Container
new Box
{
RelativeSizeAxes = Axes.X,
Height = 100,
Colour = Color4.Black,
Alpha = 0.5f,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
Padding = new MarginPadding
{
new Container
Top = 20,
Left = WaveOverlayContainer.WIDTH_PADDING,
Right = WaveOverlayContainer.WIDTH_PADDING,
},
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
RelativeSizeAxes = Axes.Both,
Width = 0.55f,
Children = new Drawable[]
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Absolute, 20)
},
Content = new[]
{
new Drawable[]
{
scrollContainer = new OsuScrollContainer
new Container
{
RelativeSizeAxes = Axes.X,
Height = 70,
Depth = -1,
Children = new Drawable[]
{
filter = CreateFilterControl(),
Buttons.WithChild(CreateNewRoomButton().With(d =>
{
d.Size = new Vector2(150, 25);
d.Action = () => Open();
}))
}
}
},
null,
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
ScrollbarOverlapsContent = false,
Padding = new MarginPadding(10),
Child = roomsContainer = new RoomsContainer()
Children = new Drawable[]
{
scrollContainer = new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
ScrollbarOverlapsContent = false,
Child = roomsContainer = new RoomsContainer()
},
loadingLayer = new LoadingLayer(true),
}
},
loadingLayer = new LoadingLayer(true),
}
},
new RoomInspector
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Both,
Width = 0.45f,
},
}
},
},
filter = CreateFilterControl().With(d =>
{
d.RelativeSizeAxes = Axes.X;
d.Height = 80;
})
}
};
// scroll selected room into view on selection.
@ -116,18 +155,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
}
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
content.Padding = new MarginPadding
{
Top = filter.DrawHeight,
Left = WaveOverlayContainer.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH + HORIZONTAL_OVERFLOW_PADDING,
Right = WaveOverlayContainer.WIDTH_PADDING + HORIZONTAL_OVERFLOW_PADDING,
};
}
protected override void OnFocus(FocusEvent e)
{
filter.TakeFocus();
@ -199,13 +226,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
/// <summary>
/// Push a room as a new subscreen.
/// </summary>
public void Open(Room room) => Schedule(() =>
/// <param name="room">An optional template to use when creating the room.</param>
public void Open(Room room = null) => Schedule(() =>
{
// Handles the case where a room is clicked 3 times in quick succession
if (!this.IsCurrentScreen())
return;
OpenNewRoom(room);
OpenNewRoom(room ?? CreateNewRoom());
});
protected virtual void OpenNewRoom(Room room)
@ -217,6 +245,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
protected abstract FilterControl CreateFilterControl();
protected abstract OsuButton CreateNewRoomButton();
/// <summary>
/// Creates a new room.
/// </summary>
/// <returns>The created <see cref="Room"/>.</returns>
protected abstract Room CreateNewRoom();
protected abstract RoomSubScreen CreateRoomSubScreen(Room room);
private void updateLoadingLayer()

View File

@ -12,6 +12,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
[BackgroundDependencyLoader]
private void load()
{
SpriteText.Font = SpriteText.Font.With(size: 14);
Triangles.TriangleScale = 1.5f;
}

View File

@ -4,9 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
@ -54,20 +52,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Logger.Log($"Polling adjusted (listing: {multiplayerRoomManager.TimeBetweenListingPolls.Value}, selection: {multiplayerRoomManager.TimeBetweenSelectionPolls.Value})");
}
protected override Room CreateNewRoom() =>
new Room
{
Name = { Value = $"{API.LocalUser}'s awesome room" },
Category = { Value = RoomCategory.Realtime },
Type = { Value = MatchType.HeadToHead },
};
protected override string ScreenTitle => "Multiplayer";
protected override RoomManager CreateRoomManager() => new MultiplayerRoomManager();
protected override LoungeSubScreen CreateLounge() => new MultiplayerLoungeSubScreen();
protected override OsuButton CreateNewMultiplayerGameButton() => new CreateMultiplayerMatchButton();
}
}

View File

@ -3,6 +3,8 @@
using osu.Framework.Allocation;
using osu.Framework.Logging;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Lounge;
@ -13,13 +15,25 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
public class MultiplayerLoungeSubScreen : LoungeSubScreen
{
protected override FilterControl CreateFilterControl() => new MultiplayerFilterControl();
protected override RoomSubScreen CreateRoomSubScreen(Room room) => new MultiplayerMatchSubScreen(room);
[Resolved]
private IAPIProvider api { get; set; }
[Resolved]
private MultiplayerClient client { get; set; }
protected override FilterControl CreateFilterControl() => new MultiplayerFilterControl();
protected override OsuButton CreateNewRoomButton() => new CreateMultiplayerMatchButton();
protected override Room CreateNewRoom() => new Room
{
Name = { Value = $"{api.LocalUser}'s awesome room" },
Category = { Value = RoomCategory.Realtime },
Type = { Value = MatchType.HeadToHead },
};
protected override RoomSubScreen CreateRoomSubScreen(Room room) => new MultiplayerMatchSubScreen(room);
protected override void OpenNewRoom(Room room)
{
if (client?.IsConnected.Value != true)

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
@ -42,6 +43,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
private ModDisplay userModsDisplay;
private StateDisplay userStateDisplay;
private IconButton kickButton;
public ParticipantPanel(MultiplayerRoomUser user)
{
User = user;
@ -64,7 +67,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
{
new Dimension(GridSizeMode.Absolute, 18),
new Dimension(GridSizeMode.AutoSize),
new Dimension()
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
@ -157,7 +161,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
Margin = new MarginPadding { Right = 10 },
}
}
}
},
kickButton = new KickButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
Margin = new MarginPadding(4),
Action = () =>
{
Debug.Assert(user != null);
Client.KickUser(user.Id);
}
},
},
}
};
@ -167,7 +184,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
{
base.OnRoomUpdated();
if (Room == null)
if (Room == null || Client.LocalUser == null)
return;
const double fade_time = 50;
@ -179,6 +196,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability);
if (Client.IsHost && !User.Equals(Client.LocalUser))
kickButton.FadeIn(fade_time);
else
kickButton.FadeOut(fade_time);
if (Room.Host?.Equals(User) == true)
crown.FadeIn(fade_time);
else
@ -211,13 +233,36 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
new OsuMenuItem("Give host", MenuItemType.Standard, () =>
{
// Ensure the local user is still host.
if (Room.Host?.UserID != api.LocalUser.Value.Id)
if (!Client.IsHost)
return;
Client.TransferHost(targetUser);
}),
new OsuMenuItem("Kick", MenuItemType.Destructive, () =>
{
// Ensure the local user is still host.
if (!Client.IsHost)
return;
Client.KickUser(targetUser);
})
};
}
}
public class KickButton : IconButton
{
public KickButton()
{
Icon = FontAwesome.Solid.UserTimes;
TooltipText = "Kick";
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
IconHoverColour = colours.Red;
}
}
}
}

View File

@ -55,7 +55,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
public MultiSpectatorScreen(MultiplayerRoomUser[] users)
: base(users.Select(u => u.UserID).ToArray())
{
// todo: this is a bit ugly, but not sure on a better way to handle.
this.users = users;
instances = new PlayerArea[Users.Count];

View File

@ -35,6 +35,9 @@ namespace osu.Game.Screens.OnlinePlay
[Resolved(typeof(Room))]
protected BindableList<PlaylistItem> Playlist { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<RoomCategory> Category { get; private set; }
[Resolved(typeof(Room))]
protected BindableList<User> RecentParticipants { get; private set; }

View File

@ -13,7 +13,6 @@ using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
@ -24,13 +23,15 @@ using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Screens.OnlinePlay
{
[Cached]
public abstract class OnlinePlayScreen : OsuScreen, IHasSubScreenStack
{
[Cached]
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
public override bool CursorVisible => (screenStack?.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true;
// this is required due to PlayerLoader eventually being pushed to the main stack
@ -38,12 +39,8 @@ namespace osu.Game.Screens.OnlinePlay
public override bool DisallowExternalBeatmapRulesetChanges => true;
private MultiplayerWaveContainer waves;
private OsuButton createButton;
private ScreenStack screenStack;
private LoungeSubScreen loungeSubScreen;
private ScreenStack screenStack;
private readonly IBindable<bool> isIdle = new BindableBool();
@ -146,20 +143,8 @@ namespace osu.Game.Screens.OnlinePlay
}
},
new Header(ScreenTitle, screenStack),
createButton = CreateNewMultiplayerGameButton().With(button =>
{
button.Anchor = Anchor.TopRight;
button.Origin = Anchor.TopRight;
button.Size = new Vector2(150, Header.HEIGHT - 20);
button.Margin = new MarginPadding
{
Top = 10,
Right = 10 + HORIZONTAL_OVERFLOW_PADDING,
};
button.Action = () => OpenNewRoom();
}),
RoomManager,
ongoingOperationTracker,
ongoingOperationTracker
}
};
}
@ -292,18 +277,6 @@ namespace osu.Game.Screens.OnlinePlay
logo.Delay(WaveContainer.DISAPPEAR_DURATION / 2).FadeOut();
}
/// <summary>
/// Creates and opens the newly-created room.
/// </summary>
/// <param name="room">An optional template to use when creating the room.</param>
public void OpenNewRoom(Room room = null) => loungeSubScreen.Open(room ?? CreateNewRoom());
/// <summary>
/// Creates a new room.
/// </summary>
/// <returns>The created <see cref="Room"/>.</returns>
protected abstract Room CreateNewRoom();
private void screenPushed(IScreen lastScreen, IScreen newScreen)
{
subScreenChanged(lastScreen, newScreen);
@ -339,7 +312,6 @@ namespace osu.Game.Screens.OnlinePlay
((IBindable<UserActivity>)Activity).BindTo(newOsuScreen.Activity);
UpdatePollingRate(isIdle.Value);
createButton.FadeTo(newScreen is LoungeSubScreen ? 1 : 0, 200);
}
protected IScreen CurrentSubScreen => screenStack.CurrentScreen;
@ -350,8 +322,6 @@ namespace osu.Game.Screens.OnlinePlay
protected abstract LoungeSubScreen CreateLounge();
protected abstract OsuButton CreateNewMultiplayerGameButton();
private class MultiplayerWaveContainer : WaveContainer
{
protected override bool StartHidden => true;

View File

@ -3,8 +3,6 @@
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Match;
@ -46,21 +44,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Logger.Log($"Polling adjusted (listing: {playlistsManager.TimeBetweenListingPolls.Value}, selection: {playlistsManager.TimeBetweenSelectionPolls.Value})");
}
protected override Room CreateNewRoom()
{
return new Room
{
Name = { Value = $"{API.LocalUser}'s awesome playlist" },
Type = { Value = MatchType.Playlists }
};
}
protected override string ScreenTitle => "Playlists";
protected override RoomManager CreateRoomManager() => new PlaylistsRoomManager();
protected override LoungeSubScreen CreateLounge() => new PlaylistsLoungeSubScreen();
protected override OsuButton CreateNewMultiplayerGameButton() => new CreatePlaylistsRoomButton();
}
}

View File

@ -1,6 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
@ -10,8 +13,22 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
public class PlaylistsLoungeSubScreen : LoungeSubScreen
{
[Resolved]
private IAPIProvider api { get; set; }
protected override FilterControl CreateFilterControl() => new PlaylistsFilterControl();
protected override OsuButton CreateNewRoomButton() => new CreatePlaylistsRoomButton();
protected override Room CreateNewRoom()
{
return new Room
{
Name = { Value = $"{api.LocalUser}'s awesome playlist" },
Type = { Value = MatchType.Playlists }
};
}
protected override RoomSubScreen CreateRoomSubScreen(Room room) => new PlaylistsRoomSubScreen(room);
}
}

View File

@ -962,7 +962,7 @@ namespace osu.Game.Screens.Play
screenSuspension?.Expire();
// if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap.
if (Score != null && prepareScoreForDisplayTask == null)
if (prepareScoreForDisplayTask == null)
{
Score.ScoreInfo.Passed = false;
// potentially should be ScoreRank.F instead? this is the best alternative for now.

View File

@ -174,6 +174,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
public override Task TransferHost(int userId) => ((IMultiplayerClient)this).HostChanged(userId);
public override Task KickUser(int userId)
{
Debug.Assert(Room != null);
return ((IMultiplayerClient)this).UserLeft(Room.Users.Single(u => u.UserID == userId));
}
public override async Task ChangeSettings(MultiplayerRoomSettings settings)
{
Debug.Assert(Room != null);

View File

@ -7,6 +7,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
@ -46,6 +47,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
CacheAs(Filter);
CacheAs(OngoingOperationTracker);
CacheAs(AvailabilityTracker);
CacheAs(new OverlayColourProvider(OverlayColourScheme.Plum));
}
public object Get(Type type)

View File

@ -36,8 +36,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="10.3.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.809.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.808.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.811.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.810.0" />
<PackageReference Include="Sentry" Version="3.8.3" />
<PackageReference Include="SharpCompress" Version="0.28.3" />
<PackageReference Include="NUnit" Version="3.13.2" />

View File

@ -70,8 +70,8 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.809.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.808.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.811.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.810.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
<PropertyGroup>
@ -93,7 +93,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.809.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.811.0" />
<PackageReference Include="SharpCompress" Version="0.28.3" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" />