mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 15:47:26 +08:00
Merge pull request #11641 from smoogipoo/freemods
Add support for optional per-user mods in multiplayer (aka freemod)
This commit is contained in:
commit
9258836f10
@ -8,6 +8,8 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -123,5 +125,32 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUserWithMods()
|
||||||
|
{
|
||||||
|
AddStep("add user", () =>
|
||||||
|
{
|
||||||
|
Client.AddUser(new User
|
||||||
|
{
|
||||||
|
Id = 0,
|
||||||
|
Username = "User 0",
|
||||||
|
CurrentModeRank = RNG.Next(1, 100000),
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
|
});
|
||||||
|
|
||||||
|
Client.ChangeUserMods(0, new Mod[]
|
||||||
|
{
|
||||||
|
new OsuModHardRock(),
|
||||||
|
new OsuModDifficultyAdjust { ApproachRate = { Value = 1 } }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
for (var i = MultiplayerUserState.Idle; i < MultiplayerUserState.Results; i++)
|
||||||
|
{
|
||||||
|
var state = i;
|
||||||
|
AddStep($"set state: {state}", () => Client.ChangeUserState(0, state));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,11 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
{
|
{
|
||||||
public class TestSceneMatchSongSelect : RoomTestScene
|
public class TestScenePlaylistsSongSelect : RoomTestScene
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmapManager { get; set; }
|
private BeatmapManager beatmapManager { get; set; }
|
||||||
@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private RulesetStore rulesets;
|
private RulesetStore rulesets;
|
||||||
|
|
||||||
private TestMatchSongSelect songSelect;
|
private TestPlaylistsSongSelect songSelect;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Beatmap.SetDefault();
|
Beatmap.SetDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("create song select", () => LoadScreen(songSelect = new TestMatchSongSelect()));
|
AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect()));
|
||||||
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen());
|
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value));
|
AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestMatchSongSelect : MatchSongSelect
|
private class TestPlaylistsSongSelect : PlaylistsSongSelect
|
||||||
{
|
{
|
||||||
public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails;
|
public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails;
|
||||||
}
|
}
|
@ -1,7 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
|
|
||||||
namespace osu.Game.Online.Multiplayer
|
namespace osu.Game.Online.Multiplayer
|
||||||
@ -55,6 +57,13 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// <param name="beatmapAvailability">The new beatmap availability state of the user.</param>
|
/// <param name="beatmapAvailability">The new beatmap availability state of the user.</param>
|
||||||
Task UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability);
|
Task UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signals that a user in this room changed their local mods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The ID of the user whose mods have changed.</param>
|
||||||
|
/// <param name="mods">The user's new local mods.</param>
|
||||||
|
Task UserModsChanged(int userId, IEnumerable<APIMod> mods);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signals that a match is to be started. This will *only* be sent to clients which are to begin loading at this point.
|
/// Signals that a match is to be started. This will *only* be sent to clients which are to begin loading at this point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
|
|
||||||
namespace osu.Game.Online.Multiplayer
|
namespace osu.Game.Online.Multiplayer
|
||||||
@ -47,6 +49,12 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// <param name="newBeatmapAvailability">The proposed new beatmap availability state.</param>
|
/// <param name="newBeatmapAvailability">The proposed new beatmap availability state.</param>
|
||||||
Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability);
|
Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Change the local user's mods in the currently joined room.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newMods">The proposed new mods, excluding any required by the room itself.</param>
|
||||||
|
Task ChangeUserMods(IEnumerable<APIMod> newMods);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// As the host of a room, start the match.
|
/// As the host of a room, start the match.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -91,6 +92,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested);
|
connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested);
|
||||||
connection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted);
|
connection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted);
|
||||||
connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady);
|
connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady);
|
||||||
|
connection.On<int, IEnumerable<APIMod>>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged);
|
||||||
|
|
||||||
connection.Closed += async ex =>
|
connection.Closed += async ex =>
|
||||||
{
|
{
|
||||||
@ -189,6 +191,14 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeBeatmapAvailability), newBeatmapAvailability);
|
return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeBeatmapAvailability), newBeatmapAvailability);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Task ChangeUserMods(IEnumerable<APIMod> newMods)
|
||||||
|
{
|
||||||
|
if (!isConnected.Value)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeUserMods), newMods);
|
||||||
|
}
|
||||||
|
|
||||||
public override Task StartMatch()
|
public override Task StartMatch()
|
||||||
{
|
{
|
||||||
if (!isConnected.Value)
|
if (!isConnected.Value)
|
||||||
|
@ -30,15 +30,24 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
[NotNull]
|
[NotNull]
|
||||||
[Key(4)]
|
[Key(4)]
|
||||||
public IEnumerable<APIMod> Mods { get; set; } = Enumerable.Empty<APIMod>();
|
public IEnumerable<APIMod> RequiredMods { get; set; } = Enumerable.Empty<APIMod>();
|
||||||
|
|
||||||
|
[NotNull]
|
||||||
|
[Key(5)]
|
||||||
|
public IEnumerable<APIMod> AllowedMods { get; set; } = Enumerable.Empty<APIMod>();
|
||||||
|
|
||||||
public bool Equals(MultiplayerRoomSettings other)
|
public bool Equals(MultiplayerRoomSettings other)
|
||||||
=> BeatmapID == other.BeatmapID
|
=> BeatmapID == other.BeatmapID
|
||||||
&& BeatmapChecksum == other.BeatmapChecksum
|
&& BeatmapChecksum == other.BeatmapChecksum
|
||||||
&& Mods.SequenceEqual(other.Mods)
|
&& RequiredMods.SequenceEqual(other.RequiredMods)
|
||||||
|
&& AllowedMods.SequenceEqual(other.AllowedMods)
|
||||||
&& RulesetID == other.RulesetID
|
&& RulesetID == other.RulesetID
|
||||||
&& Name.Equals(other.Name, StringComparison.Ordinal);
|
&& Name.Equals(other.Name, StringComparison.Ordinal);
|
||||||
|
|
||||||
public override string ToString() => $"Name:{Name} Beatmap:{BeatmapID} ({BeatmapChecksum}) Mods:{string.Join(',', Mods)} Ruleset:{RulesetID}";
|
public override string ToString() => $"Name:{Name}"
|
||||||
|
+ $" Beatmap:{BeatmapID} ({BeatmapChecksum})"
|
||||||
|
+ $" RequiredMods:{string.Join(',', RequiredMods)}"
|
||||||
|
+ $" AllowedMods:{string.Join(',', AllowedMods)}"
|
||||||
|
+ $" Ruleset:{RulesetID}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,12 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using MessagePack;
|
using MessagePack;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
@ -27,6 +31,13 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
[Key(2)]
|
[Key(2)]
|
||||||
public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.LocallyAvailable();
|
public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.LocallyAvailable();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Any mods applicable only to the local user.
|
||||||
|
/// </summary>
|
||||||
|
[Key(3)]
|
||||||
|
[NotNull]
|
||||||
|
public IEnumerable<APIMod> Mods { get; set; } = Enumerable.Empty<APIMod>();
|
||||||
|
|
||||||
[IgnoreMember]
|
[IgnoreMember]
|
||||||
public User? User { get; set; }
|
public User? User { get; set; }
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ using osu.Game.Online.API.Requests.Responses;
|
|||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Online.Rooms.RoomStatuses;
|
using osu.Game.Online.Rooms.RoomStatuses;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
|
|
||||||
@ -191,7 +192,8 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID,
|
BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID,
|
||||||
BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash,
|
BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash,
|
||||||
RulesetID = item.GetOr(existingPlaylistItem).RulesetID,
|
RulesetID = item.GetOr(existingPlaylistItem).RulesetID,
|
||||||
Mods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.Mods
|
RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods,
|
||||||
|
AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,6 +231,14 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
public abstract Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability);
|
public abstract Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Change the local user's mods in the currently joined room.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newMods">The proposed new mods, excluding any required by the room itself.</param>
|
||||||
|
public Task ChangeUserMods(IEnumerable<Mod> newMods) => ChangeUserMods(newMods.Select(m => new APIMod(m)).ToList());
|
||||||
|
|
||||||
|
public abstract Task ChangeUserMods(IEnumerable<APIMod> newMods);
|
||||||
|
|
||||||
public abstract Task StartMatch();
|
public abstract Task StartMatch();
|
||||||
|
|
||||||
Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state)
|
Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state)
|
||||||
@ -377,6 +387,27 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task UserModsChanged(int userId, IEnumerable<APIMod> mods)
|
||||||
|
{
|
||||||
|
if (Room == null)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
Scheduler.Add(() =>
|
||||||
|
{
|
||||||
|
var user = Room?.Users.SingleOrDefault(u => u.UserID == userId);
|
||||||
|
|
||||||
|
// errors here are not critical - user mods are mostly for display.
|
||||||
|
if (user == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
user.Mods = mods;
|
||||||
|
|
||||||
|
RoomUpdated?.Invoke();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
Task IMultiplayerClient.LoadRequested()
|
Task IMultiplayerClient.LoadRequested()
|
||||||
{
|
{
|
||||||
if (Room == null)
|
if (Room == null)
|
||||||
@ -500,7 +531,8 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
beatmap.MD5Hash = settings.BeatmapChecksum;
|
beatmap.MD5Hash = settings.BeatmapChecksum;
|
||||||
|
|
||||||
var ruleset = rulesets.GetRuleset(settings.RulesetID).CreateInstance();
|
var ruleset = rulesets.GetRuleset(settings.RulesetID).CreateInstance();
|
||||||
var mods = settings.Mods.Select(m => m.ToMod(ruleset));
|
var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset));
|
||||||
|
var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset));
|
||||||
|
|
||||||
PlaylistItem playlistItem = new PlaylistItem
|
PlaylistItem playlistItem = new PlaylistItem
|
||||||
{
|
{
|
||||||
@ -510,6 +542,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
};
|
};
|
||||||
|
|
||||||
playlistItem.RequiredMods.AddRange(mods);
|
playlistItem.RequiredMods.AddRange(mods);
|
||||||
|
playlistItem.AllowedMods.AddRange(allowedMods);
|
||||||
|
|
||||||
apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity.
|
apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity.
|
||||||
apiRoom.Playlist.Add(playlistItem);
|
apiRoom.Playlist.Add(playlistItem);
|
||||||
|
63
osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs
Normal file
63
osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// 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.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay
|
||||||
|
{
|
||||||
|
public class FooterButtonFreeMods : FooterButton, IHasCurrentValue<IReadOnlyList<Mod>>
|
||||||
|
{
|
||||||
|
public Bindable<IReadOnlyList<Mod>> Current
|
||||||
|
{
|
||||||
|
get => modDisplay.Current;
|
||||||
|
set => modDisplay.Current = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ModDisplay modDisplay;
|
||||||
|
|
||||||
|
public FooterButtonFreeMods()
|
||||||
|
{
|
||||||
|
ButtonContentContainer.Add(modDisplay = new ModDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
DisplayUnrankedText = false,
|
||||||
|
Scale = new Vector2(0.8f),
|
||||||
|
ExpansionMode = ExpansionMode.AlwaysContracted,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
SelectedColour = colours.Yellow;
|
||||||
|
DeselectedColour = SelectedColour.Opacity(0.5f);
|
||||||
|
Text = @"freemods";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Current.BindValueChanged(_ => updateModDisplay(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateModDisplay()
|
||||||
|
{
|
||||||
|
if (Current.Value?.Count > 0)
|
||||||
|
modDisplay.FadeIn();
|
||||||
|
else
|
||||||
|
modDisplay.FadeOut();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
@ -29,6 +30,11 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
[Resolved(typeof(Room), nameof(Room.Playlist))]
|
[Resolved(typeof(Room), nameof(Room.Playlist))]
|
||||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Any mods applied by/to the local user.
|
||||||
|
/// </summary>
|
||||||
|
protected readonly Bindable<IReadOnlyList<Mod>> UserMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private MusicController music { get; set; }
|
private MusicController music { get; set; }
|
||||||
|
|
||||||
@ -68,6 +74,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
|
|
||||||
managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy();
|
managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy();
|
||||||
managerUpdated.BindValueChanged(beatmapUpdated);
|
managerUpdated.BindValueChanged(beatmapUpdated);
|
||||||
|
|
||||||
|
UserMods.BindValueChanged(_ => updateMods());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnEntering(IScreen last)
|
public override void OnEntering(IScreen last)
|
||||||
@ -86,6 +94,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
{
|
{
|
||||||
base.OnResuming(last);
|
base.OnResuming(last);
|
||||||
beginHandlingTrack();
|
beginHandlingTrack();
|
||||||
|
updateMods();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnExiting(IScreen next)
|
public override bool OnExiting(IScreen next)
|
||||||
@ -108,12 +117,17 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
{
|
{
|
||||||
updateWorkingBeatmap();
|
updateWorkingBeatmap();
|
||||||
|
|
||||||
var item = SelectedItem.Value;
|
if (SelectedItem.Value == null)
|
||||||
|
return;
|
||||||
|
|
||||||
Mods.Value = item?.RequiredMods?.ToArray() ?? Array.Empty<Mod>();
|
// Remove any user mods that are no longer allowed.
|
||||||
|
UserMods.Value = UserMods.Value
|
||||||
|
.Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType()))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
if (item?.Ruleset != null)
|
updateMods();
|
||||||
Ruleset.Value = item.Ruleset.Value;
|
|
||||||
|
Ruleset.Value = SelectedItem.Value.Ruleset.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beatmapUpdated(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet) => Schedule(updateWorkingBeatmap);
|
private void beatmapUpdated(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet) => Schedule(updateWorkingBeatmap);
|
||||||
@ -128,6 +142,14 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
|
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateMods()
|
||||||
|
{
|
||||||
|
if (SelectedItem.Value == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Mods.Value = UserMods.Value.Concat(SelectedItem.Value.RequiredMods).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
private void beginHandlingTrack()
|
private void beginHandlingTrack()
|
||||||
{
|
{
|
||||||
Beatmap.BindValueChanged(applyLoopingToTrack, true);
|
Beatmap.BindValueChanged(applyLoopingToTrack, true);
|
||||||
|
@ -1,70 +1,32 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Humanizer;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Overlays.Mods;
|
|
||||||
using osu.Game.Rulesets;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||||
{
|
{
|
||||||
public class MultiplayerMatchSongSelect : SongSelect, IOnlinePlaySubScreen
|
public class MultiplayerMatchSongSelect : OnlinePlaySongSelect
|
||||||
{
|
{
|
||||||
public string ShortTitle => "song selection";
|
|
||||||
|
|
||||||
public override string Title => ShortTitle.Humanize();
|
|
||||||
|
|
||||||
[Resolved(typeof(Room), nameof(Room.Playlist))]
|
|
||||||
private BindableList<PlaylistItem> playlist { get; set; }
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private StatefulMultiplayerClient client { get; set; }
|
private StatefulMultiplayerClient client { get; set; }
|
||||||
|
|
||||||
private LoadingLayer loadingLayer;
|
private LoadingLayer loadingLayer;
|
||||||
|
|
||||||
private WorkingBeatmap initialBeatmap;
|
|
||||||
private RulesetInfo initialRuleset;
|
|
||||||
private IReadOnlyList<Mod> initialMods;
|
|
||||||
|
|
||||||
private bool itemSelected;
|
|
||||||
|
|
||||||
public MultiplayerMatchSongSelect()
|
|
||||||
{
|
|
||||||
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
AddInternal(loadingLayer = new LoadingLayer(true));
|
AddInternal(loadingLayer = new LoadingLayer(true));
|
||||||
initialBeatmap = Beatmap.Value;
|
|
||||||
initialRuleset = Ruleset.Value;
|
|
||||||
initialMods = Mods.Value.ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnStart()
|
protected override void SelectItem(PlaylistItem item)
|
||||||
{
|
{
|
||||||
itemSelected = true;
|
|
||||||
var item = new PlaylistItem();
|
|
||||||
|
|
||||||
item.Beatmap.Value = Beatmap.Value.BeatmapInfo;
|
|
||||||
item.Ruleset.Value = Ruleset.Value;
|
|
||||||
|
|
||||||
item.RequiredMods.Clear();
|
|
||||||
item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy()));
|
|
||||||
|
|
||||||
// If the client is already in a room, update via the client.
|
// If the client is already in a room, update via the client.
|
||||||
// Otherwise, update the playlist directly in preparation for it to be submitted to the API on match creation.
|
// Otherwise, update the playlist directly in preparation for it to be submitted to the API on match creation.
|
||||||
if (client.Room != null)
|
if (client.Room != null)
|
||||||
@ -89,30 +51,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
playlist.Clear();
|
Playlist.Clear();
|
||||||
playlist.Add(item);
|
Playlist.Add(item);
|
||||||
this.Exit();
|
this.Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool OnExiting(IScreen next)
|
|
||||||
{
|
|
||||||
if (!itemSelected)
|
|
||||||
{
|
|
||||||
Beatmap.Value = initialBeatmap;
|
|
||||||
Ruleset.Value = initialRuleset;
|
|
||||||
Mods.Value = initialMods;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.OnExiting(next);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
|
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
|
||||||
|
|
||||||
protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay { IsValidMod = isValidMod };
|
protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust) && !mod.RequiresConfiguration;
|
||||||
|
|
||||||
private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -13,12 +14,16 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Match;
|
using osu.Game.Screens.OnlinePlay.Match;
|
||||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
using osuTK;
|
||||||
using ParticipantsList = osu.Game.Screens.OnlinePlay.Multiplayer.Participants.ParticipantsList;
|
using ParticipantsList = osu.Game.Screens.OnlinePlay.Multiplayer.Participants.ParticipantsList;
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||||
@ -36,7 +41,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||||
|
|
||||||
|
private ModSelectOverlay userModsSelectOverlay;
|
||||||
private MultiplayerMatchSettingsOverlay settingsOverlay;
|
private MultiplayerMatchSettingsOverlay settingsOverlay;
|
||||||
|
private Drawable userModsSection;
|
||||||
|
|
||||||
private IBindable<bool> isConnected;
|
private IBindable<bool> isConnected;
|
||||||
|
|
||||||
@ -90,18 +97,25 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new GridContainer
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Content = new[]
|
Padding = new MarginPadding { Horizontal = 5, Vertical = 10 },
|
||||||
|
Child = new GridContainer
|
||||||
{
|
{
|
||||||
new Drawable[]
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ColumnDimensions = new[]
|
||||||
{
|
{
|
||||||
new Container
|
new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400),
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
// Main left column
|
||||||
Padding = new MarginPadding { Horizontal = 5, Vertical = 10 },
|
new GridContainer
|
||||||
Child = new GridContainer
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
RowDimensions = new[]
|
RowDimensions = new[]
|
||||||
@ -119,19 +133,62 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
// Spacer
|
||||||
new FillFlowContainer
|
null,
|
||||||
{
|
// Main right column
|
||||||
Anchor = Anchor.Centre,
|
new FillFlowContainer
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Padding = new MarginPadding { Horizontal = 5 },
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new OverlinedHeader("Beatmap"),
|
RelativeSizeAxes = Axes.X,
|
||||||
new BeatmapSelectionControl { RelativeSizeAxes = Axes.X }
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OverlinedHeader("Beatmap"),
|
||||||
|
new BeatmapSelectionControl { RelativeSizeAxes = Axes.X }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
userModsSection = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Margin = new MarginPadding { Top = 10 },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OverlinedHeader("Extra mods"),
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(10, 0),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new PurpleTriangleButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Width = 90,
|
||||||
|
Text = "Select",
|
||||||
|
Action = () => userModsSelectOverlay.Show()
|
||||||
|
},
|
||||||
|
new ModDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
DisplayUnrankedText = false,
|
||||||
|
Current = UserMods,
|
||||||
|
Scale = new Vector2(0.8f),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,6 +230,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
new Dimension(GridSizeMode.AutoSize),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 0.5f,
|
||||||
|
Child = userModsSelectOverlay = new UserModSelectOverlay
|
||||||
|
{
|
||||||
|
SelectedMods = { BindTarget = UserMods },
|
||||||
|
IsValidMod = _ => false
|
||||||
|
}
|
||||||
|
},
|
||||||
settingsOverlay = new MultiplayerMatchSettingsOverlay
|
settingsOverlay = new MultiplayerMatchSettingsOverlay
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -199,6 +268,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
Playlist.BindCollectionChanged(onPlaylistChanged, true);
|
Playlist.BindCollectionChanged(onPlaylistChanged, true);
|
||||||
|
UserMods.BindValueChanged(onUserModsChanged);
|
||||||
|
|
||||||
client.LoadRequested += onLoadRequested;
|
client.LoadRequested += onLoadRequested;
|
||||||
|
|
||||||
@ -218,10 +288,39 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userModsSelectOverlay.State.Value == Visibility.Visible)
|
||||||
|
{
|
||||||
|
userModsSelectOverlay.Hide();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return base.OnBackButton();
|
return base.OnBackButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => SelectedItem.Value = Playlist.FirstOrDefault();
|
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
SelectedItem.Value = Playlist.FirstOrDefault();
|
||||||
|
|
||||||
|
if (SelectedItem.Value?.AllowedMods.Any() != true)
|
||||||
|
{
|
||||||
|
userModsSection.Hide();
|
||||||
|
userModsSelectOverlay.Hide();
|
||||||
|
userModsSelectOverlay.IsValidMod = _ => false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
userModsSection.Show();
|
||||||
|
userModsSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onUserModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
||||||
|
{
|
||||||
|
if (client.Room == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
client.ChangeUserMods(mods.NewValue);
|
||||||
|
}
|
||||||
|
|
||||||
private void onReadyClick()
|
private void onReadyClick()
|
||||||
{
|
{
|
||||||
@ -274,5 +373,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
if (client != null)
|
if (client != null)
|
||||||
client.LoadRequested -= onLoadRequested;
|
client.LoadRequested -= onLoadRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class UserModSelectOverlay : ModSelectOverlay
|
||||||
|
{
|
||||||
|
public UserModSelectOverlay()
|
||||||
|
{
|
||||||
|
CustomiseButton.Alpha = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -15,6 +16,8 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osu.Game.Users.Drawables;
|
using osu.Game.Users.Drawables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -29,6 +32,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private IAPIProvider api { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private RulesetStore rulesets { get; set; }
|
||||||
|
|
||||||
|
private ModDisplay userModsDisplay;
|
||||||
private StateDisplay userStateDisplay;
|
private StateDisplay userStateDisplay;
|
||||||
private SpriteIcon crown;
|
private SpriteIcon crown;
|
||||||
|
|
||||||
@ -121,6 +128,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Margin = new MarginPadding { Right = 70 },
|
||||||
|
Child = userModsDisplay = new ModDisplay
|
||||||
|
{
|
||||||
|
Scale = new Vector2(0.5f),
|
||||||
|
ExpansionMode = ExpansionMode.AlwaysContracted,
|
||||||
|
DisplayUnrankedText = false,
|
||||||
|
}
|
||||||
|
},
|
||||||
userStateDisplay = new StateDisplay
|
userStateDisplay = new StateDisplay
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
@ -142,7 +162,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
|
|
||||||
const double fade_time = 50;
|
const double fade_time = 50;
|
||||||
|
|
||||||
|
var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance();
|
||||||
|
|
||||||
userStateDisplay.Status = User.State;
|
userStateDisplay.Status = User.State;
|
||||||
|
userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList();
|
||||||
|
|
||||||
if (Room.Host?.Equals(User) == true)
|
if (Room.Host?.Equals(User) == true)
|
||||||
crown.FadeIn(fade_time);
|
crown.FadeIn(fade_time);
|
||||||
|
154
osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs
Normal file
154
osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Humanizer;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay
|
||||||
|
{
|
||||||
|
public abstract class OnlinePlaySongSelect : SongSelect, IOnlinePlaySubScreen
|
||||||
|
{
|
||||||
|
public string ShortTitle => "song selection";
|
||||||
|
|
||||||
|
public override string Title => ShortTitle.Humanize();
|
||||||
|
|
||||||
|
public override bool AllowEditing => false;
|
||||||
|
|
||||||
|
[Resolved(typeof(Room), nameof(Room.Playlist))]
|
||||||
|
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
||||||
|
|
||||||
|
private readonly Bindable<IReadOnlyList<Mod>> freeMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||||
|
private readonly FreeModSelectOverlay freeModSelectOverlay;
|
||||||
|
|
||||||
|
private WorkingBeatmap initialBeatmap;
|
||||||
|
private RulesetInfo initialRuleset;
|
||||||
|
private IReadOnlyList<Mod> initialMods;
|
||||||
|
private bool itemSelected;
|
||||||
|
|
||||||
|
protected OnlinePlaySongSelect()
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
|
||||||
|
|
||||||
|
freeModSelectOverlay = new FreeModSelectOverlay
|
||||||
|
{
|
||||||
|
SelectedMods = { BindTarget = freeMods },
|
||||||
|
IsValidMod = IsValidFreeMod,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
initialBeatmap = Beatmap.Value;
|
||||||
|
initialRuleset = Ruleset.Value;
|
||||||
|
initialMods = Mods.Value.ToList();
|
||||||
|
|
||||||
|
FooterPanels.Add(freeModSelectOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
// At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods.
|
||||||
|
// Similarly, freeMods is currently empty but should only contain the allowed mods.
|
||||||
|
Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty<Mod>();
|
||||||
|
freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty<Mod>();
|
||||||
|
|
||||||
|
Ruleset.BindValueChanged(onRulesetChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> ruleset)
|
||||||
|
{
|
||||||
|
freeMods.Value = Array.Empty<Mod>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected sealed override bool OnStart()
|
||||||
|
{
|
||||||
|
itemSelected = true;
|
||||||
|
|
||||||
|
var item = new PlaylistItem();
|
||||||
|
|
||||||
|
item.Beatmap.Value = Beatmap.Value.BeatmapInfo;
|
||||||
|
item.Ruleset.Value = Ruleset.Value;
|
||||||
|
|
||||||
|
item.RequiredMods.Clear();
|
||||||
|
item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy()));
|
||||||
|
|
||||||
|
item.AllowedMods.Clear();
|
||||||
|
item.AllowedMods.AddRange(freeMods.Value.Select(m => m.CreateCopy()));
|
||||||
|
|
||||||
|
SelectItem(item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when the user has requested a selection of a beatmap.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The resultant <see cref="PlaylistItem"/>. This item has not yet been added to the <see cref="Room"/>'s.</param>
|
||||||
|
protected abstract void SelectItem(PlaylistItem item);
|
||||||
|
|
||||||
|
public override bool OnBackButton()
|
||||||
|
{
|
||||||
|
if (freeModSelectOverlay.State.Value == Visibility.Visible)
|
||||||
|
{
|
||||||
|
freeModSelectOverlay.Hide();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnBackButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnExiting(IScreen next)
|
||||||
|
{
|
||||||
|
if (!itemSelected)
|
||||||
|
{
|
||||||
|
Beatmap.Value = initialBeatmap;
|
||||||
|
Ruleset.Value = initialRuleset;
|
||||||
|
Mods.Value = initialMods;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnExiting(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay
|
||||||
|
{
|
||||||
|
IsValidMod = IsValidMod
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons()
|
||||||
|
{
|
||||||
|
var buttons = base.CreateFooterButtons().ToList();
|
||||||
|
buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = freeMods }, freeModSelectOverlay));
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether a given <see cref="Mod"/> is valid for global selection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mod">The <see cref="Mod"/> to check.</param>
|
||||||
|
/// <returns>Whether <paramref name="mod"/> is a valid mod for online play.</returns>
|
||||||
|
protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && !ModUtils.FlattenMod(mod).Any(m => m is ModAutoplay);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether a given <see cref="Mod"/> is valid for per-player free-mod selection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mod">The <see cref="Mod"/> to check.</param>
|
||||||
|
/// <returns>Whether <paramref name="mod"/> is a selectable free-mod.</returns>
|
||||||
|
protected virtual bool IsValidFreeMod(Mod mod) => IsValidMod(mod);
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,6 @@ using osu.Game.Online.Rooms;
|
|||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Match;
|
using osu.Game.Screens.OnlinePlay.Match;
|
||||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||||
using osu.Game.Screens.Select;
|
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using Footer = osu.Game.Screens.OnlinePlay.Match.Components.Footer;
|
using Footer = osu.Game.Screens.OnlinePlay.Match.Components.Footer;
|
||||||
|
|
||||||
@ -188,7 +187,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
|||||||
settingsOverlay = new PlaylistsMatchSettingsOverlay
|
settingsOverlay = new PlaylistsMatchSettingsOverlay
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
EditPlaylist = () => this.Push(new MatchSongSelect()),
|
EditPlaylist = () => this.Push(new PlaylistsSongSelect()),
|
||||||
State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden }
|
State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,48 +1,27 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Humanizer;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Overlays.Mods;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Screens.OnlinePlay;
|
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select
|
namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||||
{
|
{
|
||||||
public class MatchSongSelect : SongSelect, IOnlinePlaySubScreen
|
public class PlaylistsSongSelect : OnlinePlaySongSelect
|
||||||
{
|
{
|
||||||
public Action<PlaylistItem> Selected;
|
|
||||||
|
|
||||||
public string ShortTitle => "song selection";
|
|
||||||
public override string Title => ShortTitle.Humanize();
|
|
||||||
|
|
||||||
public override bool AllowEditing => false;
|
|
||||||
|
|
||||||
[Resolved(typeof(Room), nameof(Room.Playlist))]
|
|
||||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmaps { get; set; }
|
private BeatmapManager beatmaps { get; set; }
|
||||||
|
|
||||||
public MatchSongSelect()
|
|
||||||
{
|
|
||||||
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new MatchBeatmapDetailArea
|
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new MatchBeatmapDetailArea
|
||||||
{
|
{
|
||||||
CreateNewItem = createNewItem
|
CreateNewItem = createNewItem
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override bool OnStart()
|
protected override void SelectItem(PlaylistItem item)
|
||||||
{
|
{
|
||||||
switch (Playlist.Count)
|
switch (Playlist.Count)
|
||||||
{
|
{
|
||||||
@ -56,8 +35,6 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.Exit();
|
this.Exit();
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createNewItem()
|
private void createNewItem()
|
||||||
@ -80,9 +57,5 @@ namespace osu.Game.Screens.Select
|
|||||||
item.RequiredMods.Clear();
|
item.RequiredMods.Clear();
|
||||||
item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy()));
|
item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay { IsValidMod = isValidMod };
|
|
||||||
|
|
||||||
private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -28,19 +28,16 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private readonly List<OverlayContainer> overlays = new List<OverlayContainer>();
|
private readonly List<OverlayContainer> overlays = new List<OverlayContainer>();
|
||||||
|
|
||||||
/// <param name="button">THe button to be added.</param>
|
/// <param name="button">The button to be added.</param>
|
||||||
/// <param name="overlay">The <see cref="OverlayContainer"/> to be toggled by this button.</param>
|
/// <param name="overlay">The <see cref="OverlayContainer"/> to be toggled by this button.</param>
|
||||||
public void AddButton(FooterButton button, OverlayContainer overlay)
|
public void AddButton(FooterButton button, OverlayContainer overlay)
|
||||||
{
|
{
|
||||||
overlays.Add(overlay);
|
if (overlay != null)
|
||||||
button.Action = () => showOverlay(overlay);
|
{
|
||||||
|
overlays.Add(overlay);
|
||||||
|
button.Action = () => showOverlay(overlay);
|
||||||
|
}
|
||||||
|
|
||||||
AddButton(button);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <param name="button">Button to be added.</param>
|
|
||||||
public void AddButton(FooterButton button)
|
|
||||||
{
|
|
||||||
button.Hovered = updateModeLight;
|
button.Hovered = updateModeLight;
|
||||||
button.HoverLost = updateModeLight;
|
button.HoverLost = updateModeLight;
|
||||||
|
|
||||||
|
@ -263,9 +263,8 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
if (Footer != null)
|
if (Footer != null)
|
||||||
{
|
{
|
||||||
Footer.AddButton(new FooterButtonMods { Current = Mods }, ModSelect);
|
foreach (var (button, overlay) in CreateFooterButtons())
|
||||||
Footer.AddButton(new FooterButtonRandom { Action = triggerRandom });
|
Footer.AddButton(button, overlay);
|
||||||
Footer.AddButton(new FooterButtonOptions(), BeatmapOptions);
|
|
||||||
|
|
||||||
BeatmapOptions.AddButton(@"Manage", @"collections", FontAwesome.Solid.Book, colours.Green, () => manageCollectionsDialog?.Show());
|
BeatmapOptions.AddButton(@"Manage", @"collections", FontAwesome.Solid.Book, colours.Green, () => manageCollectionsDialog?.Show());
|
||||||
BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo));
|
BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo));
|
||||||
@ -301,6 +300,17 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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)> CreateFooterButtons() => new (FooterButton, OverlayContainer)[]
|
||||||
|
{
|
||||||
|
(new FooterButtonMods { Current = Mods }, ModSelect),
|
||||||
|
(new FooterButtonRandom { Action = triggerRandom }, null),
|
||||||
|
(new FooterButtonOptions(), BeatmapOptions)
|
||||||
|
};
|
||||||
|
|
||||||
protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay();
|
protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay();
|
||||||
|
|
||||||
protected virtual void ApplyFilterToCarousel(FilterCriteria criteria)
|
protected virtual void ApplyFilterToCarousel(FilterCriteria criteria)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -11,6 +12,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
@ -122,6 +124,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ChangeUserMods(int userId, IEnumerable<Mod> newMods)
|
||||||
|
=> ChangeUserMods(userId, newMods.Select(m => new APIMod(m)).ToList());
|
||||||
|
|
||||||
|
public void ChangeUserMods(int userId, IEnumerable<APIMod> newMods)
|
||||||
|
{
|
||||||
|
Debug.Assert(Room != null);
|
||||||
|
((IMultiplayerClient)this).UserModsChanged(userId, newMods.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task ChangeUserMods(IEnumerable<APIMod> newMods)
|
||||||
|
{
|
||||||
|
ChangeUserMods(api.LocalUser.Value.Id, newMods);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
public override Task StartMatch()
|
public override Task StartMatch()
|
||||||
{
|
{
|
||||||
Debug.Assert(Room != null);
|
Debug.Assert(Room != null);
|
||||||
|
Loading…
Reference in New Issue
Block a user