1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-21 23:51:01 +08:00

Add ranked play models

This commit is contained in:
Dan Balasescu
2026-01-28 13:41:20 +09:00
Unverified
parent c1fc1edd9c
commit 70321e2f89
16 changed files with 376 additions and 0 deletions
@@ -4,6 +4,7 @@
using System;
using MessagePack;
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
using osu.Game.Online.Multiplayer.MatchTypes.RankedPlay;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
namespace osu.Game.Online.Multiplayer
@@ -16,6 +17,7 @@ namespace osu.Game.Online.Multiplayer
[MessagePackObject]
[Union(0, typeof(TeamVersusRoomState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
[Union(1, typeof(MatchmakingRoomState))]
[Union(2, typeof(RankedPlayRoomState))]
public abstract class MatchRoomState
{
}
@@ -5,6 +5,7 @@ using System;
using MessagePack;
using osu.Game.Online.Matchmaking.Events;
using osu.Game.Online.Multiplayer.Countdown;
using osu.Game.Online.RankedPlay;
namespace osu.Game.Online.Multiplayer
{
@@ -17,6 +18,7 @@ namespace osu.Game.Online.Multiplayer
[Union(0, typeof(CountdownStartedEvent))]
[Union(1, typeof(CountdownStoppedEvent))]
[Union(2, typeof(MatchmakingAvatarActionEvent))]
[Union(3, typeof(RankedPlayCardHandReplayEvent))]
public abstract class MatchServerEvent
{
}
@@ -0,0 +1,32 @@
// 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.Diagnostics.CodeAnalysis;
using MessagePack;
namespace osu.Game.Online.Multiplayer.MatchTypes.RankedPlay
{
[Serializable]
[MessagePackObject]
public class RankedPlayCardItem : IEquatable<RankedPlayCardItem>
{
/// <summary>
/// A unique identifier for this card.
/// </summary>
[Key(0)]
public Guid ID { get; set; } = Guid.NewGuid();
public bool Equals(RankedPlayCardItem? other)
=> other != null && ID.Equals(other.ID);
public override bool Equals(object? obj)
=> obj is RankedPlayCardItem other && Equals(other);
[SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
public override int GetHashCode()
{
return ID.GetHashCode();
}
}
}
@@ -0,0 +1,65 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using MessagePack;
namespace osu.Game.Online.Multiplayer.MatchTypes.RankedPlay
{
[Serializable]
[MessagePackObject]
public class RankedPlayRoomState : MatchRoomState
{
/// <summary>
/// The current room stage.
/// </summary>
[Key(0)]
public RankedPlayStage Stage { get; set; }
/// <summary>
/// The current round number (1-based).
/// </summary>
[Key(1)]
public int CurrentRound { get; set; }
/// <summary>
/// A multiplier applied to life point damage.
/// </summary>
[Key(2)]
public double DamageMultiplier { get; set; }
/// <summary>
/// A dictionary containing all users in the room.
/// </summary>
[Key(3)]
public Dictionary<int, RankedPlayUserInfo> Users { get; set; } = [];
/// <summary>
/// The ID of the user currently playing a card.
/// </summary>
[Key(4)]
public int ActiveUserId { get; set; }
/// <summary>
/// The average star rating of all cards.
/// </summary>
[Key(5)]
public double StarRating { get; set; }
/// <summary>
/// The winner of the match.
/// </summary>
[Key(6)]
public int? WinningUserId { get; set; }
/// <summary>
/// The user currently playing a card.
/// </summary>
[IgnoreMember]
public RankedPlayUserInfo ActiveUser => Users[ActiveUserId];
[IgnoreMember]
public RankedPlayUserInfo? WinningUser => WinningUserId == null ? null : Users[WinningUserId.Value];
}
}
@@ -0,0 +1,58 @@
// 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.
namespace osu.Game.Online.Multiplayer.MatchTypes.RankedPlay
{
public enum RankedPlayStage
{
/// <summary>
/// Waiting for clients to join.
/// </summary>
WaitForJoin,
/// <summary>
/// Period of time before the round starts.
/// </summary>
RoundWarmup,
/// <summary>
/// Users are discarding cards and drawing new ones.
/// </summary>
CardDiscard,
/// <summary>
/// Users have finished discarding their cards.
/// </summary>
FinishCardDiscard,
/// <summary>
/// The active user is selecting a card to play.
/// </summary>
CardPlay,
/// <summary>
/// The active user has made a selection, both players should now start downloading it.
/// </summary>
FinishCardPlay,
/// <summary>
/// Period of time before gameplay starts.
/// </summary>
GameplayWarmup,
/// <summary>
/// Gameplay is in progress.
/// </summary>
Gameplay,
/// <summary>
/// Users are viewing the gameplay results
/// </summary>
Results,
/// <summary>
/// The match has concluded.
/// </summary>
Ended
}
}
@@ -0,0 +1,38 @@
// 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 MessagePack;
namespace osu.Game.Online.Multiplayer.MatchTypes.RankedPlay
{
[Serializable]
[MessagePackObject]
public class RankedPlayUserInfo
{
/// <summary>
/// This user's matchmaking rating.
/// </summary>
[Key(0)]
public required int Rating { get; set; }
/// <summary>
/// The current life points.
/// </summary>
[Key(1)]
public int Life { get; set; } = 1_000_000;
/// <summary>
/// The cards in this user's hand.
/// </summary>
[Key(2)]
public List<RankedPlayCardItem> Hand { get; set; } = [];
/// <summary>
/// Rating after conclusion of the match.
/// </summary>
[Key(3)]
public int RatingAfter { get; set; }
}
}
@@ -6,6 +6,7 @@ using MessagePack;
using osu.Game.Online.Matchmaking.Events;
using osu.Game.Online.Multiplayer.Countdown;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Online.RankedPlay;
namespace osu.Game.Online.Multiplayer
{
@@ -19,6 +20,7 @@ namespace osu.Game.Online.Multiplayer
[Union(1, typeof(StartMatchCountdownRequest))]
[Union(2, typeof(StopCountdownRequest))]
[Union(3, typeof(MatchmakingAvatarActionRequest))]
[Union(4, typeof(RankedPlayCardHandReplayRequest))]
public abstract class MatchUserRequest
{
}
@@ -5,6 +5,7 @@ using System;
using MessagePack;
using osu.Game.Online.Matchmaking;
using osu.Game.Online.Multiplayer.Countdown;
using osu.Game.Online.RankedPlay;
namespace osu.Game.Online.Multiplayer
{
@@ -16,6 +17,7 @@ namespace osu.Game.Online.Multiplayer
[Union(1, typeof(ForceGameplayStartCountdown))]
[Union(2, typeof(ServerShuttingDownCountdown))]
[Union(3, typeof(MatchmakingStageCountdown))]
[Union(4, typeof(RankedPlayStageCountdown))]
public abstract class MultiplayerCountdown
{
/// <summary>
@@ -0,0 +1,39 @@
// 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.Threading.Tasks;
using osu.Game.Online.Multiplayer.MatchTypes.RankedPlay;
using osu.Game.Online.Rooms;
namespace osu.Game.Online.RankedPlay
{
public interface IRankedPlayClient
{
/// <summary>
/// Indicates that a card has been added to a user's hand.
/// </summary>
/// <param name="userId">The user whose hand has changed.</param>
/// <param name="card">The card added to the user's hand.</param>
Task RankedPlayCardAdded(int userId, RankedPlayCardItem card);
/// <summary>
/// Indicates that a card has been removed from a user's hand.
/// </summary>
/// <param name="userId">The user whose hand has changed.</param>
/// <param name="card">The card removed from the user's hand.</param>
Task RankedPlayCardRemoved(int userId, RankedPlayCardItem card);
/// <summary>
/// Indicates that a card has been revealed to the local user.
/// </summary>
/// <param name="card">The card that was revealed.</param>
/// <param name="item">The playlist item the card corresponds to.</param>
Task RankedPlayCardRevealed(RankedPlayCardItem card, MultiplayerPlaylistItem item);
/// <summary>
/// Indicates a card was played.
/// </summary>
/// <param name="card">The card played.</param>
Task RankedPlayCardPlayed(RankedPlayCardItem card);
}
}
@@ -0,0 +1,15 @@
// 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.Threading.Tasks;
using osu.Game.Online.Multiplayer.MatchTypes.RankedPlay;
namespace osu.Game.Online.RankedPlay
{
public interface IRankedPlayServer
{
Task DiscardCards(RankedPlayCardItem[] cards);
Task PlayCard(RankedPlayCardItem card);
}
}
@@ -0,0 +1,23 @@
// 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 MessagePack;
using osu.Game.Online.Multiplayer;
namespace osu.Game.Online.RankedPlay
{
[Serializable]
[MessagePackObject]
public class RankedPlayCardHandReplayEvent : MatchServerEvent
{
/// <summary>
/// The user performing the action.
/// </summary>
[Key(0)]
public int UserId { get; set; }
[Key(1)]
public required RankedPlayCardHandReplayFrame[] Frames { get; init; }
}
}
@@ -0,0 +1,35 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using MessagePack;
namespace osu.Game.Online.RankedPlay
{
[Serializable]
[MessagePackObject]
public readonly record struct RankedPlayCardHandReplayFrame
{
/// <summary>
/// Duration in milliseconds since the previous frame.
/// </summary>
[Key(0)]
public required double Delay { get; init; }
/// <summary>
/// Dictionary containing the state of each card.
/// </summary>
[Key(1)]
public required Dictionary<Guid, RankedPlayCardState> Cards { get; init; }
/// <summary>
/// Creates a replay frame that only contains state entries that differ from the previous frame
/// </summary>
public RankedPlayCardHandReplayFrame RelativeTo(RankedPlayCardHandReplayFrame other) => this with
{
Cards = Cards.Where(entry => !other.Cards.Contains(entry)).ToDictionary(),
};
}
}
@@ -0,0 +1,17 @@
// 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 MessagePack;
using osu.Game.Online.Multiplayer;
namespace osu.Game.Online.RankedPlay
{
[Serializable]
[MessagePackObject]
public class RankedPlayCardHandReplayRequest : MatchUserRequest
{
[Key(0)]
public required RankedPlayCardHandReplayFrame[] Frames { get; init; }
}
}
@@ -0,0 +1,22 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using MessagePack;
namespace osu.Game.Online.RankedPlay
{
[Serializable]
[MessagePackObject]
public readonly record struct RankedPlayCardState
{
[Key(0)]
public required bool Hovered { get; init; }
[Key(1)]
public required bool Pressed { get; init; }
[Key(2)]
public required bool Selected { get; init; }
}
}
@@ -0,0 +1,16 @@
// 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 MessagePack;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.RankedPlay;
namespace osu.Game.Online.RankedPlay
{
[MessagePackObject]
public class RankedPlayStageCountdown : MultiplayerCountdown
{
[Key(2)]
public RankedPlayStage Stage { get; set; }
}
}
@@ -8,7 +8,9 @@ using osu.Game.Online.Matchmaking.Events;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.Countdown;
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
using osu.Game.Online.Multiplayer.MatchTypes.RankedPlay;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Online.RankedPlay;
using osu.Game.Users;
namespace osu.Game.Online
@@ -57,6 +59,12 @@ namespace osu.Game.Online
(typeof(MatchmakingStageCountdown), typeof(MultiplayerCountdown)),
(typeof(MatchmakingAvatarActionRequest), typeof(MatchUserRequest)),
(typeof(MatchmakingAvatarActionEvent), typeof(MatchServerEvent)),
// ranked play
(typeof(RankedPlayRoomState), typeof(MatchRoomState)),
(typeof(RankedPlayStageCountdown), typeof(MultiplayerCountdown)),
(typeof(RankedPlayCardHandReplayRequest), typeof(MatchUserRequest)),
(typeof(RankedPlayCardHandReplayEvent), typeof(MatchServerEvent)),
};
}
}