// Copyright (c) ppy Pty Ltd . 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.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using Newtonsoft.Json; using osu.Game.IO.Serialization.Converters; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms.RoomStatuses; namespace osu.Game.Online.Rooms { [JsonObject(MemberSerialization.OptIn)] public partial class Room : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; /// /// The online room ID. Will be null while the room has not yet been created. /// public long? RoomID { get => roomId; set => SetField(ref roomId, value); } /// /// The room name. /// public string Name { get => name; set => SetField(ref name, value); } /// /// Sets the room password. Will be null after the room is created. /// /// /// To check if the room has a password, use . /// public string? Password { get => password; set { SetField(ref password, value); HasPassword = !string.IsNullOrEmpty(value); } } /// /// Whether the room has a password. /// /// /// To set a password, use . /// [JsonProperty("has_password")] public bool HasPassword { get => hasPassword; private set => SetField(ref hasPassword, value); } /// /// The room host. Will be null while the room has not yet been created. /// public APIUser? Host { get => host; set => SetField(ref host, value); } /// /// The room category. /// public RoomCategory Category { get => category; set => SetField(ref category, value); } /// /// The duration for which the room will be open. Will be null after the room is created. /// /// /// To check the room end time, use . /// public TimeSpan? Duration { get => duration == null ? null : TimeSpan.FromMinutes(duration.Value); set => SetField(ref duration, value == null ? null : (int)value.Value.TotalMinutes); } /// /// The date at which the room was opened. Will be null while the room has not yet been created. /// public DateTimeOffset? StartDate { get => startDate; set => SetField(ref startDate, value); } /// /// The date at which the room will be closed. /// /// /// To set the room duration, use . /// public DateTimeOffset? EndDate { get => endDate; set => SetField(ref endDate, value); } /// /// The maximum number of users allowed in the room. /// public int? MaxParticipants { get => maxParticipants; set => SetField(ref maxParticipants, value); } /// /// The current number of users in the room. /// public int ParticipantCount { get => participantCount; set => SetField(ref participantCount, value); } /// /// The set of most recent participants in the room. /// public IReadOnlyList RecentParticipants { get => recentParticipants; set => SetList(ref recentParticipants, value); } /// /// The match type. /// public MatchType Type { get => type; set => SetField(ref type, value); } /// /// The maximum number of attempts on the playlist. Only valid for playlist rooms. /// public int? MaxAttempts { get => maxAttempts; set => SetField(ref maxAttempts, value); } /// /// The room playlist. /// public IReadOnlyList Playlist { get => playlist; set => SetList(ref playlist, value); } /// /// Describes the items in the playlist. /// public RoomPlaylistItemStats? PlaylistItemStats { get => playlistItemStats; set => SetField(ref playlistItemStats, value); } /// /// Describes the range of difficulty of the room. /// public RoomDifficultyRange? DifficultyRange { get => difficultyRange; set => SetField(ref difficultyRange, value); } /// /// The playlist queueing mode. Only valid for multiplayer rooms. /// public QueueMode QueueMode { get => queueMode; set => SetField(ref queueMode, value); } /// /// Whether to automatically skip map intros. Only valid for multiplayer rooms. /// public bool AutoSkip { get => autoSkip; set => SetField(ref autoSkip, value); } /// /// The amount of time before the match is automatically started. Only valid for multiplayer rooms. /// public TimeSpan AutoStartDuration { get => TimeSpan.FromSeconds(autoStartDuration); set => SetField(ref autoStartDuration, (ushort)value.TotalSeconds); } /// /// Provides some extra scoring statistics for the local user in the room. /// public PlaylistAggregateScore? UserScore { get => userScore; set => SetField(ref userScore, value); } /// /// Represents the current item selected within the room. /// /// /// Only valid for room listing requests (i.e. in the lounge screen), and may not be valid while inside the room. /// public PlaylistItem? CurrentPlaylistItem { get => currentPlaylistItem; set => SetField(ref currentPlaylistItem, value); } /// /// The chat channel id for the room. Will be 0 while the room has not yet been created. /// public int ChannelId { get => channelId; private set => SetField(ref channelId, value); } /// /// The current room status. /// public RoomStatus Status { get => status; set => SetField(ref status, value); } /// /// Describes which players are able to join the room. /// public RoomAvailability Availability { get => availability; set => SetField(ref availability, value); } [OnDeserialized] private void onDeserialised(StreamingContext context) { // API doesn't populate status so let's do it here. if (EndDate != null && DateTimeOffset.Now >= EndDate) Status = new RoomStatusEnded(); else if (HasPassword) Status = new RoomStatusOpenPrivate(); else Status = new RoomStatusOpen(); } [JsonProperty("id")] private long? roomId; [JsonProperty("name")] private string name = string.Empty; [JsonProperty("password")] private string? password; // Not serialised (internal use only). private bool hasPassword; [JsonProperty("host")] private APIUser? host; [JsonProperty("category")] [JsonConverter(typeof(SnakeCaseStringEnumConverter))] private RoomCategory category; [JsonProperty("duration")] private int? duration; [JsonProperty("starts_at")] private DateTimeOffset? startDate; [JsonProperty("ends_at")] private DateTimeOffset? endDate; // Not yet serialised (not implemented). private int? maxParticipants; [JsonProperty("participant_count")] private int participantCount; [JsonProperty("recent_participants")] private IReadOnlyList recentParticipants = []; [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] private int? maxAttempts; [JsonProperty("playlist")] private IReadOnlyList playlist = []; [JsonProperty("playlist_item_stats")] private RoomPlaylistItemStats? playlistItemStats; [JsonProperty("difficulty_range")] private RoomDifficultyRange? difficultyRange; [JsonConverter(typeof(SnakeCaseStringEnumConverter))] [JsonProperty("type")] private MatchType type; [JsonConverter(typeof(SnakeCaseStringEnumConverter))] [JsonProperty("queue_mode")] private QueueMode queueMode; [JsonProperty("auto_skip")] private bool autoSkip; [JsonProperty("auto_start_duration")] private ushort autoStartDuration; [JsonProperty("current_user_score")] private PlaylistAggregateScore? userScore; [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; [JsonProperty("channel_id")] private int channelId; // Not serialised (see: GetRoomsRequest). private RoomStatus status = new RoomStatusOpen(); // Not yet serialised (not implemented). private RoomAvailability availability; /// /// Copies values from another into this one. /// /// /// **Beware**: This will store references between s. /// /// The to copy values from. public void CopyFrom(Room other) { RoomID = other.RoomID; Name = other.Name; Category = other.Category; Host = other.Host; ChannelId = other.ChannelId; Status = other.Status; Availability = other.Availability; HasPassword = other.HasPassword; Type = other.Type; MaxParticipants = other.MaxParticipants; ParticipantCount = other.ParticipantCount; EndDate = other.EndDate; UserScore = other.UserScore; QueueMode = other.QueueMode; AutoStartDuration = other.AutoStartDuration; DifficultyRange = other.DifficultyRange; PlaylistItemStats = other.PlaylistItemStats; CurrentPlaylistItem = other.CurrentPlaylistItem; AutoSkip = other.AutoSkip; Playlist = other.Playlist; RecentParticipants = other.RecentParticipants; } [JsonObject(MemberSerialization.OptIn)] public class RoomPlaylistItemStats { [JsonProperty("count_active")] public int CountActive; [JsonProperty("count_total")] public int CountTotal; [JsonProperty("ruleset_ids")] public int[] RulesetIDs = []; } [JsonObject(MemberSerialization.OptIn)] public class RoomDifficultyRange { [JsonProperty("min")] public double Min; [JsonProperty("max")] public double Max; } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); protected bool SetList(ref IReadOnlyList list, IReadOnlyList value, [CallerMemberName] string propertyName = null!) { if (list.SequenceEqual(value)) return false; list = value; OnPropertyChanged(propertyName); return true; } protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null!) { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(propertyName); return true; } } }