mirror of
https://github.com/ppy/osu.git
synced 2025-03-14 05:47:20 +08:00
Merge pull request #31260 from smoogipoo/multiplayer-free-style
Add support for "freestyle" in multiplayer
This commit is contained in:
commit
15ed029dd1
@ -60,14 +60,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
private void setUp()
|
||||
{
|
||||
AddStep("reset", () =>
|
||||
AddStep("create song select", () =>
|
||||
{
|
||||
Ruleset.Value = new OsuRuleset().RulesetInfo;
|
||||
Beatmap.SetDefault();
|
||||
SelectedMods.SetDefault();
|
||||
|
||||
LoadScreen(songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value!));
|
||||
});
|
||||
|
||||
AddStep("create song select", () => LoadScreen(songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value!)));
|
||||
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
@ -271,7 +272,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddUntilStep("last playlist item selected", () =>
|
||||
{
|
||||
var lastItem = this.ChildrenOfType<DrawableRoomPlaylistItem>().Single(p => p.Item.ID == MultiplayerClient.ServerAPIRoom?.Playlist.Last().ID);
|
||||
var lastItem = this.ChildrenOfType<MultiplayerQueueList>()
|
||||
.Single()
|
||||
.ChildrenOfType<DrawableRoomPlaylistItem>()
|
||||
.Single(p => p.Item.ID == MultiplayerClient.ServerAPIRoom?.Playlist.Last().ID);
|
||||
return lastItem.IsSelectedItem;
|
||||
});
|
||||
}
|
||||
|
@ -308,6 +308,33 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("set state: locally available", () => MultiplayerClient.ChangeUserBeatmapAvailability(0, BeatmapAvailability.LocallyAvailable()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserWithStyle()
|
||||
{
|
||||
AddStep("add users", () =>
|
||||
{
|
||||
MultiplayerClient.AddUser(new APIUser
|
||||
{
|
||||
Id = 0,
|
||||
Username = "User 0",
|
||||
RulesetsStatistics = new Dictionary<string, UserStatistics>
|
||||
{
|
||||
{
|
||||
Ruleset.Value.ShortName,
|
||||
new UserStatistics { GlobalRank = RNG.Next(1, 100000), }
|
||||
}
|
||||
},
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
});
|
||||
|
||||
MultiplayerClient.ChangeUserStyle(0, 259, 2);
|
||||
});
|
||||
|
||||
AddStep("set beatmap locally available", () => MultiplayerClient.ChangeUserBeatmapAvailability(0, BeatmapAvailability.LocallyAvailable()));
|
||||
AddStep("change user style to beatmap: 258, ruleset: 1", () => MultiplayerClient.ChangeUserStyle(0, 258, 1));
|
||||
AddStep("change user style to beatmap: null, ruleset: null", () => MultiplayerClient.ChangeUserStyle(0, null, null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestModOverlap()
|
||||
{
|
||||
|
@ -24,6 +24,21 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString StartMatchWithCountdown(string humanReadableTime) => new TranslatableString(getKey(@"start_match_width_countdown"), @"Start match in {0}", humanReadableTime);
|
||||
|
||||
/// <summary>
|
||||
/// "Choose the mods which all players should play with."
|
||||
/// </summary>
|
||||
public static LocalisableString RequiredModsButtonTooltip => new TranslatableString(getKey(@"required_mods_button_tooltip"), @"Choose the mods which all players should play with.");
|
||||
|
||||
/// <summary>
|
||||
/// "Each player can choose their preferred mods from a selected list."
|
||||
/// </summary>
|
||||
public static LocalisableString FreeModsButtonTooltip => new TranslatableString(getKey(@"free_mods_button_tooltip"), @"Each player can choose their preferred mods from a selected list.");
|
||||
|
||||
/// <summary>
|
||||
/// "Each player can choose their preferred difficulty, ruleset and mods."
|
||||
/// </summary>
|
||||
public static LocalisableString FreestyleButtonTooltip => new TranslatableString(getKey(@"freestyle_button_tooltip"), @"Each player can choose their preferred difficulty, ruleset and mods.");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +95,14 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// <param name="beatmapAvailability">The new beatmap availability state of the user.</param>
|
||||
Task UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability);
|
||||
|
||||
/// <summary>
|
||||
/// Signals that a user in this room changed their style.
|
||||
/// </summary>
|
||||
/// <param name="userId">The ID of the user whose style changed.</param>
|
||||
/// <param name="beatmapId">The user's beatmap.</param>
|
||||
/// <param name="rulesetId">The user's ruleset.</param>
|
||||
Task UserStyleChanged(int userId, int? beatmapId, int? rulesetId);
|
||||
|
||||
/// <summary>
|
||||
/// Signals that a user in this room changed their local mods.
|
||||
/// </summary>
|
||||
|
@ -57,6 +57,13 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// <param name="newBeatmapAvailability">The proposed new beatmap availability state.</param>
|
||||
Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability);
|
||||
|
||||
/// <summary>
|
||||
/// Change the local user's style in the currently joined room.
|
||||
/// </summary>
|
||||
/// <param name="beatmapId">The beatmap.</param>
|
||||
/// <param name="rulesetId">The ruleset.</param>
|
||||
Task ChangeUserStyle(int? beatmapId, int? rulesetId);
|
||||
|
||||
/// <summary>
|
||||
/// Change the local user's mods in the currently joined room.
|
||||
/// </summary>
|
||||
|
@ -358,6 +358,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
public abstract Task DisconnectInternal();
|
||||
|
||||
public abstract Task ChangeUserStyle(int? beatmapId, int? rulesetId);
|
||||
|
||||
/// <summary>
|
||||
/// Change the local user's mods in the currently joined room.
|
||||
/// </summary>
|
||||
@ -653,6 +655,25 @@ namespace osu.Game.Online.Multiplayer
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task UserStyleChanged(int userId, int? beatmapId, int? rulesetId)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
var user = Room?.Users.SingleOrDefault(u => u.UserID == userId);
|
||||
|
||||
// errors here are not critical - user style is mostly for display.
|
||||
if (user == null)
|
||||
return;
|
||||
|
||||
user.BeatmapId = beatmapId;
|
||||
user.RulesetId = rulesetId;
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task UserModsChanged(int userId, IEnumerable<APIMod> mods)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
|
@ -22,9 +22,6 @@ namespace osu.Game.Online.Multiplayer
|
||||
[Key(1)]
|
||||
public MultiplayerUserState State { get; set; } = MultiplayerUserState.Idle;
|
||||
|
||||
[Key(4)]
|
||||
public MatchUserState? MatchState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The availability state of the current beatmap.
|
||||
/// </summary>
|
||||
@ -37,6 +34,21 @@ namespace osu.Game.Online.Multiplayer
|
||||
[Key(3)]
|
||||
public IEnumerable<APIMod> Mods { get; set; } = Enumerable.Empty<APIMod>();
|
||||
|
||||
[Key(4)]
|
||||
public MatchUserState? MatchState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If not-null, a local override for this user's ruleset selection.
|
||||
/// </summary>
|
||||
[Key(5)]
|
||||
public int? RulesetId;
|
||||
|
||||
/// <summary>
|
||||
/// If not-null, a local override for this user's beatmap selection.
|
||||
/// </summary>
|
||||
[Key(6)]
|
||||
public int? BeatmapId;
|
||||
|
||||
[IgnoreMember]
|
||||
public APIUser? User { get; set; }
|
||||
|
||||
|
@ -60,6 +60,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
connection.On(nameof(IMultiplayerClient.GameplayStarted), ((IMultiplayerClient)this).GameplayStarted);
|
||||
connection.On<GameplayAbortReason>(nameof(IMultiplayerClient.GameplayAborted), ((IMultiplayerClient)this).GameplayAborted);
|
||||
connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady);
|
||||
connection.On<int, int?, int?>(nameof(IMultiplayerClient.UserStyleChanged), ((IMultiplayerClient)this).UserStyleChanged);
|
||||
connection.On<int, IEnumerable<APIMod>>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged);
|
||||
connection.On<int, BeatmapAvailability>(nameof(IMultiplayerClient.UserBeatmapAvailabilityChanged), ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged);
|
||||
connection.On<MatchRoomState>(nameof(IMultiplayerClient.MatchRoomStateChanged), ((IMultiplayerClient)this).MatchRoomStateChanged);
|
||||
@ -186,6 +187,16 @@ namespace osu.Game.Online.Multiplayer
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeBeatmapAvailability), newBeatmapAvailability);
|
||||
}
|
||||
|
||||
public override Task ChangeUserStyle(int? beatmapId, int? rulesetId)
|
||||
{
|
||||
if (!IsConnected.Value)
|
||||
return Task.CompletedTask;
|
||||
|
||||
Debug.Assert(connection != null);
|
||||
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeUserStyle), beatmapId, rulesetId);
|
||||
}
|
||||
|
||||
public override Task ChangeUserMods(IEnumerable<APIMod> newMods)
|
||||
{
|
||||
if (!IsConnected.Value)
|
||||
|
@ -31,6 +31,7 @@ namespace osu.Game.Online.Rooms
|
||||
var req = base.CreateWebRequest();
|
||||
req.Method = HttpMethod.Post;
|
||||
req.AddParameter("version_hash", versionHash);
|
||||
req.AddParameter("beatmap_id", beatmapInfo.OnlineID.ToString(CultureInfo.InvariantCulture));
|
||||
req.AddParameter("beatmap_hash", beatmapInfo.MD5Hash);
|
||||
req.AddParameter("ruleset_id", rulesetId.ToString(CultureInfo.InvariantCulture));
|
||||
return req;
|
||||
|
@ -56,6 +56,12 @@ namespace osu.Game.Online.Rooms
|
||||
[Key(10)]
|
||||
public double StarRating { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether participants in the room are able to pick their own choice of beatmap difficulty and ruleset.
|
||||
/// </summary>
|
||||
[Key(11)]
|
||||
public bool Freestyle { get; set; }
|
||||
|
||||
[SerializationConstructor]
|
||||
public MultiplayerPlaylistItem()
|
||||
{
|
||||
|
@ -77,11 +77,14 @@ namespace osu.Game.Online.Rooms
|
||||
[CanBeNull]
|
||||
public MultiplayerScoresAround ScoresAround { get; set; }
|
||||
|
||||
public ScoreInfo CreateScoreInfo(ScoreManager scoreManager, RulesetStore rulesets, PlaylistItem playlistItem, [NotNull] BeatmapInfo beatmap)
|
||||
[JsonProperty("ruleset_id")]
|
||||
public int RulesetId { get; set; }
|
||||
|
||||
public ScoreInfo CreateScoreInfo(ScoreManager scoreManager, RulesetStore rulesets, [NotNull] BeatmapInfo beatmap)
|
||||
{
|
||||
var ruleset = rulesets.GetRuleset(playlistItem.RulesetID);
|
||||
var ruleset = rulesets.GetRuleset(RulesetId);
|
||||
if (ruleset == null)
|
||||
throw new InvalidOperationException($"Couldn't create score with unknown ruleset: {playlistItem.RulesetID}");
|
||||
throw new InvalidOperationException($"Couldn't create score with unknown ruleset: {RulesetId}");
|
||||
|
||||
var rulesetInstance = ruleset.CreateInstance();
|
||||
|
||||
@ -91,7 +94,7 @@ namespace osu.Game.Online.Rooms
|
||||
TotalScore = TotalScore,
|
||||
MaxCombo = MaxCombo,
|
||||
BeatmapInfo = beatmap,
|
||||
Ruleset = rulesets.GetRuleset(playlistItem.RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {playlistItem.RulesetID} not found locally"),
|
||||
Ruleset = ruleset,
|
||||
Passed = Passed,
|
||||
Statistics = Statistics,
|
||||
MaximumStatistics = MaximumStatistics,
|
||||
|
@ -67,6 +67,12 @@ namespace osu.Game.Online.Rooms
|
||||
set => Beatmap = new APIBeatmap { OnlineID = value };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether participants in the room are able to pick their own choice of beatmap difficulty and ruleset.
|
||||
/// </summary>
|
||||
[JsonProperty("freestyle")]
|
||||
public bool Freestyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A beatmap representing this playlist item.
|
||||
/// In many cases, this will *not* contain any usable information apart from OnlineID.
|
||||
@ -101,6 +107,7 @@ namespace osu.Game.Online.Rooms
|
||||
PlayedAt = item.PlayedAt;
|
||||
RequiredMods = item.RequiredMods.ToArray();
|
||||
AllowedMods = item.AllowedMods.ToArray();
|
||||
Freestyle = item.Freestyle;
|
||||
}
|
||||
|
||||
public void MarkInvalid() => valid.Value = false;
|
||||
@ -120,18 +127,19 @@ namespace osu.Game.Online.Rooms
|
||||
|
||||
#endregion
|
||||
|
||||
public PlaylistItem With(Optional<long> id = default, Optional<IBeatmapInfo> beatmap = default, Optional<ushort?> playlistOrder = default)
|
||||
public PlaylistItem With(Optional<long> id = default, Optional<IBeatmapInfo> beatmap = default, Optional<ushort?> playlistOrder = default, Optional<int> ruleset = default)
|
||||
{
|
||||
return new PlaylistItem(beatmap.GetOr(Beatmap))
|
||||
{
|
||||
ID = id.GetOr(ID),
|
||||
OwnerID = OwnerID,
|
||||
RulesetID = RulesetID,
|
||||
RulesetID = ruleset.GetOr(RulesetID),
|
||||
Expired = Expired,
|
||||
PlaylistOrder = playlistOrder.GetOr(PlaylistOrder),
|
||||
PlayedAt = PlayedAt,
|
||||
AllowedMods = AllowedMods,
|
||||
RequiredMods = RequiredMods,
|
||||
Freestyle = Freestyle,
|
||||
valid = { Value = Valid.Value },
|
||||
};
|
||||
}
|
||||
@ -143,6 +151,7 @@ namespace osu.Game.Online.Rooms
|
||||
&& Expired == other.Expired
|
||||
&& PlaylistOrder == other.PlaylistOrder
|
||||
&& AllowedMods.SequenceEqual(other.AllowedMods)
|
||||
&& RequiredMods.SequenceEqual(other.RequiredMods);
|
||||
&& RequiredMods.SequenceEqual(other.RequiredMods)
|
||||
&& Freestyle == other.Freestyle;
|
||||
}
|
||||
}
|
||||
|
@ -161,7 +161,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new DrawableRoomPlaylistItem(playlistItem)
|
||||
new DrawableRoomPlaylistItem(playlistItem, true)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AllowReordering = false,
|
||||
|
@ -142,10 +142,10 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
|
||||
request.Success += req => Schedule(() =>
|
||||
{
|
||||
var best = req.Scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo)).ToArray();
|
||||
var best = req.Scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, beatmap.Value.BeatmapInfo)).ToArray();
|
||||
|
||||
userBestScore.Value = req.UserScore;
|
||||
var userBest = userBestScore.Value?.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo);
|
||||
var userBest = userBestScore.Value?.CreateScoreInfo(scoreManager, rulesets, beatmap.Value.BeatmapInfo);
|
||||
|
||||
cancellationTokenSource?.Cancel();
|
||||
cancellationTokenSource = null;
|
||||
|
@ -74,7 +74,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
public bool IsSelectedItem => SelectedItem.Value?.ID == Item.ID;
|
||||
|
||||
private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both };
|
||||
private readonly DelayedLoadWrapper onScreenLoader;
|
||||
private readonly IBindable<bool> valid = new Bindable<bool>();
|
||||
|
||||
private IBeatmapInfo? beatmap;
|
||||
@ -120,9 +120,11 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
[Resolved(CanBeNull = true)]
|
||||
private ManageCollectionsDialog? manageCollectionsDialog { get; set; }
|
||||
|
||||
public DrawableRoomPlaylistItem(PlaylistItem item)
|
||||
public DrawableRoomPlaylistItem(PlaylistItem item, bool loadImmediately = false)
|
||||
: base(item)
|
||||
{
|
||||
onScreenLoader = new DelayedLoadWrapper(Empty, timeBeforeLoad: loadImmediately ? 0 : 500) { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
Item = item;
|
||||
|
||||
valid.BindTo(item.Valid);
|
||||
|
@ -18,6 +18,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Select;
|
||||
using osuTK;
|
||||
using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
@ -36,8 +37,9 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
}
|
||||
}
|
||||
|
||||
private OsuSpriteText count = null!;
|
||||
public new Action Action { set => throw new NotSupportedException("The click action is handled by the button itself."); }
|
||||
|
||||
private OsuSpriteText count = null!;
|
||||
private Circle circle = null!;
|
||||
|
||||
private readonly FreeModSelectOverlay freeModSelectOverlay;
|
||||
@ -45,6 +47,9 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
public FooterButtonFreeMods(FreeModSelectOverlay freeModSelectOverlay)
|
||||
{
|
||||
this.freeModSelectOverlay = freeModSelectOverlay;
|
||||
|
||||
// Overwrite any external behaviour as we delegate the main toggle action to a sub-button.
|
||||
base.Action = toggleAllFreeMods;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
@ -91,6 +96,8 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
SelectedColour = colours.Yellow;
|
||||
DeselectedColour = SelectedColour.Opacity(0.5f);
|
||||
Text = @"freemods";
|
||||
|
||||
TooltipText = MultiplayerMatchStrings.FreeModsButtonTooltip;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -98,9 +105,6 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindValueChanged(_ => updateModDisplay(), true);
|
||||
|
||||
// Overwrite any external behaviour as we delegate the main toggle action to a sub-button.
|
||||
Action = toggleAllFreeMods;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
103
osu.Game/Screens/OnlinePlay/FooterButtonFreestyle.cs
Normal file
103
osu.Game/Screens/OnlinePlay/FooterButtonFreestyle.cs
Normal file
@ -0,0 +1,103 @@
|
||||
// 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.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
public partial class FooterButtonFreestyle : FooterButton, IHasCurrentValue<bool>
|
||||
{
|
||||
private readonly BindableWithCurrent<bool> current = new BindableWithCurrent<bool>();
|
||||
|
||||
public Bindable<bool> Current
|
||||
{
|
||||
get => current.Current;
|
||||
set => current.Current = value;
|
||||
}
|
||||
|
||||
public new Action Action { set => throw new NotSupportedException("The click action is handled by the button itself."); }
|
||||
|
||||
private OsuSpriteText text = null!;
|
||||
private Circle circle = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public FooterButtonFreestyle()
|
||||
{
|
||||
// Overwrite any external behaviour as we delegate the main toggle action to a sub-button.
|
||||
base.Action = () => current.Value = !current.Value;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
ButtonContentContainer.AddRange(new[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
circle = new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Colour = colours.YellowDark,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Padding = new MarginPadding(5),
|
||||
UseFullGlyphHeight = false,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
SelectedColour = colours.Yellow;
|
||||
DeselectedColour = SelectedColour.Opacity(0.5f);
|
||||
Text = @"freestyle";
|
||||
|
||||
TooltipText = MultiplayerMatchStrings.FreestyleButtonTooltip;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindValueChanged(_ => updateDisplay(), true);
|
||||
}
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
if (current.Value)
|
||||
{
|
||||
text.Text = "on";
|
||||
text.FadeColour(colours.Gray2, 200, Easing.OutQuint);
|
||||
circle.FadeColour(colours.Yellow, 200, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Text = "off";
|
||||
text.FadeColour(colours.GrayF, 200, Easing.OutQuint);
|
||||
circle.FadeColour(colours.Gray4, 200, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -179,6 +179,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
},
|
||||
new FreestyleStatusPill(Room)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
},
|
||||
endDateInfo = new EndDateInfo(Room)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
|
@ -0,0 +1,64 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.ComponentModel;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
public partial class FreestyleStatusPill : OnlinePlayPill
|
||||
{
|
||||
private readonly Room room;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
protected override FontUsage Font => base.Font.With(weight: FontWeight.SemiBold);
|
||||
|
||||
public FreestyleStatusPill(Room room)
|
||||
{
|
||||
this.room = room;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Pill.Background.Alpha = 1;
|
||||
Pill.Background.Colour = colours.Yellow;
|
||||
|
||||
TextFlow.Text = "Freestyle";
|
||||
TextFlow.Colour = Color4.Black;
|
||||
|
||||
room.PropertyChanged += onRoomPropertyChanged;
|
||||
updateFreestyleStatus();
|
||||
}
|
||||
|
||||
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(Room.CurrentPlaylistItem):
|
||||
case nameof(Room.Playlist):
|
||||
updateFreestyleStatus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFreestyleStatus()
|
||||
{
|
||||
PlaylistItem? currentItem = room.Playlist.GetCurrentItem() ?? room.CurrentPlaylistItem;
|
||||
Alpha = currentItem?.Freestyle == true ? 1 : 0;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
room.PropertyChanged -= onRoomPropertyChanged;
|
||||
}
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Utils;
|
||||
using Container = osu.Framework.Graphics.Containers.Container;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Match
|
||||
@ -50,7 +51,18 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
/// A container that provides controls for selection of user mods.
|
||||
/// This will be shown/hidden automatically when applicable.
|
||||
/// </summary>
|
||||
protected Drawable? UserModsSection;
|
||||
protected Drawable UserModsSection = null!;
|
||||
|
||||
/// <summary>
|
||||
/// A container that provides controls for selection of the user style.
|
||||
/// This will be shown/hidden automatically when applicable.
|
||||
/// </summary>
|
||||
protected Drawable UserStyleSection = null!;
|
||||
|
||||
/// <summary>
|
||||
/// A container that will display the user's style.
|
||||
/// </summary>
|
||||
protected Container<DrawableRoomPlaylistItem> UserStyleDisplayContainer = null!;
|
||||
|
||||
private Sample? sampleStart;
|
||||
|
||||
@ -254,11 +266,11 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
|
||||
UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods));
|
||||
SelectedItem.BindValueChanged(_ => updateSpecifics());
|
||||
UserMods.BindValueChanged(_ => updateSpecifics());
|
||||
|
||||
beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem);
|
||||
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap());
|
||||
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateSpecifics());
|
||||
|
||||
userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(UserModsSelectOverlay);
|
||||
|
||||
@ -327,7 +339,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
public override void OnSuspending(ScreenTransitionEvent e)
|
||||
{
|
||||
// Should be a noop in most cases, but let's ensure beyond doubt that the beatmap is in a correct state.
|
||||
updateWorkingBeatmap();
|
||||
updateSpecifics();
|
||||
|
||||
onLeaving();
|
||||
base.OnSuspending(e);
|
||||
@ -336,10 +348,10 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
public override void OnResuming(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnResuming(e);
|
||||
updateWorkingBeatmap();
|
||||
|
||||
updateSpecifics();
|
||||
|
||||
beginHandlingTrack();
|
||||
Scheduler.AddOnce(UpdateMods);
|
||||
Scheduler.AddOnce(updateRuleset);
|
||||
}
|
||||
|
||||
protected bool ExitConfirmed { get; private set; }
|
||||
@ -389,9 +401,13 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
|
||||
protected void StartPlay()
|
||||
{
|
||||
if (SelectedItem.Value == null)
|
||||
if (SelectedItem.Value is not PlaylistItem item)
|
||||
return;
|
||||
|
||||
item = item.With(
|
||||
ruleset: GetGameplayRuleset().OnlineID,
|
||||
beatmap: new Optional<IBeatmapInfo>(GetGameplayBeatmap()));
|
||||
|
||||
// User may be at song select or otherwise when the host starts gameplay.
|
||||
// Ensure that they first return to this screen, else global bindables (beatmap etc.) may be in a bad state.
|
||||
if (!this.IsCurrentScreen())
|
||||
@ -407,7 +423,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
// fallback is to allow this class to operate when there is no parent OnlineScreen (testing purposes).
|
||||
var targetScreen = (Screen?)ParentScreen ?? this;
|
||||
|
||||
targetScreen.Push(CreateGameplayScreen(SelectedItem.Value));
|
||||
targetScreen.Push(CreateGameplayScreen(item));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -417,66 +433,75 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
/// <returns>The screen to enter.</returns>
|
||||
protected abstract Screen CreateGameplayScreen(PlaylistItem selectedItem);
|
||||
|
||||
private void selectedItemChanged()
|
||||
private void updateSpecifics()
|
||||
{
|
||||
updateWorkingBeatmap();
|
||||
|
||||
if (SelectedItem.Value is not PlaylistItem selected)
|
||||
if (!this.IsCurrentScreen() || SelectedItem.Value is not PlaylistItem item)
|
||||
return;
|
||||
|
||||
var rulesetInstance = Rulesets.GetRuleset(selected.RulesetID)?.CreateInstance();
|
||||
Debug.Assert(rulesetInstance != null);
|
||||
var allowedMods = selected.AllowedMods.Select(m => m.ToMod(rulesetInstance));
|
||||
var rulesetInstance = GetGameplayRuleset().CreateInstance();
|
||||
|
||||
// Remove any user mods that are no longer allowed.
|
||||
UserMods.Value = UserMods.Value.Where(m => allowedMods.Any(a => m.GetType() == a.GetType())).ToList();
|
||||
Mod[] allowedMods = item.AllowedMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
|
||||
Mod[] newUserMods = UserMods.Value.Where(m => allowedMods.Any(a => m.GetType() == a.GetType())).ToArray();
|
||||
if (!newUserMods.SequenceEqual(UserMods.Value))
|
||||
UserMods.Value = UserMods.Value.Where(m => allowedMods.Any(a => m.GetType() == a.GetType())).ToList();
|
||||
|
||||
UpdateMods();
|
||||
updateRuleset();
|
||||
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
|
||||
int beatmapId = GetGameplayBeatmap().OnlineID;
|
||||
var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == beatmapId);
|
||||
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
|
||||
UserModsSelectOverlay.Beatmap.Value = Beatmap.Value;
|
||||
|
||||
if (!selected.AllowedMods.Any())
|
||||
Mods.Value = GetGameplayMods().Select(m => m.ToMod(rulesetInstance)).ToArray();
|
||||
Ruleset.Value = GetGameplayRuleset();
|
||||
|
||||
bool freeMod = item.AllowedMods.Any();
|
||||
bool freestyle = item.Freestyle;
|
||||
|
||||
// For now, the game can never be in a state where freemod and freestyle are on at the same time.
|
||||
// This will change, but due to the current implementation if this was to occur drawables will overlap so let's assert.
|
||||
Debug.Assert(!freeMod || !freestyle);
|
||||
|
||||
if (freeMod)
|
||||
{
|
||||
UserModsSection?.Hide();
|
||||
UserModsSelectOverlay.Hide();
|
||||
UserModsSelectOverlay.IsValidMod = _ => false;
|
||||
UserModsSection.Show();
|
||||
UserModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType());
|
||||
}
|
||||
else
|
||||
{
|
||||
UserModsSection?.Show();
|
||||
UserModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType());
|
||||
UserModsSection.Hide();
|
||||
UserModsSelectOverlay.Hide();
|
||||
UserModsSelectOverlay.IsValidMod = _ => false;
|
||||
}
|
||||
|
||||
if (freestyle)
|
||||
{
|
||||
UserStyleSection.Show();
|
||||
|
||||
PlaylistItem gameplayItem = SelectedItem.Value.With(ruleset: GetGameplayRuleset().OnlineID, beatmap: new Optional<IBeatmapInfo>(GetGameplayBeatmap()));
|
||||
PlaylistItem? currentItem = UserStyleDisplayContainer.SingleOrDefault()?.Item;
|
||||
|
||||
if (gameplayItem.Equals(currentItem))
|
||||
return;
|
||||
|
||||
UserStyleDisplayContainer.Child = new DrawableRoomPlaylistItem(gameplayItem, true)
|
||||
{
|
||||
AllowReordering = false,
|
||||
AllowEditing = freestyle,
|
||||
RequestEdit = _ => OpenStyleSelection()
|
||||
};
|
||||
}
|
||||
else
|
||||
UserStyleSection.Hide();
|
||||
}
|
||||
|
||||
private void updateWorkingBeatmap()
|
||||
{
|
||||
if (SelectedItem.Value == null || !this.IsCurrentScreen())
|
||||
return;
|
||||
protected virtual APIMod[] GetGameplayMods() => UserMods.Value.Select(m => new APIMod(m)).Concat(SelectedItem.Value!.RequiredMods).ToArray();
|
||||
|
||||
var beatmap = SelectedItem.Value?.Beatmap;
|
||||
protected virtual RulesetInfo GetGameplayRuleset() => Rulesets.GetRuleset(SelectedItem.Value!.RulesetID)!;
|
||||
|
||||
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
|
||||
var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID);
|
||||
protected virtual IBeatmapInfo GetGameplayBeatmap() => SelectedItem.Value!.Beatmap;
|
||||
|
||||
UserModsSelectOverlay.Beatmap.Value = Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
|
||||
}
|
||||
|
||||
protected virtual void UpdateMods()
|
||||
{
|
||||
if (SelectedItem.Value == null || !this.IsCurrentScreen())
|
||||
return;
|
||||
|
||||
var rulesetInstance = Rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance();
|
||||
Debug.Assert(rulesetInstance != null);
|
||||
Mods.Value = UserMods.Value.Concat(SelectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))).ToList();
|
||||
}
|
||||
|
||||
private void updateRuleset()
|
||||
{
|
||||
if (SelectedItem.Value == null || !this.IsCurrentScreen())
|
||||
return;
|
||||
|
||||
Ruleset.Value = Rulesets.GetRuleset(SelectedItem.Value.RulesetID);
|
||||
}
|
||||
protected abstract void OpenStyleSelection();
|
||||
|
||||
private void beginHandlingTrack()
|
||||
{
|
||||
|
@ -0,0 +1,89 @@
|
||||
// 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.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
public partial class MultiplayerMatchFreestyleSelect : OnlinePlayFreestyleSelect
|
||||
{
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OngoingOperationTracker operationTracker { get; set; } = null!;
|
||||
|
||||
private readonly IBindable<bool> operationInProgress = new Bindable<bool>();
|
||||
|
||||
private LoadingLayer loadingLayer = null!;
|
||||
private IDisposable? selectionOperation;
|
||||
|
||||
public MultiplayerMatchFreestyleSelect(Room room, PlaylistItem item)
|
||||
: base(room, item)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(loadingLayer = new LoadingLayer(true));
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
operationInProgress.BindTo(operationTracker.InProgress);
|
||||
operationInProgress.BindValueChanged(_ => updateLoadingLayer(), true);
|
||||
}
|
||||
|
||||
private void updateLoadingLayer()
|
||||
{
|
||||
if (operationInProgress.Value)
|
||||
loadingLayer.Show();
|
||||
else
|
||||
loadingLayer.Hide();
|
||||
}
|
||||
|
||||
protected override bool OnStart()
|
||||
{
|
||||
if (operationInProgress.Value)
|
||||
{
|
||||
Logger.Log($"{nameof(OnStart)} aborted due to {nameof(operationInProgress)}");
|
||||
return false;
|
||||
}
|
||||
|
||||
selectionOperation = operationTracker.BeginOperation();
|
||||
|
||||
client.ChangeUserStyle(Beatmap.Value.BeatmapInfo.OnlineID, Ruleset.Value.OnlineID)
|
||||
.FireAndForget(onSuccess: () =>
|
||||
{
|
||||
selectionOperation.Dispose();
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
// If an error or server side trigger occurred this screen may have already exited by external means.
|
||||
if (this.IsCurrentScreen())
|
||||
this.Exit();
|
||||
});
|
||||
}, onError: _ =>
|
||||
{
|
||||
selectionOperation.Dispose();
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
Carousel.AllowSelection = true;
|
||||
});
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -86,7 +86,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
BeatmapChecksum = item.Beatmap.MD5Hash,
|
||||
RulesetID = item.RulesetID,
|
||||
RequiredMods = item.RequiredMods.ToArray(),
|
||||
AllowedMods = item.AllowedMods.ToArray()
|
||||
AllowedMods = item.AllowedMods.ToArray(),
|
||||
Freestyle = item.Freestyle
|
||||
};
|
||||
|
||||
Task task = itemToEdit != null ? client.EditPlaylistItem(multiplayerItem) : client.AddPlaylistItem(multiplayerItem);
|
||||
|
@ -16,6 +16,8 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
@ -145,43 +147,66 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
SelectedItem = SelectedItem
|
||||
}
|
||||
},
|
||||
new[]
|
||||
new Drawable[]
|
||||
{
|
||||
UserModsSection = new FillFlowContainer
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Top = 10 },
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
Children = new[]
|
||||
{
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
UserModsSection = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new UserModSelectButton
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 90,
|
||||
Text = "Select",
|
||||
Action = ShowUserModSelect,
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Current = UserMods,
|
||||
Scale = new Vector2(0.8f),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new UserModSelectButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 90,
|
||||
Text = "Select",
|
||||
Action = ShowUserModSelect,
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Current = UserMods,
|
||||
Scale = new Vector2(0.8f),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
UserStyleSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Difficulty"),
|
||||
UserStyleDisplayContainer = new Container<DrawableRoomPlaylistItem>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
RowDimensions = new[]
|
||||
@ -228,6 +253,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit));
|
||||
}
|
||||
|
||||
protected override void OpenStyleSelection()
|
||||
{
|
||||
if (!this.IsCurrentScreen() || SelectedItem.Value is not PlaylistItem item)
|
||||
return;
|
||||
|
||||
this.Push(new MultiplayerMatchFreestyleSelect(Room, item));
|
||||
}
|
||||
|
||||
protected override Drawable CreateFooter() => new MultiplayerMatchFooter
|
||||
{
|
||||
SelectedItem = SelectedItem
|
||||
@ -238,16 +271,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
SelectedItem = SelectedItem
|
||||
};
|
||||
|
||||
protected override void UpdateMods()
|
||||
protected override APIMod[] GetGameplayMods()
|
||||
{
|
||||
if (SelectedItem.Value == null || client.LocalUser == null || !this.IsCurrentScreen())
|
||||
return;
|
||||
// Using the room's reported status makes the server authoritative.
|
||||
return client.LocalUser?.Mods != null ? client.LocalUser.Mods.Concat(SelectedItem.Value!.RequiredMods).ToArray() : base.GetGameplayMods();
|
||||
}
|
||||
|
||||
// update local mods based on room's reported status for the local user (omitting the base call implementation).
|
||||
// this makes the server authoritative, and avoids the local user potentially setting mods that the server is not aware of (ie. if the match was started during the selection being changed).
|
||||
var rulesetInstance = Rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance();
|
||||
Debug.Assert(rulesetInstance != null);
|
||||
Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(rulesetInstance)).Concat(SelectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))).ToList();
|
||||
protected override RulesetInfo GetGameplayRuleset()
|
||||
{
|
||||
// Using the room's reported status makes the server authoritative.
|
||||
return client.LocalUser?.RulesetId != null ? Rulesets.GetRuleset(client.LocalUser.RulesetId.Value)! : base.GetGameplayRuleset();
|
||||
}
|
||||
|
||||
protected override IBeatmapInfo GetGameplayBeatmap()
|
||||
{
|
||||
// Using the room's reported status makes the server authoritative.
|
||||
return client.LocalUser?.BeatmapId != null ? new APIBeatmap { OnlineID = client.LocalUser.BeatmapId.Value } : base.GetGameplayBeatmap();
|
||||
}
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
@ -349,23 +388,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
return;
|
||||
}
|
||||
|
||||
updateCurrentItem();
|
||||
SelectedItem.Value = Room.Playlist.SingleOrDefault(i => i.ID == client.Room.Settings.PlaylistItemId);
|
||||
|
||||
addItemButton.Alpha = localUserCanAddItem ? 1 : 0;
|
||||
|
||||
Scheduler.AddOnce(UpdateMods);
|
||||
|
||||
Activity.Value = new UserActivity.InLobby(Room);
|
||||
}
|
||||
|
||||
private bool localUserCanAddItem => client.IsHost || Room.QueueMode != QueueMode.HostOnly;
|
||||
|
||||
private void updateCurrentItem()
|
||||
{
|
||||
Debug.Assert(client.Room != null);
|
||||
SelectedItem.Value = Room.Playlist.SingleOrDefault(i => i.ID == client.Room.Settings.PlaylistItemId);
|
||||
}
|
||||
|
||||
private void handleRoomLost() => Schedule(() =>
|
||||
{
|
||||
Logger.Log($"{this} exiting due to loss of room or connection");
|
||||
|
@ -4,6 +4,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
@ -14,6 +16,9 @@ using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -47,6 +52,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
private SpriteIcon crown = null!;
|
||||
|
||||
private OsuSpriteText userRankText = null!;
|
||||
private StyleDisplayIcon userStyleDisplay = null!;
|
||||
private ModDisplay userModsDisplay = null!;
|
||||
private StateDisplay userStateDisplay = null!;
|
||||
|
||||
@ -149,16 +155,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Margin = new MarginPadding { Right = 70 },
|
||||
Child = userModsDisplay = new ModDisplay
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Scale = new Vector2(0.5f),
|
||||
ExpansionMode = ExpansionMode.AlwaysContracted,
|
||||
userStyleDisplay = new StyleDisplayIcon(),
|
||||
userModsDisplay = new ModDisplay
|
||||
{
|
||||
Scale = new Vector2(0.5f),
|
||||
ExpansionMode = ExpansionMode.AlwaysContracted,
|
||||
}
|
||||
}
|
||||
},
|
||||
userStateDisplay = new StateDisplay
|
||||
@ -208,9 +218,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability);
|
||||
|
||||
if ((User.BeatmapAvailability.State == DownloadState.LocallyAvailable) && (User.State != MultiplayerUserState.Spectating))
|
||||
{
|
||||
userModsDisplay.FadeIn(fade_time);
|
||||
userStyleDisplay.FadeIn(fade_time);
|
||||
}
|
||||
else
|
||||
{
|
||||
userModsDisplay.FadeOut(fade_time);
|
||||
userStyleDisplay.FadeOut(fade_time);
|
||||
}
|
||||
|
||||
if ((User.BeatmapId == null && User.RulesetId == null) || (User.BeatmapId == currentItem?.BeatmapID && User.RulesetId == currentItem?.RulesetID))
|
||||
userStyleDisplay.Style = null;
|
||||
else
|
||||
userStyleDisplay.Style = (User.BeatmapId ?? currentItem?.BeatmapID ?? 0, User.RulesetId ?? currentItem?.RulesetID ?? 0);
|
||||
|
||||
kickButton.Alpha = client.IsHost && !User.Equals(client.LocalUser) ? 1 : 0;
|
||||
crown.Alpha = client.Room.Host?.Equals(User) == true ? 1 : 0;
|
||||
@ -284,5 +305,81 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
IconHoverColour = colours.Red;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class StyleDisplayIcon : CompositeComponent
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapLookupCache beatmapLookupCache { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; } = null!;
|
||||
|
||||
public StyleDisplayIcon()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
private (int beatmap, int ruleset)? style;
|
||||
|
||||
public (int beatmap, int ruleset)? Style
|
||||
{
|
||||
get => style;
|
||||
set
|
||||
{
|
||||
if (style == value)
|
||||
return;
|
||||
|
||||
style = value;
|
||||
Scheduler.Add(refresh);
|
||||
}
|
||||
}
|
||||
|
||||
private CancellationTokenSource? cancellationSource;
|
||||
|
||||
private void refresh()
|
||||
{
|
||||
cancellationSource?.Cancel();
|
||||
cancellationSource?.Dispose();
|
||||
cancellationSource = null;
|
||||
|
||||
if (Style == null)
|
||||
{
|
||||
ClearInternal();
|
||||
return;
|
||||
}
|
||||
|
||||
cancellationSource = new CancellationTokenSource();
|
||||
CancellationToken token = cancellationSource.Token;
|
||||
|
||||
int localBeatmap = Style.Value.beatmap;
|
||||
int localRuleset = Style.Value.ruleset;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var beatmap = await beatmapLookupCache.GetBeatmapAsync(localBeatmap, token).ConfigureAwait(false);
|
||||
if (beatmap == null)
|
||||
return;
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
InternalChild = new DifficultyIcon(beatmap, rulesets.GetRuleset(localRuleset))
|
||||
{
|
||||
Size = new Vector2(20),
|
||||
TooltipType = DifficultyIconTooltipType.Extended,
|
||||
};
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"Error while populating participant style icon {e}");
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
104
osu.Game/Screens/OnlinePlay/OnlinePlayFreestyleSelect.cs
Normal file
104
osu.Game/Screens/OnlinePlay/OnlinePlayFreestyleSelect.cs
Normal file
@ -0,0 +1,104 @@
|
||||
// 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 Humanizer;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
public abstract partial class OnlinePlayFreestyleSelect : SongSelect, IOnlinePlaySubScreen, IHandlePresentBeatmap
|
||||
{
|
||||
public string ShortTitle => "style selection";
|
||||
|
||||
public override string Title => ShortTitle.Humanize();
|
||||
|
||||
public override bool AllowEditing => false;
|
||||
|
||||
protected override UserActivity InitialActivity => new UserActivity.InLobby(room);
|
||||
|
||||
private readonly Room room;
|
||||
private readonly PlaylistItem item;
|
||||
|
||||
protected OnlinePlayFreestyleSelect(Room room, PlaylistItem item)
|
||||
{
|
||||
this.room = room;
|
||||
this.item = item;
|
||||
|
||||
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
LeftArea.Padding = new MarginPadding { Top = Header.HEIGHT };
|
||||
}
|
||||
|
||||
protected override FilterControl CreateFilterControl() => new DifficultySelectFilterControl(item);
|
||||
|
||||
protected override IEnumerable<(FooterButton button, OverlayContainer? overlay)> CreateSongSelectFooterButtons()
|
||||
{
|
||||
// Required to create the drawable components.
|
||||
base.CreateSongSelectFooterButtons();
|
||||
return Enumerable.Empty<(FooterButton, OverlayContainer?)>();
|
||||
}
|
||||
|
||||
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
|
||||
|
||||
public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset)
|
||||
{
|
||||
// This screen cannot present beatmaps.
|
||||
}
|
||||
|
||||
private partial class DifficultySelectFilterControl : FilterControl
|
||||
{
|
||||
private readonly PlaylistItem item;
|
||||
private double itemLength;
|
||||
private int beatmapSetId;
|
||||
|
||||
public DifficultySelectFilterControl(PlaylistItem item)
|
||||
{
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RealmAccess realm)
|
||||
{
|
||||
realm.Run(r =>
|
||||
{
|
||||
int beatmapId = item.Beatmap.OnlineID;
|
||||
BeatmapInfo? beatmap = r.All<BeatmapInfo>().FirstOrDefault(b => b.OnlineID == beatmapId);
|
||||
|
||||
itemLength = beatmap?.Length ?? 0;
|
||||
beatmapSetId = beatmap?.BeatmapSet?.OnlineID ?? 0;
|
||||
});
|
||||
}
|
||||
|
||||
public override FilterCriteria CreateCriteria()
|
||||
{
|
||||
var criteria = base.CreateCriteria();
|
||||
|
||||
// Must be from the same set as the playlist item.
|
||||
criteria.BeatmapSetId = beatmapSetId;
|
||||
criteria.HasOnlineID = true;
|
||||
|
||||
// Must be within 30s of the playlist item.
|
||||
criteria.Length.Min = itemLength - 30000;
|
||||
criteria.Length.Max = itemLength + 30000;
|
||||
criteria.Length.IsLowerInclusive = true;
|
||||
criteria.Length.IsUpperInclusive = true;
|
||||
|
||||
return criteria;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Utils;
|
||||
using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
@ -41,10 +42,12 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
protected override UserActivity InitialActivity => new UserActivity.InLobby(room);
|
||||
|
||||
protected readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
protected readonly Bindable<bool> Freestyle = new Bindable<bool>();
|
||||
|
||||
private readonly Room room;
|
||||
private readonly PlaylistItem? initialItem;
|
||||
private readonly FreeModSelectOverlay freeModSelectOverlay;
|
||||
private readonly FreeModSelectOverlay freeModSelect;
|
||||
private FooterButton freeModsFooterButton = null!;
|
||||
|
||||
private IDisposable? freeModSelectOverlayRegistration;
|
||||
|
||||
@ -61,7 +64,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
|
||||
|
||||
freeModSelectOverlay = new FreeModSelectOverlay
|
||||
freeModSelect = new FreeModSelectOverlay
|
||||
{
|
||||
SelectedMods = { BindTarget = FreeMods },
|
||||
IsValidMod = IsValidFreeMod,
|
||||
@ -72,7 +75,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
private void load()
|
||||
{
|
||||
LeftArea.Padding = new MarginPadding { Top = Header.HEIGHT };
|
||||
LoadComponent(freeModSelectOverlay);
|
||||
LoadComponent(freeModSelect);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -108,12 +111,35 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
Mods.Value = initialItem.RequiredMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
|
||||
FreeMods.Value = initialItem.AllowedMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
|
||||
}
|
||||
|
||||
Freestyle.Value = initialItem.Freestyle;
|
||||
}
|
||||
|
||||
Mods.BindValueChanged(onModsChanged);
|
||||
Ruleset.BindValueChanged(onRulesetChanged);
|
||||
Freestyle.BindValueChanged(onFreestyleChanged, true);
|
||||
|
||||
freeModSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(freeModSelectOverlay);
|
||||
freeModSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(freeModSelect);
|
||||
}
|
||||
|
||||
private void onFreestyleChanged(ValueChangedEvent<bool> enabled)
|
||||
{
|
||||
if (enabled.NewValue)
|
||||
{
|
||||
freeModsFooterButton.Enabled.Value = false;
|
||||
ModsFooterButton.Enabled.Value = false;
|
||||
|
||||
ModSelect.Hide();
|
||||
freeModSelect.Hide();
|
||||
|
||||
Mods.Value = [];
|
||||
FreeMods.Value = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
freeModsFooterButton.Enabled.Value = true;
|
||||
ModsFooterButton.Enabled.Value = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void onModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
||||
@ -121,7 +147,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
FreeMods.Value = FreeMods.Value.Where(checkCompatibleFreeMod).ToList();
|
||||
|
||||
// Reset the validity delegate to update the overlay's display.
|
||||
freeModSelectOverlay.IsValidMod = IsValidFreeMod;
|
||||
freeModSelect.IsValidMod = IsValidFreeMod;
|
||||
}
|
||||
|
||||
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> ruleset)
|
||||
@ -135,7 +161,8 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
RulesetID = Ruleset.Value.OnlineID,
|
||||
RequiredMods = Mods.Value.Select(m => new APIMod(m)).ToArray(),
|
||||
AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray()
|
||||
AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray(),
|
||||
Freestyle = Freestyle.Value
|
||||
};
|
||||
|
||||
return SelectItem(item);
|
||||
@ -150,9 +177,9 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
public override bool OnBackButton()
|
||||
{
|
||||
if (freeModSelectOverlay.State.Value == Visibility.Visible)
|
||||
if (freeModSelect.State.Value == Visibility.Visible)
|
||||
{
|
||||
freeModSelectOverlay.Hide();
|
||||
freeModSelect.Hide();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -161,7 +188,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
public override bool OnExiting(ScreenExitEvent e)
|
||||
{
|
||||
freeModSelectOverlay.Hide();
|
||||
freeModSelect.Hide();
|
||||
return base.OnExiting(e);
|
||||
}
|
||||
|
||||
@ -170,12 +197,17 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
IsValidMod = IsValidMod
|
||||
};
|
||||
|
||||
protected override IEnumerable<(FooterButton, OverlayContainer?)> CreateSongSelectFooterButtons()
|
||||
protected override IEnumerable<(FooterButton button, OverlayContainer? overlay)> CreateSongSelectFooterButtons()
|
||||
{
|
||||
var baseButtons = base.CreateSongSelectFooterButtons().ToList();
|
||||
var freeModsButton = new FooterButtonFreeMods(freeModSelectOverlay) { Current = FreeMods };
|
||||
|
||||
baseButtons.Insert(baseButtons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (freeModsButton, freeModSelectOverlay));
|
||||
baseButtons.Single(i => i.button is FooterButtonMods).button.TooltipText = MultiplayerMatchStrings.RequiredModsButtonTooltip;
|
||||
|
||||
baseButtons.InsertRange(baseButtons.FindIndex(b => b.button is FooterButtonMods) + 1, new (FooterButton, OverlayContainer?)[]
|
||||
{
|
||||
(freeModsFooterButton = new FooterButtonFreeMods(freeModSelect) { Current = FreeMods }, null),
|
||||
(new FooterButtonFreestyle { Current = Freestyle }, null)
|
||||
});
|
||||
|
||||
return baseButtons;
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
/// <param name="pivot">An optional pivot around which the scores were retrieved.</param>
|
||||
protected virtual ScoreInfo[] PerformSuccessCallback(Action<IEnumerable<ScoreInfo>> callback, List<MultiplayerScore> scores, MultiplayerScores? pivot = null)
|
||||
{
|
||||
var scoreInfos = scores.Select(s => s.CreateScoreInfo(ScoreManager, Rulesets, PlaylistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray();
|
||||
var scoreInfos = scores.Select(s => s.CreateScoreInfo(ScoreManager, Rulesets, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray();
|
||||
|
||||
// Invoke callback to add the scores. Exclude the score provided to this screen since it's added already.
|
||||
callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID));
|
||||
|
@ -0,0 +1,37 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
public partial class PlaylistsRoomFreestyleSelect : OnlinePlayFreestyleSelect
|
||||
{
|
||||
public new readonly Bindable<BeatmapInfo?> Beatmap = new Bindable<BeatmapInfo?>();
|
||||
public new readonly Bindable<RulesetInfo?> Ruleset = new Bindable<RulesetInfo?>();
|
||||
|
||||
public PlaylistsRoomFreestyleSelect(Room room, PlaylistItem item)
|
||||
: base(room, item)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool OnStart()
|
||||
{
|
||||
// Beatmaps without a valid online ID are filtered away; this is just a final safety.
|
||||
if (base.Beatmap.Value.BeatmapInfo.OnlineID < 0)
|
||||
return false;
|
||||
|
||||
if (base.Ruleset.Value.OnlineID < 0)
|
||||
return false;
|
||||
|
||||
Beatmap.Value = base.Beatmap.Value.BeatmapInfo;
|
||||
Ruleset.Value = base.Ruleset.Value;
|
||||
this.Exit();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -11,11 +11,13 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
@ -46,6 +48,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
private FillFlowContainer progressSection = null!;
|
||||
private DrawableRoomPlaylist drawablePlaylist = null!;
|
||||
|
||||
private readonly Bindable<BeatmapInfo?> userBeatmap = new Bindable<BeatmapInfo?>();
|
||||
private readonly Bindable<RulesetInfo?> userRuleset = new Bindable<RulesetInfo?>();
|
||||
|
||||
public PlaylistsRoomSubScreen(Room room)
|
||||
: base(room, false) // Editing is temporarily not allowed.
|
||||
{
|
||||
@ -66,6 +71,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
SelectedItem.BindValueChanged(onSelectedItemChanged, true);
|
||||
isIdle.BindValueChanged(_ => updatePollingRate(), true);
|
||||
|
||||
Room.PropertyChanged += onRoomPropertyChanged;
|
||||
@ -74,6 +80,16 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
updateRoomPlaylist();
|
||||
}
|
||||
|
||||
private void onSelectedItemChanged(ValueChangedEvent<PlaylistItem?> item)
|
||||
{
|
||||
// Simplest for now.
|
||||
userBeatmap.Value = null;
|
||||
userRuleset.Value = null;
|
||||
}
|
||||
|
||||
protected override IBeatmapInfo GetGameplayBeatmap() => userBeatmap.Value ?? base.GetGameplayBeatmap();
|
||||
protected override RulesetInfo GetGameplayRuleset() => userRuleset.Value ?? base.GetGameplayRuleset();
|
||||
|
||||
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
@ -168,41 +184,65 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
new Drawable[]
|
||||
{
|
||||
UserModsSection = new FillFlowContainer
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Bottom = 10 },
|
||||
Children = new Drawable[]
|
||||
Children = new[]
|
||||
{
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
UserModsSection = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Bottom = 10 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new UserModSelectButton
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 90,
|
||||
Text = "Select",
|
||||
Action = ShowUserModSelect,
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Current = UserMods,
|
||||
Scale = new Vector2(0.8f),
|
||||
},
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new UserModSelectButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 90,
|
||||
Text = "Select",
|
||||
Action = ShowUserModSelect,
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Current = UserMods,
|
||||
Scale = new Vector2(0.8f),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
UserStyleSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Difficulty"),
|
||||
UserStyleDisplayContainer = new Container<DrawableRoomPlaylistItem>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -274,6 +314,18 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
},
|
||||
};
|
||||
|
||||
protected override void OpenStyleSelection()
|
||||
{
|
||||
if (!this.IsCurrentScreen() || SelectedItem.Value is not PlaylistItem item)
|
||||
return;
|
||||
|
||||
this.Push(new PlaylistsRoomFreestyleSelect(Room, item)
|
||||
{
|
||||
Beatmap = { BindTarget = userBeatmap },
|
||||
Ruleset = { BindTarget = userRuleset }
|
||||
});
|
||||
}
|
||||
|
||||
private void updatePollingRate()
|
||||
{
|
||||
selectionPollingComponent.TimeBetweenPolls.Value = isIdle.Value ? 30000 : 5000;
|
||||
|
@ -39,7 +39,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
ID = room.Playlist.Count == 0 ? 0 : room.Playlist.Max(p => p.ID) + 1,
|
||||
RulesetID = Ruleset.Value.OnlineID,
|
||||
RequiredMods = Mods.Value.Select(m => new APIMod(m)).ToArray(),
|
||||
AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray()
|
||||
AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray(),
|
||||
Freestyle = Freestyle.Value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +90,12 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
if (match && criteria.RulesetCriteria != null)
|
||||
match &= criteria.RulesetCriteria.Matches(BeatmapInfo, criteria);
|
||||
|
||||
if (match && criteria.HasOnlineID == true)
|
||||
match &= BeatmapInfo.OnlineID >= 0;
|
||||
|
||||
if (match && criteria.BeatmapSetId != null)
|
||||
match &= criteria.BeatmapSetId == BeatmapInfo.BeatmapSet?.OnlineID;
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select
|
||||
[CanBeNull]
|
||||
private FilterCriteria currentCriteria;
|
||||
|
||||
public FilterCriteria CreateCriteria()
|
||||
public virtual FilterCriteria CreateCriteria()
|
||||
{
|
||||
string query = searchTextBox.Text;
|
||||
|
||||
|
@ -56,6 +56,9 @@ namespace osu.Game.Screens.Select
|
||||
public RulesetInfo? Ruleset;
|
||||
public IReadOnlyList<Mod>? Mods;
|
||||
public bool AllowConvertedBeatmaps;
|
||||
public int? BeatmapSetId;
|
||||
|
||||
public bool? HasOnlineID;
|
||||
|
||||
private string searchText = string.Empty;
|
||||
|
||||
|
@ -83,6 +83,11 @@ namespace osu.Game.Screens.Select
|
||||
/// </summary>
|
||||
protected Container FooterPanels { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="FooterButton"/> that opens the mod select dialog.
|
||||
/// </summary>
|
||||
protected FooterButton ModsFooterButton { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Whether entering editor mode should be allowed.
|
||||
/// </summary>
|
||||
@ -214,11 +219,11 @@ namespace osu.Game.Screens.Select
|
||||
},
|
||||
}
|
||||
},
|
||||
FilterControl = new FilterControl
|
||||
FilterControl = CreateFilterControl().With(d =>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = FilterControl.HEIGHT,
|
||||
},
|
||||
d.RelativeSizeAxes = Axes.X;
|
||||
d.Height = FilterControl.HEIGHT;
|
||||
}),
|
||||
new GridContainer // used for max width implementation
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -387,6 +392,8 @@ namespace osu.Game.Screens.Select
|
||||
SampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection");
|
||||
}
|
||||
|
||||
protected virtual FilterControl CreateFilterControl() => new FilterControl();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -408,9 +415,9 @@ namespace osu.Game.Screens.Select
|
||||
/// Creates the buttons to be displayed in the footer.
|
||||
/// </summary>
|
||||
/// <returns>A set of <see cref="FooterButton"/> and an optional <see cref="OverlayContainer"/> which the button opens when pressed.</returns>
|
||||
protected virtual IEnumerable<(FooterButton, OverlayContainer?)> CreateSongSelectFooterButtons() => new (FooterButton, OverlayContainer?)[]
|
||||
protected virtual IEnumerable<(FooterButton button, OverlayContainer? overlay)> CreateSongSelectFooterButtons() => new (FooterButton, OverlayContainer?)[]
|
||||
{
|
||||
(new FooterButtonMods { Current = Mods }, ModSelect),
|
||||
(ModsFooterButton = new FooterButtonMods { Current = Mods }, ModSelect),
|
||||
(new FooterButtonRandom
|
||||
{
|
||||
NextRandom = () => Carousel.SelectNextRandom(),
|
||||
|
@ -335,6 +335,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override Task ChangeUserStyle(int? beatmapId, int? rulesetId)
|
||||
{
|
||||
ChangeUserStyle(api.LocalUser.Value.Id, beatmapId, rulesetId);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void ChangeUserStyle(int userId, int? beatmapId, int? rulesetId)
|
||||
{
|
||||
Debug.Assert(ServerRoom != null);
|
||||
|
||||
var user = ServerRoom.Users.Single(u => u.UserID == userId);
|
||||
user.BeatmapId = beatmapId;
|
||||
user.RulesetId = rulesetId;
|
||||
|
||||
((IMultiplayerClient)this).UserStyleChanged(userId, beatmapId, rulesetId);
|
||||
}
|
||||
|
||||
public void ChangeUserMods(int userId, IEnumerable<Mod> newMods)
|
||||
=> ChangeUserMods(userId, newMods.Select(m => new APIMod(m)));
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user