1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 18:07:23 +08:00

Merge branch 'master' into multiplayer-participant-rank

This commit is contained in:
Dean Herbert 2021-02-19 13:16:44 +09:00 committed by GitHub
commit b68dbbceff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 300 additions and 159 deletions

View File

@ -165,10 +165,10 @@ namespace osu.Game.Tests.Online
{
}
public override async Task<BeatmapSetInfo> Import(BeatmapSetInfo item, ArchiveReader archive = null, CancellationToken cancellationToken = default)
public override async Task<BeatmapSetInfo> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
{
await AllowImport.Task;
return await (CurrentImportTask = base.Import(item, archive, cancellationToken));
return await (CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken));
}
}

View File

@ -277,8 +277,7 @@ namespace osu.Game.Tournament.Screens.Editors
userId.Value = user.Id.ToString();
userId.BindValueChanged(idString =>
{
if (!(int.TryParse(idString.NewValue, out var parsed)))
return;
int.TryParse(idString.NewValue, out var parsed);
user.Id = parsed;

View File

@ -217,6 +217,8 @@ namespace osu.Game.Tournament
req.Success += res =>
{
user.Id = res.Id;
user.Username = res.Username;
user.Statistics = res.Statistics;
user.Country = res.Country;
@ -225,7 +227,11 @@ namespace osu.Game.Tournament
success?.Invoke();
};
req.Failure += _ => failure?.Invoke();
req.Failure += _ =>
{
user.Id = 1;
failure?.Invoke();
};
if (immediate)
API.Perform(req);

View File

@ -27,6 +27,8 @@ namespace osu.Game.Audio
protected TrackManagerPreviewTrack CurrentTrack;
private readonly BindableNumber<double> globalTrackVolumeAdjust = new BindableNumber<double>(OsuGameBase.GLOBAL_TRACK_VOLUME_ADJUST);
[BackgroundDependencyLoader]
private void load()
{
@ -35,6 +37,7 @@ namespace osu.Game.Audio
trackStore = new PreviewTrackStore(new OnlineStore());
audio.AddItem(trackStore);
trackStore.AddAdjustment(AdjustableProperty.Volume, globalTrackVolumeAdjust);
trackStore.AddAdjustment(AdjustableProperty.Volume, audio.VolumeTrack);
}

View File

@ -51,7 +51,12 @@ namespace osu.Game.Beatmaps
[JsonProperty(@"tags")]
public string Tags { get; set; }
/// <summary>
/// The time in milliseconds to begin playing the track for preview purposes.
/// If -1, the track should begin playing at 40% of its length.
/// </summary>
public int PreviewTime { get; set; }
public string AudioFile { get; set; }
public string BackgroundFile { get; set; }

View File

@ -266,6 +266,26 @@ namespace osu.Game.Beatmaps
[NotNull]
public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000);
/// <summary>
/// Reads the correct track restart point from beatmap metadata and sets looping to enabled.
/// </summary>
public void PrepareTrackForPreviewLooping()
{
Track.Looping = true;
Track.RestartPoint = Metadata.PreviewTime;
if (Track.RestartPoint == -1)
{
if (!Track.IsLoaded)
{
// force length to be populated (https://github.com/ppy/osu-framework/issues/4202)
Track.Seek(Track.CurrentTime);
}
Track.RestartPoint = 0.4f * Track.Length;
}
}
/// <summary>
/// Transfer a valid audio track into this working beatmap. Used as an optimisation to avoid reload / track swap
/// across difficulties in the same beatmap set.

View File

@ -29,6 +29,14 @@ namespace osu.Game.Collections
/// </summary>
protected virtual bool ShowManageCollectionsItem => true;
private readonly BindableWithCurrent<CollectionFilterMenuItem> current = new BindableWithCurrent<CollectionFilterMenuItem>();
public new Bindable<CollectionFilterMenuItem> Current
{
get => current.Current;
set => current.Current = value;
}
private readonly IBindableList<BeatmapCollection> collections = new BindableList<BeatmapCollection>();
private readonly IBindableList<BeatmapInfo> beatmaps = new BindableList<BeatmapInfo>();
private readonly BindableList<CollectionFilterMenuItem> filters = new BindableList<CollectionFilterMenuItem>();
@ -36,25 +44,28 @@ namespace osu.Game.Collections
[Resolved(CanBeNull = true)]
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
[Resolved(CanBeNull = true)]
private CollectionManager collectionManager { get; set; }
public CollectionFilterDropdown()
{
ItemSource = filters;
}
[BackgroundDependencyLoader(permitNulls: true)]
private void load([CanBeNull] CollectionManager collectionManager)
{
if (collectionManager != null)
collections.BindTo(collectionManager.Collections);
collections.CollectionChanged += (_, __) => collectionsChanged();
collectionsChanged();
Current.Value = new AllBeatmapsCollectionFilterMenuItem();
}
protected override void LoadComplete()
{
base.LoadComplete();
if (collectionManager != null)
collections.BindTo(collectionManager.Collections);
// Dropdown has logic which triggers a change on the bindable with every change to the contained items.
// This is not desirable here, as it leads to multiple filter operations running even though nothing has changed.
// An extra bindable is enough to subvert this behaviour.
base.Current = Current;
collections.BindCollectionChanged((_, __) => collectionsChanged(), true);
Current.BindValueChanged(filterChanged, true);
}

View File

@ -1,6 +1,7 @@
// 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 JetBrains.Annotations;
using osu.Framework.Bindables;
@ -9,7 +10,7 @@ namespace osu.Game.Collections
/// <summary>
/// A <see cref="BeatmapCollection"/> filter.
/// </summary>
public class CollectionFilterMenuItem
public class CollectionFilterMenuItem : IEquatable<CollectionFilterMenuItem>
{
/// <summary>
/// The collection to filter beatmaps from.
@ -33,6 +34,11 @@ namespace osu.Game.Collections
Collection = collection;
CollectionName = Collection?.Name.GetBoundCopy() ?? new Bindable<string>("All beatmaps");
}
public bool Equals(CollectionFilterMenuItem other)
=> other != null && CollectionName.Value == other.CollectionName.Value;
public override int GetHashCode() => CollectionName.Value.GetHashCode();
}
public class AllBeatmapsCollectionFilterMenuItem : CollectionFilterMenuItem

View File

@ -139,22 +139,19 @@ namespace osu.Game.Collections
PostNotification?.Invoke(notification);
var collection = readCollections(stream, notification);
bool importCompleted = false;
Schedule(() =>
{
importCollections(collection);
importCompleted = true;
});
while (!IsDisposed && !importCompleted)
await Task.Delay(10);
await importCollections(collection);
notification.CompletionText = $"Imported {collection.Count} collections";
notification.State = ProgressNotificationState.Completed;
}
private void importCollections(List<BeatmapCollection> newCollections)
private Task importCollections(List<BeatmapCollection> newCollections)
{
var tcs = new TaskCompletionSource<bool>();
Schedule(() =>
{
try
{
foreach (var newCol in newCollections)
{
@ -168,6 +165,17 @@ namespace osu.Game.Collections
existing.Beatmaps.Add(newBeatmap);
}
}
tcs.SetResult(true);
}
catch (Exception e)
{
Logger.Error(e, "Failed to import collection.");
tcs.SetException(e);
}
});
return tcs.Task;
}
private List<BeatmapCollection> readCollections(Stream stream, ProgressNotification notification = null)

View File

@ -38,6 +38,11 @@ namespace osu.Game.Database
{
private const int import_queue_request_concurrency = 1;
/// <summary>
/// The size of a batch import operation before considering it a lower priority operation.
/// </summary>
private const int low_priority_import_batch_size = 1;
/// <summary>
/// A singleton scheduler shared by all <see cref="ArchiveModelManager{TModel,TFileModel}"/>.
/// </summary>
@ -47,6 +52,13 @@ namespace osu.Game.Database
/// </remarks>
private static readonly ThreadedTaskScheduler import_scheduler = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager<TModel, TFileModel>));
/// <summary>
/// A second scheduler for lower priority imports.
/// For simplicity, these will just run in parallel with normal priority imports, but a future refactor would see this implemented via a custom scheduler/queue.
/// See https://gist.github.com/peppy/f0e118a14751fc832ca30dd48ba3876b for an incomplete version of this.
/// </summary>
private static readonly ThreadedTaskScheduler import_scheduler_low_priority = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager<TModel, TFileModel>));
/// <summary>
/// Set an endpoint for notifications to be posted to.
/// </summary>
@ -103,8 +115,11 @@ namespace osu.Game.Database
/// <summary>
/// Import one or more <typeparamref name="TModel"/> items from filesystem <paramref name="paths"/>.
/// This will post notifications tracking progress.
/// </summary>
/// <remarks>
/// This will be treated as a low priority import if more than one path is specified; use <see cref="Import(ImportTask[])"/> to always import at standard priority.
/// This will post notifications tracking progress.
/// </remarks>
/// <param name="paths">One or more archive locations on disk.</param>
public Task Import(params string[] paths)
{
@ -133,13 +148,15 @@ namespace osu.Game.Database
var imported = new List<TModel>();
bool isLowPriorityImport = tasks.Length > low_priority_import_batch_size;
await Task.WhenAll(tasks.Select(async task =>
{
notification.CancellationToken.ThrowIfCancellationRequested();
try
{
var model = await Import(task, notification.CancellationToken);
var model = await Import(task, isLowPriorityImport, notification.CancellationToken);
lock (imported)
{
@ -193,15 +210,16 @@ namespace osu.Game.Database
/// Note that this bypasses the UI flow and should only be used for special cases or testing.
/// </summary>
/// <param name="task">The <see cref="ImportTask"/> containing data about the <typeparamref name="TModel"/> to import.</param>
/// <param name="lowPriority">Whether this is a low priority import.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>The imported model, if successful.</returns>
internal async Task<TModel> Import(ImportTask task, CancellationToken cancellationToken = default)
internal async Task<TModel> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
TModel import;
using (ArchiveReader reader = task.GetReader())
import = await Import(reader, cancellationToken);
import = await Import(reader, lowPriority, cancellationToken);
// We may or may not want to delete the file depending on where it is stored.
// e.g. reconstructing/repairing database with items from default storage.
@ -226,11 +244,12 @@ namespace osu.Game.Database
public Action<IEnumerable<TModel>> PresentImport;
/// <summary>
/// Import an item from an <see cref="ArchiveReader"/>.
/// Silently import an item from an <see cref="ArchiveReader"/>.
/// </summary>
/// <param name="archive">The archive to be imported.</param>
/// <param name="lowPriority">Whether this is a low priority import.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
public Task<TModel> Import(ArchiveReader archive, CancellationToken cancellationToken = default)
public Task<TModel> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
@ -253,7 +272,7 @@ namespace osu.Game.Database
return null;
}
return Import(model, archive, cancellationToken);
return Import(model, archive, lowPriority, cancellationToken);
}
/// <summary>
@ -303,12 +322,13 @@ namespace osu.Game.Database
}
/// <summary>
/// Import an item from a <typeparamref name="TModel"/>.
/// Silently import an item from a <typeparamref name="TModel"/>.
/// </summary>
/// <param name="item">The model to be imported.</param>
/// <param name="archive">An optional archive to use for model population.</param>
/// <param name="lowPriority">Whether this is a low priority import.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
public virtual async Task<TModel> Import(TModel item, ArchiveReader archive = null, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () =>
public virtual async Task<TModel> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
@ -383,7 +403,7 @@ namespace osu.Game.Database
flushEvents(true);
return item;
}, cancellationToken, TaskCreationOptions.HideScheduler, import_scheduler).Unwrap();
}, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap();
/// <summary>
/// Exports an item to a legacy (.zip based) package.

View File

@ -22,7 +22,7 @@ namespace osu.Game.Online.API.Requests.Responses
public double? PP { get; set; }
[JsonProperty(@"room_id")]
public int RoomID { get; set; }
public long RoomID { get; set; }
[JsonProperty("total_score")]
public long TotalScore { get; set; }

View File

@ -95,7 +95,7 @@ namespace osu.Game.Online.Multiplayer
private Room? apiRoom;
// Todo: This is temporary, until the multiplayer server returns the item id on match start or otherwise.
private int playlistItemId;
private long playlistItemId;
[BackgroundDependencyLoader]
private void load()

View File

@ -15,7 +15,7 @@ namespace osu.Game.Online
/// A <see cref="Container"/> for displaying online content which require a local user to be logged in.
/// Shows its children only when the local user is logged in and supports displaying a placeholder if not.
/// </summary>
public abstract class OnlineViewContainer : Container
public class OnlineViewContainer : Container
{
protected LoadingSpinner LoadingSpinner { get; private set; }
@ -30,7 +30,7 @@ namespace osu.Game.Online
[Resolved]
protected IAPIProvider API { get; private set; }
protected OnlineViewContainer(string placeholderMessage)
public OnlineViewContainer(string placeholderMessage)
{
this.placeholderMessage = placeholderMessage;
}

View File

@ -9,11 +9,11 @@ namespace osu.Game.Online.Rooms
{
public class CreateRoomScoreRequest : APIRequest<APIScoreToken>
{
private readonly int roomId;
private readonly int playlistItemId;
private readonly long roomId;
private readonly long playlistItemId;
private readonly string versionHash;
public CreateRoomScoreRequest(int roomId, int playlistItemId, string versionHash)
public CreateRoomScoreRequest(long roomId, long playlistItemId, string versionHash)
{
this.roomId = roomId;
this.playlistItemId = playlistItemId;

View File

@ -7,9 +7,9 @@ namespace osu.Game.Online.Rooms
{
public class GetRoomLeaderboardRequest : APIRequest<APILeaderboard>
{
private readonly int roomId;
private readonly long roomId;
public GetRoomLeaderboardRequest(int roomId)
public GetRoomLeaderboardRequest(long roomId)
{
this.roomId = roomId;
}

View File

@ -7,9 +7,9 @@ namespace osu.Game.Online.Rooms
{
public class GetRoomRequest : APIRequest<Room>
{
public readonly int RoomId;
public readonly long RoomId;
public GetRoomRequest(int roomId)
public GetRoomRequest(long roomId)
{
RoomId = roomId;
}

View File

@ -15,8 +15,8 @@ namespace osu.Game.Online.Rooms
/// </summary>
public class IndexPlaylistScoresRequest : APIRequest<IndexedMultiplayerScores>
{
public readonly int RoomId;
public readonly int PlaylistItemId;
public readonly long RoomId;
public readonly long PlaylistItemId;
[CanBeNull]
public readonly Cursor Cursor;
@ -24,13 +24,13 @@ namespace osu.Game.Online.Rooms
[CanBeNull]
public readonly IndexScoresParams IndexParams;
public IndexPlaylistScoresRequest(int roomId, int playlistItemId)
public IndexPlaylistScoresRequest(long roomId, long playlistItemId)
{
RoomId = roomId;
PlaylistItemId = playlistItemId;
}
public IndexPlaylistScoresRequest(int roomId, int playlistItemId, [NotNull] Cursor cursor, [NotNull] IndexScoresParams indexParams)
public IndexPlaylistScoresRequest(long roomId, long playlistItemId, [NotNull] Cursor cursor, [NotNull] IndexScoresParams indexParams)
: this(roomId, playlistItemId)
{
Cursor = cursor;

View File

@ -18,7 +18,7 @@ namespace osu.Game.Online.Rooms
public class MultiplayerScore
{
[JsonProperty("id")]
public int ID { get; set; }
public long ID { get; set; }
[JsonProperty("user")]
public User User { get; set; }

View File

@ -15,7 +15,7 @@ namespace osu.Game.Online.Rooms
public class PlaylistItem : IEquatable<PlaylistItem>
{
[JsonProperty("id")]
public int ID { get; set; }
public long ID { get; set; }
[JsonProperty("beatmap_id")]
public int BeatmapID { get; set; }

View File

@ -17,7 +17,7 @@ namespace osu.Game.Online.Rooms
{
[Cached]
[JsonProperty("id")]
public readonly Bindable<int?> RoomID = new Bindable<int?>();
public readonly Bindable<long?> RoomID = new Bindable<long?>();
[Cached]
[JsonProperty("name")]

View File

@ -7,11 +7,11 @@ namespace osu.Game.Online.Rooms
{
public class ShowPlaylistUserScoreRequest : APIRequest<MultiplayerScore>
{
private readonly int roomId;
private readonly int playlistItemId;
private readonly long roomId;
private readonly long playlistItemId;
private readonly long userId;
public ShowPlaylistUserScoreRequest(int roomId, int playlistItemId, long userId)
public ShowPlaylistUserScoreRequest(long roomId, long playlistItemId, long userId)
{
this.roomId = roomId;
this.playlistItemId = playlistItemId;

View File

@ -11,12 +11,12 @@ namespace osu.Game.Online.Rooms
{
public class SubmitRoomScoreRequest : APIRequest<MultiplayerScore>
{
private readonly int scoreId;
private readonly int roomId;
private readonly int playlistItemId;
private readonly long scoreId;
private readonly long roomId;
private readonly long playlistItemId;
private readonly ScoreInfo scoreInfo;
public SubmitRoomScoreRequest(int scoreId, int roomId, int playlistItemId, ScoreInfo scoreInfo)
public SubmitRoomScoreRequest(long scoreId, long roomId, long playlistItemId, ScoreInfo scoreInfo)
{
this.scoreId = scoreId;
this.roomId = roomId;

View File

@ -156,7 +156,12 @@ namespace osu.Game
protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager();
private readonly BindableNumber<double> globalTrackVolumeAdjust = new BindableNumber<double>(0.5f);
/// <summary>
/// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects.
/// </summary>
internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.5;
private readonly BindableNumber<double> globalTrackVolumeAdjust = new BindableNumber<double>(GLOBAL_TRACK_VOLUME_ADJUST);
[BackgroundDependencyLoader]
private void load()

View File

@ -30,7 +30,7 @@ namespace osu.Game.Overlays
protected List<APIUpdateStream> Streams;
public ChangelogOverlay()
: base(OverlayColourScheme.Purple)
: base(OverlayColourScheme.Purple, false)
{
}

View File

@ -24,6 +24,7 @@ using osu.Game.Overlays.Chat.Tabs;
using osuTK.Input;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Online;
namespace osu.Game.Overlays
{
@ -118,6 +119,11 @@ namespace osu.Game.Overlays
{
RelativeSizeAxes = Axes.Both,
},
new OnlineViewContainer("Sign in to chat")
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
currentChannelContainer = new Container<DrawableChannel>
{
RelativeSizeAxes = Axes.Both,
@ -152,6 +158,8 @@ namespace osu.Game.Overlays
}
},
loading = new LoadingSpinner(),
},
}
}
},
tabsArea = new TabsArea

View File

@ -14,7 +14,7 @@ namespace osu.Game.Overlays
private readonly Bindable<string> article = new Bindable<string>(null);
public NewsOverlay()
: base(OverlayColourScheme.Purple)
: base(OverlayColourScheme.Purple, false)
{
}

View File

@ -4,6 +4,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
namespace osu.Game.Overlays
{
@ -16,10 +17,16 @@ namespace osu.Game.Overlays
protected readonly LoadingLayer Loading;
private readonly Container content;
protected OnlineOverlay(OverlayColourScheme colourScheme)
protected OnlineOverlay(OverlayColourScheme colourScheme, bool requiresSignIn = true)
: base(colourScheme)
{
base.Content.AddRange(new Drawable[]
var mainContent = requiresSignIn
? new OnlineViewContainer($"Sign in to view the {Header.Title.Title}")
: new Container();
mainContent.RelativeSizeAxes = Axes.Both;
mainContent.AddRange(new Drawable[]
{
ScrollFlow = new OverlayScrollContainer
{
@ -43,6 +50,8 @@ namespace osu.Game.Overlays
},
Loading = new LoadingLayer(true)
});
base.Content.Add(mainContent);
}
}
}

View File

@ -61,7 +61,6 @@ namespace osu.Game.Overlays
LoadComponentAsync(display, loaded =>
{
if (API.IsLoggedIn)
Loading.Hide();
Child = loaded;

View File

@ -111,12 +111,10 @@ namespace osu.Game.Screens.Menu
{
setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash);
if (setInfo != null)
{
initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
}
if (setInfo == null)
return false;
return UsingThemedIntro;
return (initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0])) != null;
}
}
@ -170,7 +168,7 @@ namespace osu.Game.Screens.Menu
{
// Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu.
if (UsingThemedIntro)
Track.Restart();
Track.Start();
}
protected override void LogoArriving(OsuLogo logo, bool resuming)

View File

@ -179,14 +179,15 @@ namespace osu.Game.Screens.Menu
base.OnEntering(last);
buttons.FadeInFromZero(500);
var metadata = Beatmap.Value.Metadata;
if (last is IntroScreen && musicController.TrackLoaded)
{
if (!musicController.CurrentTrack.IsRunning)
var track = musicController.CurrentTrack;
// presume the track is the current beatmap's track. not sure how correct this assumption is but it has worked until now.
if (!track.IsRunning)
{
musicController.CurrentTrack.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * musicController.CurrentTrack.Length);
musicController.CurrentTrack.Start();
Beatmap.Value.PrepareTrackForPreviewLooping();
track.Restart();
}
}

View File

@ -116,7 +116,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
joinedRoom.Value = null;
}
private readonly HashSet<int> ignoredRooms = new HashSet<int>();
private readonly HashSet<long> ignoredRooms = new HashSet<long>();
private void onRoomsReceived(List<Room> received)
{

View File

@ -11,7 +11,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
public class MatchChatDisplay : StandAloneChatDisplay
{
[Resolved(typeof(Room), nameof(Room.RoomID))]
private Bindable<int?> roomId { get; set; }
private Bindable<long?> roomId { get; set; }
[Resolved(typeof(Room), nameof(Room.ChannelId))]
private Bindable<int> channelId { get; set; }

View File

@ -15,7 +15,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
public class MatchLeaderboard : Leaderboard<MatchLeaderboardScope, APIUserScoreAggregate>
{
[Resolved(typeof(Room), nameof(Room.RoomID))]
private Bindable<int?> roomId { get; set; }
private Bindable<long?> roomId { get; set; }
[BackgroundDependencyLoader]
private void load()

View File

@ -78,7 +78,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy();
managerUpdated.BindValueChanged(beatmapUpdated);
UserMods.BindValueChanged(_ => updateMods());
UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods));
}
public override void OnEntering(IScreen last)
@ -97,7 +97,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
{
base.OnResuming(last);
beginHandlingTrack();
updateMods();
Scheduler.AddOnce(UpdateMods);
}
public override bool OnExiting(IScreen next)
@ -128,7 +128,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
.Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType()))
.ToList();
updateMods();
UpdateMods();
Ruleset.Value = SelectedItem.Value.Ruleset.Value;
}
@ -145,7 +145,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
}
private void updateMods()
protected virtual void UpdateMods()
{
if (SelectedItem.Value == null)
return;
@ -173,9 +173,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
if (track != null)
{
track.RestartPoint = Beatmap.Value.Metadata.PreviewTime;
track.Looping = true;
Beatmap.Value.PrepareTrackForPreviewLooping();
music?.EnsurePlayingSomething();
}
}
@ -185,10 +183,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
var track = Beatmap?.Value?.Track;
if (track != null)
{
track.Looping = false;
track.RestartPoint = 0;
}
}
}
}

View File

@ -357,7 +357,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
public class CreateOrUpdateButton : TriangleButton
{
[Resolved(typeof(Room), nameof(Room.RoomID))]
private Bindable<int?> roomId { get; set; }
private Bindable<long?> roomId { get; set; }
protected override void LoadComplete()
{

View File

@ -274,6 +274,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
UserMods.BindValueChanged(onUserModsChanged);
client.LoadRequested += onLoadRequested;
client.RoomUpdated += onRoomUpdated;
isConnected = client.IsConnected.GetBoundCopy();
isConnected.BindValueChanged(connected =>
@ -283,6 +284,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
}, true);
}
protected override void UpdateMods()
{
if (SelectedItem.Value == null || client.LocalUser == null)
return;
// update local mods based on room's reported status for the local user (omitting the base call implementation).
// this makes the server authoritative, and avoids the local user potentially setting mods that the server is not aware of (ie. if the match was started during the selection being changed).
var ruleset = Ruleset.Value.CreateInstance();
Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(ruleset)).Concat(SelectedItem.Value.RequiredMods).ToList();
}
public override bool OnBackButton()
{
if (client.Room != null && settingsOverlay.State.Value == Visibility.Visible)
@ -391,6 +403,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
}
}
private void onRoomUpdated()
{
// user mods may have changed.
Scheduler.AddOnce(UpdateMods);
}
private void onLoadRequested()
{
Debug.Assert(client.Room != null);
@ -408,7 +426,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
base.Dispose(isDisposing);
if (client != null)
{
client.RoomUpdated -= onRoomUpdated;
client.LoadRequested -= onLoadRequested;
}
modSettingChangeTracker?.Dispose();
}

View File

@ -9,7 +9,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
public class MultiplayerResultsScreen : PlaylistsResultsScreen
{
public MultiplayerResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem)
public MultiplayerResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem)
: base(score, roomId, playlistItem, false, false)
{
}

View File

@ -13,7 +13,7 @@ namespace osu.Game.Screens.OnlinePlay
public class OnlinePlayComposite : CompositeDrawable
{
[Resolved(typeof(Room))]
protected Bindable<int?> RoomID { get; private set; }
protected Bindable<long?> RoomID { get; private set; }
[Resolved(typeof(Room), nameof(Room.Name))]
protected Bindable<string> RoomName { get; private set; }

View File

@ -24,7 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
public Action Exited;
[Resolved(typeof(Room), nameof(Room.RoomID))]
protected Bindable<int?> RoomId { get; private set; }
protected Bindable<long?> RoomId { get; private set; }
protected readonly PlaylistItem PlaylistItem;

View File

@ -19,7 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
public class PlaylistsResultsScreen : ResultsScreen
{
private readonly int roomId;
private readonly long roomId;
private readonly PlaylistItem playlistItem;
protected LoadingSpinner LeftSpinner { get; private set; }
@ -32,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
[Resolved]
private IAPIProvider api { get; set; }
public PlaylistsResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry, bool allowWatchingReplay = true)
public PlaylistsResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem, bool allowRetry, bool allowWatchingReplay = true)
: base(score, allowRetry, allowWatchingReplay)
{
this.roomId = roomId;

View File

@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
public override string ShortTitle => "playlist";
[Resolved(typeof(Room), nameof(Room.RoomID))]
private Bindable<int?> roomId { get; set; }
private Bindable<long?> roomId { get; set; }
private MatchSettingsOverlay settingsOverlay;
private MatchLeaderboard leaderboard;

View File

@ -72,19 +72,6 @@ namespace osu.Game.Screens.Play.HUD
}
},
};
Current.ValueChanged += mods =>
{
iconsContainer.Clear();
foreach (Mod mod in mods.NewValue)
{
iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) });
}
if (IsLoaded)
appearTransform();
};
}
protected override void Dispose(bool isDisposing)
@ -97,7 +84,19 @@ namespace osu.Game.Screens.Play.HUD
{
base.LoadComplete();
Current.BindValueChanged(mods =>
{
iconsContainer.Clear();
if (mods.NewValue != null)
{
foreach (Mod mod in mods.NewValue)
iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) });
appearTransform();
}
}, true);
iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint);
}

View File

@ -25,8 +25,13 @@ namespace osu.Game.Screens.Select.Carousel
public readonly Bindable<CarouselItemState> State = new Bindable<CarouselItemState>(CarouselItemState.NotSelected);
private readonly HoverLayer hoverLayer;
protected override Container<Drawable> Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
private const float corner_radius = 10;
private const float border_thickness = 2.5f;
public CarouselHeader()
{
RelativeSizeAxes = Axes.X;
@ -36,12 +41,12 @@ namespace osu.Game.Screens.Select.Carousel
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 10,
CornerRadius = corner_radius,
BorderColour = new Color4(221, 255, 255, 255),
Children = new Drawable[]
{
Content,
new HoverLayer()
hoverLayer = new HoverLayer()
}
};
}
@ -59,6 +64,8 @@ namespace osu.Game.Screens.Select.Carousel
{
case CarouselItemState.Collapsed:
case CarouselItemState.NotSelected:
hoverLayer.InsetForBorder = false;
BorderContainer.BorderThickness = 0;
BorderContainer.EdgeEffect = new EdgeEffectParameters
{
@ -70,7 +77,9 @@ namespace osu.Game.Screens.Select.Carousel
break;
case CarouselItemState.Selected:
BorderContainer.BorderThickness = 2.5f;
hoverLayer.InsetForBorder = true;
BorderContainer.BorderThickness = border_thickness;
BorderContainer.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
@ -107,6 +116,26 @@ namespace osu.Game.Screens.Select.Carousel
sampleHover = audio.Samples.Get("SongSelect/song-ping");
}
public bool InsetForBorder
{
set
{
if (value)
{
// apply same border as above to avoid applying additive overlay to it (and blowing out the colour).
Masking = true;
CornerRadius = corner_radius;
BorderThickness = border_thickness;
}
else
{
BorderThickness = 0;
CornerRadius = 0;
Masking = false;
}
}
}
protected override bool OnHover(HoverEvent e)
{
box.FadeIn(100, Easing.OutQuint);

View File

@ -44,7 +44,7 @@ namespace osu.Game.Screens.Select
Sort = sortMode.Value,
AllowConvertedBeatmaps = showConverted.Value,
Ruleset = ruleset.Value,
Collection = collectionDropdown?.Current.Value.Collection
Collection = collectionDropdown?.Current.Value?.Collection
};
if (!minimumStars.IsDefault)

View File

@ -648,8 +648,9 @@ namespace osu.Game.Screens.Select
{
Debug.Assert(!isHandlingLooping);
music.CurrentTrack.Looping = isHandlingLooping = true;
isHandlingLooping = true;
ensureTrackLooping(Beatmap.Value, TrackChangeDirection.None);
music.TrackChanged += ensureTrackLooping;
}
@ -665,7 +666,7 @@ namespace osu.Game.Screens.Select
}
private void ensureTrackLooping(WorkingBeatmap beatmap, TrackChangeDirection changeDirection)
=> music.CurrentTrack.Looping = true;
=> beatmap.PrepareTrackForPreviewLooping();
public override bool OnBackButton()
{
@ -719,8 +720,6 @@ namespace osu.Game.Screens.Select
bool isNewTrack = !lastTrack.TryGetTarget(out var last) || last != track;
track.RestartPoint = Beatmap.Value.Metadata.PreviewTime;
if (!track.IsRunning && (music.UserPauseRequested != true || isNewTrack))
music.Play(true);