1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-22 21:00:58 +08:00
Files
osu-lazer/osu.Game/Users/UserPanel.cs
T
Dan Balasescu a3b8b9aee9 Implement duels for ranked play (#37556)
I've seen this suggested quite a bit and is a pretty easy implementation
all things considered.

For now, while on the queue screen, you can open up the dashboard
overlay and select another player to duel. This will bring you into an
unranked lobby.


https://github.com/user-attachments/assets/712897a9-9350-4741-899d-59662c722e43
2026-04-29 16:12:27 +09:00

223 lines
7.4 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
using osu.Framework.Screens;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Chat;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Localisation;
using osu.Game.Online.Metadata;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay.Matchmaking.Queue;
using osu.Game.Screens.Play;
using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Users
{
public abstract partial class UserPanel : OsuClickableContainer, IHasContextMenu, IFilterable
{
public readonly APIUser User;
/// <summary>
/// Perform an action in addition to showing the user's profile.
/// This should be used to perform auxiliary tasks and not as a primary action for clicking a user panel (to maintain a consistent UX).
/// </summary>
public new Action? Action;
protected Action ViewProfile { get; private set; } = null!;
protected Drawable Background { get; private set; } = null!;
protected UserPanel(APIUser user)
: base(HoverSampleSet.Button)
{
ArgumentNullException.ThrowIfNull(user);
User = user;
}
[Resolved]
private UserProfileOverlay? profileOverlay { get; set; }
[Resolved]
private IAPIProvider api { get; set; } = null!;
[Resolved]
private ChannelManager? channelManager { get; set; }
[Resolved]
private ChatOverlay? chatOverlay { get; set; }
[Resolved]
private IDialogOverlay? dialogOverlay { get; set; }
[Resolved]
protected OverlayColourProvider? ColourProvider { get; private set; }
[Resolved]
private IPerformFromScreenRunner? performer { get; set; }
[Resolved]
protected OsuColour Colours { get; private set; } = null!;
[Resolved]
private MultiplayerClient? multiplayerClient { get; set; }
[Resolved]
private MetadataClient? metadataClient { get; set; }
[Resolved]
private QueueController? queueController { get; set; }
[BackgroundDependencyLoader]
private void load()
{
Masking = true;
Add(new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourProvider?.Background5 ?? Colours.Gray1
});
var background = CreateBackground();
if (background != null)
Add(background);
Add(CreateLayout());
base.Action = ViewProfile = () =>
{
Action?.Invoke();
profileOverlay?.ShowUser(User);
};
}
// TODO: this whole api is messy. half these Create methods are expected to by the implementation and half are implictly called.
protected abstract Drawable CreateLayout();
/// <summary>
/// Panel background container. Can be null if a panel doesn't want a background under it's layout
/// </summary>
protected virtual Drawable? CreateBackground() => Background = new UserCoverBackground
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
User = User
};
protected OsuSpriteText CreateUsername() => new OsuSpriteText
{
Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold),
Shadow = false,
Text = User.Username,
};
protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar(User, false);
protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.CountryCode)
{
Size = new Vector2(36, 26),
Action = Action,
};
protected Drawable CreateTeamLogo() => new UpdateableTeamFlag(User.Team)
{
Size = new Vector2(52, 26),
};
public MenuItem[] ContextMenuItems
{
get
{
List<MenuItem> items = new List<MenuItem>
{
new OsuMenuItem(ContextMenuStrings.ViewProfile, MenuItemType.Highlighted, ViewProfile)
};
if (User.Equals(api.LocalUser.Value))
return items.ToArray();
items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, () =>
{
channelManager?.OpenPrivateChannel(User);
chatOverlay?.Show();
}));
items.Add(!isUserBlocked()
? new OsuMenuItem(UsersStrings.BlocksButtonBlock, MenuItemType.Destructive, () => dialogOverlay?.Push(ConfirmBlockActionDialog.Block(User)))
: new OsuMenuItem(UsersStrings.BlocksButtonUnblock, MenuItemType.Standard, () => dialogOverlay?.Push(ConfirmBlockActionDialog.Unblock(User))));
if (isUserOnline())
{
items.Add(new OsuMenuItem(ContextMenuStrings.SpectatePlayer, MenuItemType.Standard, () =>
{
if (isUserOnline())
performer?.PerformFromScreen(s => s.Push(new SoloSpectatorScreen(User)));
}));
if (canInviteUser())
{
items.Add(new OsuMenuItem(ContextMenuStrings.InvitePlayer, MenuItemType.Standard, () =>
{
if (canInviteUser())
multiplayerClient!.InvitePlayer(User.Id);
}));
}
if (canDuelUser())
{
items.Add(new OsuMenuItem("Duel", MenuItemType.Standard, () =>
{
if (canDuelUser())
queueController?.IssueDuel(queueController.SelectedPool.Value!, User.Id);
}));
}
}
return items.ToArray();
bool isUserOnline() => metadataClient?.GetPresence(User.OnlineID) != null;
bool canInviteUser() => isUserOnline() && multiplayerClient?.Room?.Users.All(u => u.UserID != User.Id) == true;
bool isUserBlocked() => api.LocalUserState.Blocks.Any(b => b.TargetID == User.OnlineID);
bool canDuelUser() => isUserOnline() && queueController?.SelectedPool.Value != null;
}
}
public IEnumerable<LocalisableString> FilterTerms => [User.Username];
public bool MatchingFilter
{
set
{
if (value)
Show();
else
Hide();
}
}
public bool FilteringActive { get; set; }
}
}