mirror of
https://github.com/ppy/osu.git
synced 2025-02-22 08:19:05 +08:00
Merge pull request #31850 from smoogipoo/freestyle-mods
Allow user mods in multiplayer freestyle
This commit is contained in:
commit
ecc12abe02
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
@ -342,6 +343,40 @@ namespace osu.Game.Tests.Mods
|
||||
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.055).ToString(), "1.06x");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRoomModValidity()
|
||||
{
|
||||
Assert.IsTrue(ModUtils.IsValidModForMatchType(new OsuModHardRock(), MatchType.Playlists));
|
||||
Assert.IsTrue(ModUtils.IsValidModForMatchType(new OsuModDoubleTime(), MatchType.Playlists));
|
||||
Assert.IsTrue(ModUtils.IsValidModForMatchType(new ModAdaptiveSpeed(), MatchType.Playlists));
|
||||
Assert.IsFalse(ModUtils.IsValidModForMatchType(new OsuModAutoplay(), MatchType.Playlists));
|
||||
Assert.IsFalse(ModUtils.IsValidModForMatchType(new OsuModTouchDevice(), MatchType.Playlists));
|
||||
|
||||
Assert.IsTrue(ModUtils.IsValidModForMatchType(new OsuModHardRock(), MatchType.HeadToHead));
|
||||
Assert.IsTrue(ModUtils.IsValidModForMatchType(new OsuModDoubleTime(), MatchType.HeadToHead));
|
||||
// For now, adaptive speed isn't allowed in multiplayer because it's a per-user rate adjustment.
|
||||
Assert.IsFalse(ModUtils.IsValidModForMatchType(new ModAdaptiveSpeed(), MatchType.HeadToHead));
|
||||
Assert.IsFalse(ModUtils.IsValidModForMatchType(new OsuModAutoplay(), MatchType.HeadToHead));
|
||||
Assert.IsFalse(ModUtils.IsValidModForMatchType(new OsuModTouchDevice(), MatchType.HeadToHead));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRoomFreeModValidity()
|
||||
{
|
||||
Assert.IsTrue(ModUtils.IsValidFreeModForMatchType(new OsuModHardRock(), MatchType.Playlists));
|
||||
Assert.IsTrue(ModUtils.IsValidFreeModForMatchType(new OsuModDoubleTime(), MatchType.Playlists));
|
||||
Assert.IsTrue(ModUtils.IsValidFreeModForMatchType(new ModAdaptiveSpeed(), MatchType.Playlists));
|
||||
Assert.IsFalse(ModUtils.IsValidFreeModForMatchType(new OsuModAutoplay(), MatchType.Playlists));
|
||||
Assert.IsFalse(ModUtils.IsValidFreeModForMatchType(new OsuModTouchDevice(), MatchType.Playlists));
|
||||
|
||||
Assert.IsTrue(ModUtils.IsValidFreeModForMatchType(new OsuModHardRock(), MatchType.HeadToHead));
|
||||
// For now, all rate adjustment mods aren't allowed as free mods in multiplayer.
|
||||
Assert.IsFalse(ModUtils.IsValidFreeModForMatchType(new OsuModDoubleTime(), MatchType.HeadToHead));
|
||||
Assert.IsFalse(ModUtils.IsValidFreeModForMatchType(new ModAdaptiveSpeed(), MatchType.HeadToHead));
|
||||
Assert.IsFalse(ModUtils.IsValidFreeModForMatchType(new OsuModAutoplay(), MatchType.HeadToHead));
|
||||
Assert.IsFalse(ModUtils.IsValidFreeModForMatchType(new OsuModTouchDevice(), MatchType.HeadToHead));
|
||||
}
|
||||
|
||||
public abstract class CustomMod1 : Mod, IModCompatibilitySpecification
|
||||
{
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Y = -ScreenFooter.HEIGHT,
|
||||
Current = { BindTarget = freeModSelectOverlay.SelectedMods },
|
||||
FreeMods = { BindTarget = freeModSelectOverlay.SelectedMods },
|
||||
},
|
||||
footer = new ScreenFooter(),
|
||||
},
|
||||
|
@ -12,11 +12,14 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
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.Rulesets.Catch.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
@ -393,6 +396,40 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestModsAndRuleset()
|
||||
{
|
||||
AddStep("add another user", () =>
|
||||
{
|
||||
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.ChangeUserBeatmapAvailability(0, BeatmapAvailability.LocallyAvailable());
|
||||
});
|
||||
|
||||
AddStep("set user styles", () =>
|
||||
{
|
||||
MultiplayerClient.ChangeUserStyle(API.LocalUser.Value.OnlineID, 259, 1);
|
||||
MultiplayerClient.ChangeUserMods(API.LocalUser.Value.OnlineID,
|
||||
[new APIMod(new TaikoModConstantSpeed()), new APIMod(new TaikoModHidden()), new APIMod(new TaikoModFlashlight()), new APIMod(new TaikoModHardRock())]);
|
||||
|
||||
MultiplayerClient.ChangeUserStyle(0, 259, 2);
|
||||
MultiplayerClient.ChangeUserMods(0,
|
||||
[new APIMod(new CatchModFloatingFruits()), new APIMod(new CatchModHidden()), new APIMod(new CatchModMirror())]);
|
||||
});
|
||||
}
|
||||
|
||||
private void createNewParticipantsList()
|
||||
{
|
||||
ParticipantsList? participantsList = null;
|
||||
|
@ -266,7 +266,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
private void assertQueueTabCount(int count)
|
||||
{
|
||||
string queueTabText = count > 0 ? $"Queue ({count})" : "Queue";
|
||||
string queueTabText = count > 0 ? $"Up next ({count})" : "Up next";
|
||||
AddUntilStep($"Queue tab shows \"{queueTabText}\"", () =>
|
||||
{
|
||||
return this.ChildrenOfType<OsuTabControl<MultiplayerPlaylistDisplayMode>.OsuTabItem>()
|
||||
|
@ -53,13 +53,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 2,
|
||||
Margin = new MarginPadding { Bottom = 2 }
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Margin = new MarginPadding { Top = 5 },
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
|
@ -11,31 +11,22 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Select;
|
||||
using osuTK;
|
||||
using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
public partial class FooterButtonFreeMods : FooterButton, IHasCurrentValue<IReadOnlyList<Mod>>
|
||||
public partial class FooterButtonFreeMods : FooterButton
|
||||
{
|
||||
private readonly BindableWithCurrent<IReadOnlyList<Mod>> current = new BindableWithCurrent<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
public readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>();
|
||||
public readonly IBindable<bool> Freestyle = new Bindable<bool>();
|
||||
|
||||
public Bindable<IReadOnlyList<Mod>> Current
|
||||
{
|
||||
get => current.Current;
|
||||
set
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
current.Current = value;
|
||||
}
|
||||
}
|
||||
protected override bool IsActive => FreeMods.Value.Count > 0;
|
||||
|
||||
public new Action Action { set => throw new NotSupportedException("The click action is handled by the button itself."); }
|
||||
|
||||
@ -104,7 +95,8 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindValueChanged(_ => updateModDisplay(), true);
|
||||
Freestyle.BindValueChanged(_ => updateModDisplay());
|
||||
FreeMods.BindValueChanged(_ => updateModDisplay(), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -114,16 +106,16 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
var availableMods = allAvailableAndValidMods.ToArray();
|
||||
|
||||
Current.Value = Current.Value.Count == availableMods.Length
|
||||
FreeMods.Value = FreeMods.Value.Count == availableMods.Length
|
||||
? Array.Empty<Mod>()
|
||||
: availableMods;
|
||||
}
|
||||
|
||||
private void updateModDisplay()
|
||||
{
|
||||
int currentCount = Current.Value.Count;
|
||||
int currentCount = FreeMods.Value.Count;
|
||||
|
||||
if (currentCount == allAvailableAndValidMods.Count())
|
||||
if (currentCount == allAvailableAndValidMods.Count() || Freestyle.Value)
|
||||
{
|
||||
count.Text = "all";
|
||||
count.FadeColour(colours.Gray2, 200, Easing.OutQuint);
|
||||
|
@ -8,23 +8,18 @@ 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;
|
||||
using osu.Game.Screens.Select;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
public partial class FooterButtonFreestyle : FooterButton, IHasCurrentValue<bool>
|
||||
public partial class FooterButtonFreestyle : FooterButton
|
||||
{
|
||||
private readonly BindableWithCurrent<bool> current = new BindableWithCurrent<bool>();
|
||||
public readonly Bindable<bool> Freestyle = new Bindable<bool>();
|
||||
|
||||
public Bindable<bool> Current
|
||||
{
|
||||
get => current.Current;
|
||||
set => current.Current = value;
|
||||
}
|
||||
protected override bool IsActive => Freestyle.Value;
|
||||
|
||||
public new Action Action { set => throw new NotSupportedException("The click action is handled by the button itself."); }
|
||||
|
||||
@ -37,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
public FooterButtonFreestyle()
|
||||
{
|
||||
// Overwrite any external behaviour as we delegate the main toggle action to a sub-button.
|
||||
base.Action = () => current.Value = !current.Value;
|
||||
base.Action = () => Freestyle.Value = !Freestyle.Value;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -81,12 +76,12 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindValueChanged(_ => updateDisplay(), true);
|
||||
Freestyle.BindValueChanged(_ => updateDisplay(), true);
|
||||
}
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
if (current.Value)
|
||||
if (Freestyle.Value)
|
||||
{
|
||||
text.Text = "on";
|
||||
text.FadeColour(colours.Gray2, 200, Easing.OutQuint);
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
@ -440,11 +439,14 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
|
||||
var rulesetInstance = GetGameplayRuleset().CreateInstance();
|
||||
|
||||
Mod[] allowedMods = item.Freestyle
|
||||
? rulesetInstance.AllMods.OfType<Mod>().Where(m => ModUtils.IsValidFreeModForMatchType(m, Room.Type)).ToArray()
|
||||
: item.AllowedMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
|
||||
|
||||
// Remove any user mods that are no longer allowed.
|
||||
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();
|
||||
UserMods.Value = newUserMods;
|
||||
|
||||
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
|
||||
int beatmapId = GetGameplayBeatmap().OnlineID;
|
||||
@ -455,14 +457,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
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)
|
||||
if (allowedMods.Length > 0)
|
||||
{
|
||||
UserModsSection.Show();
|
||||
UserModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType());
|
||||
@ -474,7 +469,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
UserModsSelectOverlay.IsValidMod = _ => false;
|
||||
}
|
||||
|
||||
if (freestyle)
|
||||
if (item.Freestyle)
|
||||
{
|
||||
UserStyleSection.Show();
|
||||
|
||||
@ -487,7 +482,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
UserStyleDisplayContainer.Child = new DrawableRoomPlaylistItem(gameplayItem, true)
|
||||
{
|
||||
AllowReordering = false,
|
||||
AllowEditing = freestyle,
|
||||
AllowEditing = true,
|
||||
RequestEdit = _ => OpenStyleSelection()
|
||||
};
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
QueueItems.BindCollectionChanged((_, _) => Text.Text = QueueItems.Count > 0 ? $"Queue ({QueueItems.Count})" : "Queue", true);
|
||||
QueueItems.BindCollectionChanged((_, _) => Text.Text = QueueItems.Count > 0 ? $"Up next ({QueueItems.Count})" : "Up next", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ using osu.Framework.Screens;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Select;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
@ -122,9 +121,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
}
|
||||
|
||||
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
|
||||
|
||||
protected override bool IsValidMod(Mod mod) => base.IsValidMod(mod) && mod.ValidForMultiplayer;
|
||||
|
||||
protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && mod.ValidForMultiplayerAsFreeMod;
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
new Drawable?[]
|
||||
{
|
||||
// Participants column
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -118,15 +117,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
}
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
// Beatmap column
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.Absolute, 5),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedHeader("Beatmap") },
|
||||
new Drawable[] { new OverlinedHeader("Beatmap queue") },
|
||||
new Drawable[]
|
||||
{
|
||||
addItemButton = new AddItemButton
|
||||
@ -147,80 +153,67 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
SelectedItem = SelectedItem
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
new[]
|
||||
{
|
||||
new Container
|
||||
UserModsSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Top = 10 },
|
||||
Children = new[]
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
UserModsSection = new FillFlowContainer
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
new UserModSelectButton
|
||||
{
|
||||
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),
|
||||
},
|
||||
}
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 90,
|
||||
Height = 30,
|
||||
Text = "Select",
|
||||
Action = ShowUserModSelect,
|
||||
},
|
||||
}
|
||||
},
|
||||
UserStyleSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Difficulty"),
|
||||
UserStyleDisplayContainer = new Container<DrawableRoomPlaylistItem>
|
||||
new ModDisplay
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
}
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Current = UserMods,
|
||||
Scale = new Vector2(0.8f),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
new[]
|
||||
{
|
||||
UserStyleSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Top = 10 },
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Difficulty"),
|
||||
UserStyleDisplayContainer = new Container<DrawableRoomPlaylistItem>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.Absolute, 5),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
// Main right column
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
@ -161,11 +161,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
Origin = Anchor.CentreRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Margin = new MarginPadding { Right = 70 },
|
||||
Spacing = new Vector2(2),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
userStyleDisplay = new StyleDisplayIcon(),
|
||||
userStyleDisplay = new StyleDisplayIcon
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
userModsDisplay = new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Scale = new Vector2(0.5f),
|
||||
ExpansionMode = ExpansionMode.AlwaysContracted,
|
||||
}
|
||||
@ -209,15 +216,28 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
|
||||
const double fade_time = 50;
|
||||
|
||||
MultiplayerPlaylistItem? currentItem = client.Room.GetCurrentItem();
|
||||
Ruleset? ruleset = currentItem != null ? rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance() : null;
|
||||
if (client.Room.GetCurrentItem() is MultiplayerPlaylistItem currentItem)
|
||||
{
|
||||
int userBeatmapId = User.BeatmapId ?? currentItem.BeatmapID;
|
||||
int userRulesetId = User.RulesetId ?? currentItem.RulesetID;
|
||||
Ruleset? userRuleset = rulesets.GetRuleset(userRulesetId)?.CreateInstance();
|
||||
|
||||
int? currentModeRank = ruleset != null ? User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank : null;
|
||||
userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty;
|
||||
int? currentModeRank = userRuleset == null ? null : User.User?.RulesetsStatistics?.GetValueOrDefault(userRuleset.ShortName)?.GlobalRank;
|
||||
userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty;
|
||||
|
||||
if (userBeatmapId == currentItem.BeatmapID && userRulesetId == currentItem.RulesetID)
|
||||
userStyleDisplay.Style = null;
|
||||
else
|
||||
userStyleDisplay.Style = (userBeatmapId, userRulesetId);
|
||||
|
||||
// If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187
|
||||
// This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix.
|
||||
Schedule(() => userModsDisplay.Current.Value = userRuleset == null ? Array.Empty<Mod>() : User.Mods.Select(m => m.ToMod(userRuleset)).ToList());
|
||||
}
|
||||
|
||||
userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability);
|
||||
|
||||
if ((User.BeatmapAvailability.State == DownloadState.LocallyAvailable) && (User.State != MultiplayerUserState.Spectating))
|
||||
if (User.BeatmapAvailability.State == DownloadState.LocallyAvailable && User.State != MultiplayerUserState.Spectating)
|
||||
{
|
||||
userModsDisplay.FadeIn(fade_time);
|
||||
userStyleDisplay.FadeIn(fade_time);
|
||||
@ -228,20 +248,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
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;
|
||||
|
||||
// If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187
|
||||
// This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix.
|
||||
Schedule(() =>
|
||||
{
|
||||
userModsDisplay.Current.Value = ruleset != null ? User.Mods.Select(m => m.ToMod(ruleset)).ToList() : Array.Empty<Mod>();
|
||||
});
|
||||
}
|
||||
|
||||
public MenuItem[]? ContextMenuItems
|
||||
|
@ -42,7 +42,7 @@ 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>();
|
||||
protected readonly Bindable<bool> Freestyle = new Bindable<bool>(true);
|
||||
|
||||
private readonly Room room;
|
||||
private readonly PlaylistItem? initialItem;
|
||||
@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
freeModSelect = new FreeModSelectOverlay
|
||||
{
|
||||
SelectedMods = { BindTarget = FreeMods },
|
||||
IsValidMod = IsValidFreeMod,
|
||||
IsValidMod = isValidFreeMod,
|
||||
};
|
||||
}
|
||||
|
||||
@ -126,6 +126,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
if (enabled.NewValue)
|
||||
{
|
||||
freeModsFooterButton.Enabled.Value = false;
|
||||
freeModsFooterButton.Enabled.Value = false;
|
||||
ModsFooterButton.Enabled.Value = false;
|
||||
|
||||
@ -144,10 +145,10 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
private void onModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
||||
{
|
||||
FreeMods.Value = FreeMods.Value.Where(checkCompatibleFreeMod).ToList();
|
||||
FreeMods.Value = FreeMods.Value.Where(isValidFreeMod).ToList();
|
||||
|
||||
// Reset the validity delegate to update the overlay's display.
|
||||
freeModSelect.IsValidMod = IsValidFreeMod;
|
||||
freeModSelect.IsValidMod = isValidFreeMod;
|
||||
}
|
||||
|
||||
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> ruleset)
|
||||
@ -194,7 +195,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
protected override ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay(OverlayColourScheme.Plum)
|
||||
{
|
||||
IsValidMod = IsValidMod
|
||||
IsValidMod = isValidMod
|
||||
};
|
||||
|
||||
protected override IEnumerable<(FooterButton button, OverlayContainer? overlay)> CreateSongSelectFooterButtons()
|
||||
@ -205,8 +206,15 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
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)
|
||||
(freeModsFooterButton = new FooterButtonFreeMods(freeModSelect)
|
||||
{
|
||||
FreeMods = { BindTarget = FreeMods },
|
||||
Freestyle = { BindTarget = Freestyle }
|
||||
}, null),
|
||||
(new FooterButtonFreestyle
|
||||
{
|
||||
Freestyle = { BindTarget = Freestyle }
|
||||
}, null)
|
||||
});
|
||||
|
||||
return baseButtons;
|
||||
@ -217,18 +225,18 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
/// </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).All(m => m.UserPlayable);
|
||||
private bool isValidMod(Mod mod) => ModUtils.IsValidModForMatchType(mod, room.Type);
|
||||
|
||||
/// <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) && checkCompatibleFreeMod(mod);
|
||||
|
||||
private bool checkCompatibleFreeMod(Mod mod)
|
||||
=> Mods.Value.All(m => m.Acronym != mod.Acronym) // Mod must not be contained in the required mods.
|
||||
&& ModUtils.CheckCompatibleSet(Mods.Value.Append(mod).ToArray()); // Mod must be compatible with all the required mods.
|
||||
private bool isValidFreeMod(Mod mod) => ModUtils.IsValidFreeModForMatchType(mod, room.Type)
|
||||
// Mod must not be contained in the required mods.
|
||||
&& Mods.Value.All(m => m.Acronym != mod.Acronym)
|
||||
// Mod must be compatible with all the required mods.
|
||||
&& ModUtils.CheckCompatibleSet(Mods.Value.Append(mod).ToArray());
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
|
@ -146,7 +146,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
new Drawable?[]
|
||||
{
|
||||
// Playlist items column
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -176,73 +175,67 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
new Dimension(),
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
// Middle column (mods and leaderboard)
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
new[]
|
||||
{
|
||||
new Container
|
||||
UserModsSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Bottom = 10 },
|
||||
Children = new[]
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
UserModsSection = new FillFlowContainer
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Bottom = 10 },
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
new UserModSelectButton
|
||||
{
|
||||
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),
|
||||
},
|
||||
}
|
||||
}
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 90,
|
||||
Height = 30,
|
||||
Text = "Select",
|
||||
Action = ShowUserModSelect,
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Current = UserMods,
|
||||
Scale = new Vector2(0.8f),
|
||||
},
|
||||
}
|
||||
},
|
||||
UserStyleSection = new FillFlowContainer
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
new[]
|
||||
{
|
||||
UserStyleSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Bottom = 10 },
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Difficulty"),
|
||||
UserStyleDisplayContainer = new Container<DrawableRoomPlaylistItem>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Difficulty"),
|
||||
UserStyleDisplayContainer = new Container<DrawableRoomPlaylistItem>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
}
|
||||
}
|
||||
},
|
||||
AutoSizeAxes = Axes.Y
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -273,12 +266,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
// Main right column
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
@ -25,6 +25,11 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
protected static readonly Vector2 SHEAR = new Vector2(SHEAR_WIDTH / Footer.HEIGHT, 0);
|
||||
|
||||
/// <summary>
|
||||
/// Used to show an initial animation hinting at the enabled state.
|
||||
/// </summary>
|
||||
protected virtual bool IsActive => false;
|
||||
|
||||
public LocalisableString Text
|
||||
{
|
||||
get => SpriteText?.Text ?? default;
|
||||
@ -124,6 +129,18 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
base.LoadComplete();
|
||||
Enabled.BindValueChanged(_ => updateDisplay(), true);
|
||||
|
||||
if (IsActive)
|
||||
{
|
||||
box.ClearTransforms();
|
||||
|
||||
using (box.BeginDelayedSequence(200))
|
||||
{
|
||||
box.FadeIn(200)
|
||||
.Then()
|
||||
.FadeOut(1500, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Action Hovered;
|
||||
|
@ -8,6 +8,7 @@ using System.Linq;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
@ -292,5 +293,45 @@ namespace osu.Game.Utils
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a mod can be applied to playlist items in the given match type.
|
||||
/// </summary>
|
||||
/// <param name="mod">The mod to test.</param>
|
||||
/// <param name="type">The match type.</param>
|
||||
public static bool IsValidModForMatchType(Mod mod, MatchType type)
|
||||
{
|
||||
if (mod.Type == ModType.System || !mod.UserPlayable || !mod.HasImplementation)
|
||||
return false;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MatchType.Playlists:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return mod.ValidForMultiplayer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a mod can be applied as a free mod to playlist items in the given match type.
|
||||
/// </summary>
|
||||
/// <param name="mod">The mod to test.</param>
|
||||
/// <param name="type">The match type.</param>
|
||||
public static bool IsValidFreeModForMatchType(Mod mod, MatchType type)
|
||||
{
|
||||
if (mod.Type == ModType.System || !mod.UserPlayable || !mod.HasImplementation)
|
||||
return false;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MatchType.Playlists:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return mod.ValidForMultiplayerAsFreeMod;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user